1/*
2 * Copyright 2012, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <ctype.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include <BufferIO.h>
13#include <Directory.h>
14#include <Entry.h>
15#include <File.h>
16#include <String.h>
17
18#include <mail_util.h>
19
20
21struct mail_header_field {
22	const char*	rfc_name;
23	const char*	attr_name;
24	type_code	attr_type;
25	// currently either B_STRING_TYPE and B_TIME_TYPE
26};
27
28
29static const mail_header_field gDefaultFields[] = {
30	{ "To",				B_MAIL_ATTR_TO,			B_STRING_TYPE },
31	{ "From",         	B_MAIL_ATTR_FROM,		B_STRING_TYPE },
32	{ "Cc",				B_MAIL_ATTR_CC,			B_STRING_TYPE },
33	{ "Date",         	B_MAIL_ATTR_WHEN,		B_TIME_TYPE   },
34	{ "Delivery-Date",	B_MAIL_ATTR_WHEN,		B_TIME_TYPE   },
35	{ "Reply-To",     	B_MAIL_ATTR_REPLY,		B_STRING_TYPE },
36	{ "Subject",      	B_MAIL_ATTR_SUBJECT,	B_STRING_TYPE },
37	{ "X-Priority",		B_MAIL_ATTR_PRIORITY,	B_STRING_TYPE },
38		// Priorities with prefered
39	{ "Priority",		B_MAIL_ATTR_PRIORITY,	B_STRING_TYPE },
40		// one first - the numeric
41	{ "X-Msmail-Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE },
42		// one (has more levels).
43	{ "Mime-Version",	B_MAIL_ATTR_MIME,		B_STRING_TYPE },
44	{ "STATUS",       	B_MAIL_ATTR_STATUS,		B_STRING_TYPE },
45	{ "THREAD",       	B_MAIL_ATTR_THREAD,		B_STRING_TYPE },
46	{ "NAME",       	B_MAIL_ATTR_NAME,		B_STRING_TYPE },
47	{ NULL,				NULL,					0 }
48};
49
50
51enum mode {
52	PARSE_HEADER,
53	EXTRACT_FROM_HEADER,
54	PARSE_FIELDS
55};
56static mode gParseMode = PARSE_HEADER;
57
58
59void
60format_filter(BFile& file)
61{
62	BMessage message;
63
64	BString header;
65	off_t size;
66	if (file.GetSize(&size) == B_OK) {
67		if (size > 8192)
68			size = 8192;
69		char* buffer = header.LockBuffer(size);
70		file.Read(buffer, size);
71		header.UnlockBuffer(size);
72	}
73
74	for (int i = 0; gDefaultFields[i].rfc_name; ++i) {
75		BString target;
76		if (extract_from_header(header, gDefaultFields[i].rfc_name, target)
77				== B_OK)
78			message.AddString(gDefaultFields[i].rfc_name, target);
79	}
80}
81
82
83status_t
84parse_fields(BPositionIO& input, size_t maxSize)
85{
86	char* buffer = (char*)malloc(maxSize);
87	if (buffer == NULL)
88		return B_NO_MEMORY;
89
90	BMessage header;
91
92	ssize_t bytesRead = input.Read(buffer, maxSize);
93	for (int pos = 0; pos < bytesRead; pos++) {
94		const char* target = NULL;
95		int fieldIndex = 0;
96		int fieldStart = 0;
97
98		// Test for fields we should retrieve
99		for (int i = 0; gDefaultFields[i].rfc_name; i++) {
100			size_t fieldLength = strlen(gDefaultFields[i].rfc_name);
101			if (!memcmp(&buffer[pos], gDefaultFields[i].rfc_name,
102					fieldLength) && buffer[pos + fieldLength] == ':') {
103				target = gDefaultFields[i].rfc_name;
104				pos += fieldLength + 1;
105				fieldStart = pos;
106				fieldIndex = i;
107				break;
108			}
109		}
110
111		// Find end of line
112		while (pos < bytesRead && buffer[pos] != '\n')
113			pos++;
114
115		// Fill in field
116		if (target != NULL) {
117			// Skip white space
118			while (isspace(buffer[fieldStart]) && fieldStart < pos)
119				fieldStart++;
120
121			int end = pos - 1;
122			while (isspace(buffer[end]) && fieldStart < end - 1)
123				end--;
124
125			char* start = &buffer[fieldStart];
126			size_t sourceLength = end + 1 - fieldStart;
127			size_t length = rfc2047_to_utf8(&start, &sourceLength,
128				sourceLength);
129			start[length] = '\0';
130
131			header.AddString(gDefaultFields[fieldIndex].rfc_name, start);
132			target = NULL;
133		}
134	}
135
136	free(buffer);
137	return B_OK;
138}
139
140
141void
142process_file(const BEntry& entry)
143{
144	BFile file(&entry, B_READ_ONLY);
145	if (file.InitCheck() != B_OK) {
146		fprintf(stderr, "could not open file: %s\n",
147			strerror(file.InitCheck()));
148		return;
149	}
150
151	switch (gParseMode) {
152		case PARSE_HEADER:
153		{
154			BBufferIO bufferIO(&file, 8192, false);
155			BMessage headers;
156			parse_header(headers, bufferIO);
157			break;
158		}
159		case EXTRACT_FROM_HEADER:
160			format_filter(file);
161			break;
162		case PARSE_FIELDS:
163			parse_fields(file, 8192);
164			break;
165	}
166}
167
168
169void
170process_directory(const BEntry& directoryEntry)
171{
172	BDirectory directory(&directoryEntry);
173	BEntry entry;
174	while (directory.GetNextEntry(&entry, false) == B_OK) {
175		if (entry.IsDirectory())
176			process_directory(entry);
177		else
178			process_file(entry);
179	}
180}
181
182
183int
184main(int argc, char** argv)
185{
186	if (argc < 3) {
187		fprintf(stderr, "Expected: <parse|extract|fields> <path-to-mails>\n");
188		return 1;
189	}
190
191	if (!strcmp(argv[1], "parse"))
192		gParseMode = PARSE_HEADER;
193	else if (!strcmp(argv[1], "extract"))
194		gParseMode = EXTRACT_FROM_HEADER;
195	else if (!strcmp(argv[1], "fields"))
196		gParseMode = PARSE_FIELDS;
197	else {
198		fprintf(stderr,
199			"Invalid type. Must be one of parse, extract, or fields.\n");
200		return 1;
201	}
202
203	for (int i = 2; i < argc; i++) {
204		BEntry entry(argv[i]);
205		if (entry.IsDirectory())
206			process_directory(entry);
207		else
208			process_file(entry);
209	}
210}
211