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 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 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 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 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 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