108c63a2dSAxel Dörfler /*
208c63a2dSAxel Dörfler * Copyright 2005-2009, Haiku Inc.
308c63a2dSAxel Dörfler * This file may be used under the terms of the MIT License.
4667176abSJonas Sundström *
508c63a2dSAxel Dörfler * Originally public domain written by Alexander G. M. Smith.
6667176abSJonas Sundström */
7667176abSJonas Sundström
808c63a2dSAxel Dörfler
908c63a2dSAxel Dörfler /*! BeMailToMBox is a utility program (requested by Frank Zschockelt) that
1008c63a2dSAxel Dörfler converts BeOS e-mail files into Unix mailbox files (the kind that Pine
1108c63a2dSAxel Dörfler uses). All the files in the input directory are concatenated with the
1208c63a2dSAxel Dörfler appropriate mbox header lines added between them, and trailing blank lines
1308c63a2dSAxel Dörfler reduced. The resulting text is written to standard output. Command line
1408c63a2dSAxel Dörfler driven.
1508c63a2dSAxel Dörfler */
1608c63a2dSAxel Dörfler
1708c63a2dSAxel Dörfler #include <ctype.h>
1808c63a2dSAxel Dörfler #include <errno.h>
1908c63a2dSAxel Dörfler #include <string.h>
2008c63a2dSAxel Dörfler #include <stdio.h>
2108c63a2dSAxel Dörfler #include <time.h>
22667176abSJonas Sundström
23667176abSJonas Sundström #include <Application.h>
24667176abSJonas Sundström #include <StorageKit.h>
25667176abSJonas Sundström #include <SupportKit.h>
26667176abSJonas Sundström
27667176abSJonas Sundström
2808c63a2dSAxel Dörfler extern const char* __progname;
2908c63a2dSAxel Dörfler static const char* kProgramName = __progname;
30667176abSJonas Sundström
31cc592ed1SAxel Dörfler time_t gDateStampTime;
32cc592ed1SAxel Dörfler // Time value used for stamping each message header. Incremented by 1 second
33cc592ed1SAxel Dörfler // for each message, starts out with the current local time.
34667176abSJonas Sundström
35667176abSJonas Sundström
36cc592ed1SAxel Dörfler /*! Global utility function to display an error message and return. The message
37cc592ed1SAxel Dörfler part describes the error, and if errorNumber is non-zero, gets the string
38cc592ed1SAxel Dörfler ", error code $X (standard description)." appended to it. If the message
39cc592ed1SAxel Dörfler is NULL then it gets defaulted to "Something went wrong".
40667176abSJonas Sundström */
41cc592ed1SAxel Dörfler static void
DisplayErrorMessage(const char * messageString=NULL,status_t errorNumber=0,const char * titleString=NULL)42cc592ed1SAxel Dörfler DisplayErrorMessage(const char* messageString = NULL, status_t errorNumber = 0,
43cc592ed1SAxel Dörfler const char* titleString = NULL)
44667176abSJonas Sundström {
45cc592ed1SAxel Dörfler char errorBuffer[2048];
46667176abSJonas Sundström
47cc592ed1SAxel Dörfler if (titleString == NULL)
48cc592ed1SAxel Dörfler titleString = "Error Message:";
49667176abSJonas Sundström
50cc592ed1SAxel Dörfler if (messageString == NULL) {
51cc592ed1SAxel Dörfler if (errorNumber == B_OK)
52cc592ed1SAxel Dörfler messageString = "No error, no message, why bother?";
53667176abSJonas Sundström else
54cc592ed1SAxel Dörfler messageString = "Error";
55667176abSJonas Sundström }
56667176abSJonas Sundström
57cc592ed1SAxel Dörfler if (errorNumber != 0) {
58*66fa08aaSMurai Takashi snprintf(errorBuffer, sizeof(errorBuffer), "%s: %s (%" B_PRIx32 ")"
59cc592ed1SAxel Dörfler "has occured.", messageString, strerror(errorNumber), errorNumber);
6032ebacf2SAxel Dörfler messageString = errorBuffer;
61667176abSJonas Sundström }
62667176abSJonas Sundström
63cc592ed1SAxel Dörfler fputs(titleString, stderr);
64667176abSJonas Sundström fputc('\n', stderr);
65cc592ed1SAxel Dörfler fputs(messageString, stderr);
66667176abSJonas Sundström fputc('\n', stderr);
67667176abSJonas Sundström }
68667176abSJonas Sundström
69667176abSJonas Sundström
70cc592ed1SAxel Dörfler /*! Determine if a line of text is the start of another message. Pine mailbox
71cc592ed1SAxel Dörfler files have messages that start with a line that could say something like
72cc592ed1SAxel Dörfler "From agmsmith@achilles.net Fri Oct 31 21:19:36 EST 1997" or maybe something
73cc592ed1SAxel Dörfler like "From POPmail Mon Oct 20 21:12:36 1997" or in a more modern format,
74cc592ed1SAxel Dörfler "From agmsmith@achilles.net Tue Sep 4 09:04:11 2001 -0400". I generalise it
75cc592ed1SAxel Dörfler to "From blah Day MMM NN XX:XX:XX TZONE1 YYYY TZONE2". Blah is an e-mail
76cc592ed1SAxel Dörfler address you can ignore (just treat it as a word separated by spaces). Day
77cc592ed1SAxel Dörfler is a 3 letter day of the week. MMM is a 3 letter month name. NN is the two
78cc592ed1SAxel Dörfler digit day of the week, has a leading space if the day is less than 10.
79cc592ed1SAxel Dörfler XX:XX:XX is the time, the X's are digits. TZONE1 is the old style optional
80cc592ed1SAxel Dörfler time zone of 3 capital letters. YYYY is the four digit year. TZONE2 is the
81cc592ed1SAxel Dörfler optional modern time zone info, a plus or minus sign and 4 digits. Returns
82cc592ed1SAxel Dörfler true if the line of text (ended with a NUL byte, no line feed or carriage
83cc592ed1SAxel Dörfler returns at the end) is the start of a message.
84667176abSJonas Sundström */
85cc592ed1SAxel Dörfler bool
IsStartOfMailMessage(char * lineString)86cc592ed1SAxel Dörfler IsStartOfMailMessage(char* lineString)
87667176abSJonas Sundström {
88cc592ed1SAxel Dörfler // It starts with "From "
8932ebacf2SAxel Dörfler if (memcmp("From ", lineString, 5) != 0)
90667176abSJonas Sundström return false;
91667176abSJonas Sundström
92cc592ed1SAxel Dörfler char* string = lineString + 4;
93cc592ed1SAxel Dörfler while (*string == ' ')
94cc592ed1SAxel Dörfler string++;
95667176abSJonas Sundström
96cc592ed1SAxel Dörfler // Skip over the e-mail address (or stop at the end of string).
97667176abSJonas Sundström
98cc592ed1SAxel Dörfler while (*string != ' ' && *string != 0)
99cc592ed1SAxel Dörfler string++;
100cc592ed1SAxel Dörfler while (*string == ' ')
101cc592ed1SAxel Dörfler string++;
102667176abSJonas Sundström
103cc592ed1SAxel Dörfler // TODO: improve this!!!
104cc592ed1SAxel Dörfler
105cc592ed1SAxel Dörfler // Look for the 3 letter day of the week.
106cc592ed1SAxel Dörfler if (memcmp(string, "Mon", 3) != 0 && memcmp(string, "Tue", 3) != 0
107cc592ed1SAxel Dörfler && memcmp(string, "Wed", 3) != 0 && memcmp(string, "Thu", 3) != 0
108cc592ed1SAxel Dörfler && memcmp(string, "Fri", 3) != 0 && memcmp(string, "Sat", 3) != 0
109cc592ed1SAxel Dörfler && memcmp(string, "Sun", 3) != 0) {
110cc592ed1SAxel Dörfler fprintf(stderr, "False alarm, not a valid day of the week in \"%s\""
111cc592ed1SAxel Dörfler ".\n", lineString);
112667176abSJonas Sundström return false;
113667176abSJonas Sundström }
114667176abSJonas Sundström
115cc592ed1SAxel Dörfler string += 3;
116cc592ed1SAxel Dörfler while (*string == ' ')
117cc592ed1SAxel Dörfler string++;
118667176abSJonas Sundström
119cc592ed1SAxel Dörfler // Look for the 3 letter month code.
120cc592ed1SAxel Dörfler if (memcmp(string, "Jan", 3) != 0 && memcmp(string, "Feb", 3) != 0
121cc592ed1SAxel Dörfler && memcmp(string, "Mar", 3) != 0 && memcmp(string, "Apr", 3) != 0
122cc592ed1SAxel Dörfler && memcmp(string, "May", 3) != 0 && memcmp(string, "Jun", 3) != 0
123cc592ed1SAxel Dörfler && memcmp(string, "Jul", 3) != 0 && memcmp(string, "Aug", 3) != 0
124cc592ed1SAxel Dörfler && memcmp(string, "Sep", 3) != 0 && memcmp(string, "Oct", 3) != 0
125cc592ed1SAxel Dörfler && memcmp(string, "Nov", 3) != 0 && memcmp(string, "Dec", 3) != 0) {
126667176abSJonas Sundström fprintf(stderr, "False alarm, not a valid month name in \"%s\".\n",
127cc592ed1SAxel Dörfler lineString);
128667176abSJonas Sundström return false;
129667176abSJonas Sundström }
130667176abSJonas Sundström
131cc592ed1SAxel Dörfler string += 3;
132cc592ed1SAxel Dörfler while (*string == ' ')
133cc592ed1SAxel Dörfler string++;
134667176abSJonas Sundström
135cc592ed1SAxel Dörfler // Skip the day of the month. Require at least one digit.
136cc592ed1SAxel Dörfler if (*string < '0' || *string > '9') {
137667176abSJonas Sundström fprintf(stderr, "False alarm, not a valid day of the "
138cc592ed1SAxel Dörfler "month number in \"%s\".\n", lineString);
139667176abSJonas Sundström return false;
140667176abSJonas Sundström }
141667176abSJonas Sundström
142cc592ed1SAxel Dörfler while (*string >= '0' && *string <= '9')
143cc592ed1SAxel Dörfler string++;
144cc592ed1SAxel Dörfler while (*string == ' ')
145cc592ed1SAxel Dörfler string++;
146667176abSJonas Sundström
147cc592ed1SAxel Dörfler // Check the time. Look for the sequence
148cc592ed1SAxel Dörfler // digit-digit-colon-digit-digit-colon-digit-digit.
149cc592ed1SAxel Dörfler
150cc592ed1SAxel Dörfler if (string[0] < '0' || string[0] > '9'
151cc592ed1SAxel Dörfler || string[1] < '0' || string[1] > '9'
152cc592ed1SAxel Dörfler || string[2] != ':'
153cc592ed1SAxel Dörfler || string[3] < '0' || string[3] > '9'
154cc592ed1SAxel Dörfler || string[4] < '0' || string[4] > '9'
155cc592ed1SAxel Dörfler || string[5] != ':'
156cc592ed1SAxel Dörfler || string[6] < '0' || string[6] > '9'
157cc592ed1SAxel Dörfler || string[7] < '0' || string[7] > '9') {
158667176abSJonas Sundström fprintf(stderr, "False alarm, not a valid time value in \"%s\".\n",
159cc592ed1SAxel Dörfler lineString);
160667176abSJonas Sundström return false;
161667176abSJonas Sundström }
162667176abSJonas Sundström
163cc592ed1SAxel Dörfler string += 8;
164cc592ed1SAxel Dörfler while (*string == ' ')
165cc592ed1SAxel Dörfler string++;
166667176abSJonas Sundström
167cc592ed1SAxel Dörfler // Look for the optional antique 3 capital letter time zone and skip it.
168cc592ed1SAxel Dörfler if (string[0] >= 'A' && string[0] <= 'Z'
169cc592ed1SAxel Dörfler && string[1] >= 'A' && string[1] <= 'Z'
170cc592ed1SAxel Dörfler && string[2] >= 'A' && string[2] <= 'Z') {
171cc592ed1SAxel Dörfler string += 3;
172cc592ed1SAxel Dörfler while (*string == ' ')
173cc592ed1SAxel Dörfler string++;
174667176abSJonas Sundström }
175667176abSJonas Sundström
176cc592ed1SAxel Dörfler // Look for the 4 digit year.
177cc592ed1SAxel Dörfler if (string[0] < '0' || string[0] > '9'
178cc592ed1SAxel Dörfler || string[1] < '0' || string[1] > '9'
179cc592ed1SAxel Dörfler || string[2] < '0' || string[2] > '9'
180cc592ed1SAxel Dörfler || string[3] < '0' || string[3] > '9') {
181667176abSJonas Sundström fprintf(stderr, "False alarm, not a valid 4 digit year in \"%s\".\n",
182cc592ed1SAxel Dörfler lineString);
183667176abSJonas Sundström return false;
184667176abSJonas Sundström }
185667176abSJonas Sundström
186cc592ed1SAxel Dörfler string += 4;
187cc592ed1SAxel Dörfler while (*string == ' ')
188cc592ed1SAxel Dörfler string++;
189667176abSJonas Sundström
190cc592ed1SAxel Dörfler // Look for the optional modern time zone and skip over it if present.
191cc592ed1SAxel Dörfler if ((string[0] == '+' || string[0] == '-')
192cc592ed1SAxel Dörfler && string[1] >= '0' && string[1] <= '9'
193cc592ed1SAxel Dörfler && string[2] >= '0' && string[2] <= '9'
194cc592ed1SAxel Dörfler && string[3] >= '0' && string[3] <= '9'
195cc592ed1SAxel Dörfler && string[4] >= '0' && string[4] <= '9') {
196cc592ed1SAxel Dörfler string += 5;
197cc592ed1SAxel Dörfler while (*string == ' ')
198cc592ed1SAxel Dörfler string++;
199667176abSJonas Sundström }
200667176abSJonas Sundström
201cc592ed1SAxel Dörfler // Look for end of string.
202cc592ed1SAxel Dörfler if (*string != 0) {
203667176abSJonas Sundström fprintf(stderr, "False alarm, extra stuff after the "
204cc592ed1SAxel Dörfler "year/time zone in \"%s\".\n", lineString);
205667176abSJonas Sundström return false;
206667176abSJonas Sundström }
207667176abSJonas Sundström
208667176abSJonas Sundström return true;
209667176abSJonas Sundström }
210667176abSJonas Sundström
211667176abSJonas Sundström
212cc592ed1SAxel Dörfler /*! Read the input file, convert it to mbox format, and write it to standard
213cc592ed1SAxel Dörfler output. Returns zero if successful, a negative error code if an error
214cc592ed1SAxel Dörfler occured.
215667176abSJonas Sundström */
216cc592ed1SAxel Dörfler status_t
ProcessMessageFile(char * fileName)217cc592ed1SAxel Dörfler ProcessMessageFile(char* fileName)
218667176abSJonas Sundström {
219cc592ed1SAxel Dörfler fprintf(stdout, "Now processing: \"%s\"\n", fileName);
220667176abSJonas Sundström
221cc592ed1SAxel Dörfler FILE* inputFile = fopen(fileName, "rb");
222cc592ed1SAxel Dörfler if (inputFile == NULL) {
223cc592ed1SAxel Dörfler DisplayErrorMessage("Unable to open file", errno);
224cc592ed1SAxel Dörfler return errno;
225667176abSJonas Sundström }
226667176abSJonas Sundström
227cc592ed1SAxel Dörfler // Extract a text message from the Mail file.
228667176abSJonas Sundström
229cc592ed1SAxel Dörfler BString messageText;
230cc592ed1SAxel Dörfler int lineNumber = 0;
231667176abSJonas Sundström
232cc592ed1SAxel Dörfler while (!feof(inputFile)) {
233cc592ed1SAxel Dörfler // First read in one line of text.
234cc592ed1SAxel Dörfler char line[102400];
235cc592ed1SAxel Dörfler if (fgets(line, sizeof(line), inputFile) == NULL) {
236cc592ed1SAxel Dörfler if (ferror(inputFile)) {
237cc592ed1SAxel Dörfler char errorString[2048];
238cc592ed1SAxel Dörfler snprintf(errorString, sizeof(errorString),
239cc592ed1SAxel Dörfler "Error while reading from \"%s\"", fileName);
240cc592ed1SAxel Dörfler DisplayErrorMessage(errorString, errno);
24132ebacf2SAxel Dörfler fclose(inputFile);
24232ebacf2SAxel Dörfler return errno;
243667176abSJonas Sundström }
244cc592ed1SAxel Dörfler break;
245cc592ed1SAxel Dörfler // No error, just end of file.
246667176abSJonas Sundström }
247667176abSJonas Sundström
248cc592ed1SAxel Dörfler // Remove any trailing control characters (line feed usually, or CRLF).
249cc592ed1SAxel Dörfler // Might also nuke trailing tabs too. Doesn't usually matter. The main
250cc592ed1SAxel Dörfler // thing is to allow input files with both LF and CRLF endings (and
251cc592ed1SAxel Dörfler // even CR endings if you come from the Macintosh world).
252667176abSJonas Sundström
253cc592ed1SAxel Dörfler char* string = line + strlen(line) - 1;
254cc592ed1SAxel Dörfler while (string >= line && *string < 32)
255cc592ed1SAxel Dörfler string--;
256cc592ed1SAxel Dörfler *(++string) = 0;
257667176abSJonas Sundström
258cc592ed1SAxel Dörfler if (lineNumber == 0 && line[0] == 0) {
259cc592ed1SAxel Dörfler // Skip leading blank lines.
260cc592ed1SAxel Dörfler continue;
261cc592ed1SAxel Dörfler }
262cc592ed1SAxel Dörfler lineNumber++;
263667176abSJonas Sundström
264cc592ed1SAxel Dörfler // Prepend the new mbox message header, if the first line of the message
265cc592ed1SAxel Dörfler // doesn't already have one.
266cc592ed1SAxel Dörfler if (lineNumber == 1 && !IsStartOfMailMessage(line)) {
267cc592ed1SAxel Dörfler time_t timestamp = gDateStampTime++;
268cc592ed1SAxel Dörfler messageText.Append("From baron@be.com ");
269cc592ed1SAxel Dörfler messageText.Append(ctime(×tamp));
270667176abSJonas Sundström }
271667176abSJonas Sundström
272cc592ed1SAxel Dörfler // Append the line to the current message text.
273cc592ed1SAxel Dörfler messageText.Append(line);
274cc592ed1SAxel Dörfler messageText.Append("\n");
275667176abSJonas Sundström }
276667176abSJonas Sundström
277cc592ed1SAxel Dörfler // Remove blank lines from the end of the message (a pet peeve of mine), but
278cc592ed1SAxel Dörfler // end the message with two new lines to separate it from the next message.
279cc592ed1SAxel Dörfler int i = messageText.Length();
280cc592ed1SAxel Dörfler while (i > 0 && (messageText[i - 1] == '\n' || messageText[i - 1] == '\r'))
281667176abSJonas Sundström i--;
282cc592ed1SAxel Dörfler messageText.Truncate(i);
283cc592ed1SAxel Dörfler messageText.Append("\n\n");
284667176abSJonas Sundström
285cc592ed1SAxel Dörfler // Write the message out.
28632ebacf2SAxel Dörfler
28732ebacf2SAxel Dörfler status_t status = B_OK;
28832ebacf2SAxel Dörfler
289cc592ed1SAxel Dörfler if (puts(messageText.String()) < 0) {
290cc592ed1SAxel Dörfler DisplayErrorMessage ("Error while writing the message", errno);
291cc592ed1SAxel Dörfler status = errno;
29232ebacf2SAxel Dörfler }
293667176abSJonas Sundström
294cc592ed1SAxel Dörfler fclose(inputFile);
295cc592ed1SAxel Dörfler return status;
296667176abSJonas Sundström }
297667176abSJonas Sundström
298667176abSJonas Sundström
299cc592ed1SAxel Dörfler int
main(int argc,char ** argv)300cc592ed1SAxel Dörfler main(int argc, char** argv)
301667176abSJonas Sundström {
302cc592ed1SAxel Dörfler BApplication app("application/x-vnd.Haiku-mail2mbox");
303667176abSJonas Sundström
304cc592ed1SAxel Dörfler if (argc <= 1 || argc >= 3) {
3059563f44bSStephan Aßmus printf("%s is a utility for converting Mail e-mail\n", argv[0]);
306667176abSJonas Sundström printf("files to Unix Pine style e-mail files. It could well\n");
307667176abSJonas Sundström printf("work with other Unix style mailbox files. Each message in\n");
308667176abSJonas Sundström printf("the input directory is converted and sent to the standard\n");
309667176abSJonas Sundström printf("output. Usage:\n\n");
31008c63a2dSAxel Dörfler printf("%s InputDirectory >OutputFile\n\n", kProgramName);
311667176abSJonas Sundström printf("Public domain, by Alexander G. M. Smith.\n");
312667176abSJonas Sundström return -10;
313667176abSJonas Sundström }
314667176abSJonas Sundström
315cc592ed1SAxel Dörfler // Set the date stamp to the current time.
316cc592ed1SAxel Dörfler gDateStampTime = time (NULL);
317667176abSJonas Sundström
318cc592ed1SAxel Dörfler // Try to open the input directory.
319cc592ed1SAxel Dörfler char inputPathName[B_PATH_NAME_LENGTH];
320cc592ed1SAxel Dörfler strlcpy(inputPathName, argv[1], sizeof(inputPathName) - 2);
321667176abSJonas Sundström
322cc592ed1SAxel Dörfler char tempString[2048];
323667176abSJonas Sundström
324cc592ed1SAxel Dörfler DIR* dir = opendir(inputPathName);
325cc592ed1SAxel Dörfler if (dir == NULL) {
326cc592ed1SAxel Dörfler sprintf(tempString, "Problems opening directory named \"%s\".",
327cc592ed1SAxel Dörfler inputPathName);
328cc592ed1SAxel Dörfler DisplayErrorMessage(tempString, errno);
329cc592ed1SAxel Dörfler return 1;
330667176abSJonas Sundström }
331667176abSJonas Sundström
332cc592ed1SAxel Dörfler // Append a trailing slash to the directory name, if it needs one.
33332ebacf2SAxel Dörfler if (inputPathName[strlen(inputPathName) - 1] != '/')
334cc592ed1SAxel Dörfler strcat(inputPathName, "/");
335667176abSJonas Sundström
336cc592ed1SAxel Dörfler int messagesDoneCount = 0;
337cc592ed1SAxel Dörfler status_t status = B_OK;
338667176abSJonas Sundström
339cc592ed1SAxel Dörfler while (dirent_t* entry = readdir(dir)) {
340cc592ed1SAxel Dörfler // skip '.' and '..'
341cc592ed1SAxel Dörfler if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
342667176abSJonas Sundström break;
343667176abSJonas Sundström
344cc592ed1SAxel Dörfler strlcpy(tempString, inputPathName, sizeof(tempString));
34532ebacf2SAxel Dörfler strlcat(tempString, entry->d_name, sizeof(tempString));
346cc592ed1SAxel Dörfler
347cc592ed1SAxel Dörfler status = ProcessMessageFile(tempString);
348cc592ed1SAxel Dörfler if (status != B_OK)
349cc592ed1SAxel Dörfler break;
350cc592ed1SAxel Dörfler
351cc592ed1SAxel Dörfler messagesDoneCount++;
352667176abSJonas Sundström }
353cc592ed1SAxel Dörfler
354cc592ed1SAxel Dörfler closedir(dir);
355cc592ed1SAxel Dörfler
356cc592ed1SAxel Dörfler if (status != B_OK) {
357cc592ed1SAxel Dörfler DisplayErrorMessage("Stopping early because an error occured", status);
35832ebacf2SAxel Dörfler return status;
359667176abSJonas Sundström }
360667176abSJonas Sundström
361cc592ed1SAxel Dörfler fprintf(stderr, "Did %d messages successfully.\n", messagesDoneCount);
362667176abSJonas Sundström return 0;
363667176abSJonas Sundström }
364