xref: /haiku/src/tests/kits/mail/header_test.cpp (revision 25a7b01d15612846f332751841da3579db313082)
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 
21 struct 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 
29 static 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 
51 enum mode {
52 	PARSE_HEADER,
53 	EXTRACT_FROM_HEADER,
54 	PARSE_FIELDS
55 };
56 static mode gParseMode = PARSE_HEADER;
57 
58 
59 void
format_filter(BFile & file)60 format_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 
83 status_t
parse_fields(BPositionIO & input,size_t maxSize)84 parse_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 
141 void
process_file(const BEntry & entry)142 process_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 
169 void
process_directory(const BEntry & directoryEntry)170 process_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 
183 int
main(int argc,char ** argv)184 main(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