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