2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include "NGMimeHeaderFieldParser.h"
24 #include "NGMimeHeaderFields.h"
25 #include "NGMimeUtilities.h"
28 #define USE_CUSTOM_PARSER 1
30 // TODO: if it works out, remove the old parser!
32 @implementation NGMimeRFC822DateHeaderFieldParser
34 static Class CalDateClass = Nil;
36 static NSTimeZone *gmt = nil;
37 static NSTimeZone *gmt01 = nil;
38 static NSTimeZone *gmt02 = nil;
39 static NSTimeZone *gmt03 = nil;
40 static NSTimeZone *gmt04 = nil;
41 static NSTimeZone *gmt05 = nil;
42 static NSTimeZone *gmt06 = nil;
43 static NSTimeZone *gmt07 = nil;
44 static NSTimeZone *gmt08 = nil;
45 static NSTimeZone *gmt09 = nil;
46 static NSTimeZone *gmt10 = nil;
47 static NSTimeZone *gmt11 = nil;
48 static NSTimeZone *gmt12 = nil;
49 static NSTimeZone *gmtM01 = nil;
50 static NSTimeZone *gmtM02 = nil;
51 static NSTimeZone *gmtM03 = nil;
52 static NSTimeZone *gmtM04 = nil;
53 static NSTimeZone *gmtM05 = nil;
54 static NSTimeZone *gmtM06 = nil;
55 static NSTimeZone *gmtM07 = nil;
56 static NSTimeZone *gmtM08 = nil;
57 static NSTimeZone *gmtM09 = nil;
58 static NSTimeZone *gmtM10 = nil;
59 static NSTimeZone *gmtM11 = nil;
60 static NSTimeZone *gmtM12 = nil;
61 static NSTimeZone *gmtM13 = nil;
62 static NSTimeZone *gmtM14 = nil;
63 static NSTimeZone *met = nil;
70 static BOOL didInit = NO;
74 CalDateClass = [NSCalendarDate class];
76 /* timezones which were actually used in a maillist mailbox */
77 gmt = [[NSTimeZone timeZoneWithName:@"GMT"] retain];
78 met = [[NSTimeZone timeZoneWithName:@"MET"] retain];
79 gmt01 = [[NSTimeZone timeZoneForSecondsFromGMT: 1 * (60 * 60)] retain];
80 gmt02 = [[NSTimeZone timeZoneForSecondsFromGMT: 2 * (60 * 60)] retain];
81 gmt03 = [[NSTimeZone timeZoneForSecondsFromGMT: 3 * (60 * 60)] retain];
82 gmt04 = [[NSTimeZone timeZoneForSecondsFromGMT: 4 * (60 * 60)] retain];
83 gmt05 = [[NSTimeZone timeZoneForSecondsFromGMT: 5 * (60 * 60)] retain];
84 gmt06 = [[NSTimeZone timeZoneForSecondsFromGMT: 6 * (60 * 60)] retain];
85 gmt07 = [[NSTimeZone timeZoneForSecondsFromGMT: 7 * (60 * 60)] retain];
86 gmt08 = [[NSTimeZone timeZoneForSecondsFromGMT: 8 * (60 * 60)] retain];
87 gmt09 = [[NSTimeZone timeZoneForSecondsFromGMT: 9 * (60 * 60)] retain];
88 gmt10 = [[NSTimeZone timeZoneForSecondsFromGMT: 10 * (60 * 60)] retain];
89 gmt11 = [[NSTimeZone timeZoneForSecondsFromGMT: 11 * (60 * 60)] retain];
90 gmt12 = [[NSTimeZone timeZoneForSecondsFromGMT: 12 * (60 * 60)] retain];
91 gmtM01 = [[NSTimeZone timeZoneForSecondsFromGMT: -1 * (60 * 60)] retain];
92 gmtM02 = [[NSTimeZone timeZoneForSecondsFromGMT: -2 * (60 * 60)] retain];
93 gmtM03 = [[NSTimeZone timeZoneForSecondsFromGMT: -3 * (60 * 60)] retain];
94 gmtM04 = [[NSTimeZone timeZoneForSecondsFromGMT: -4 * (60 * 60)] retain];
95 gmtM05 = [[NSTimeZone timeZoneForSecondsFromGMT: -5 * (60 * 60)] retain];
96 gmtM06 = [[NSTimeZone timeZoneForSecondsFromGMT: -6 * (60 * 60)] retain];
97 gmtM07 = [[NSTimeZone timeZoneForSecondsFromGMT: -7 * (60 * 60)] retain];
98 gmtM08 = [[NSTimeZone timeZoneForSecondsFromGMT: -8 * (60 * 60)] retain];
99 gmtM09 = [[NSTimeZone timeZoneForSecondsFromGMT: -9 * (60 * 60)] retain];
100 gmtM10 = [[NSTimeZone timeZoneForSecondsFromGMT:-10 * (60 * 60)] retain];
101 gmtM11 = [[NSTimeZone timeZoneForSecondsFromGMT:-11 * (60 * 60)] retain];
102 gmtM12 = [[NSTimeZone timeZoneForSecondsFromGMT:-12 * (60 * 60)] retain];
103 gmtM13 = [[NSTimeZone timeZoneForSecondsFromGMT:-13 * (60 * 60)] retain];
104 gmtM14 = [[NSTimeZone timeZoneForSecondsFromGMT:-14 * (60 * 60)] retain];
109 All the date formats are more or less the same. If they start with a char
110 those can be skipped to the first digit (since it is the weekday name that
111 is unnecessary for date construction).
113 TODO: use an own parser for that.
116 #if !USE_CUSTOM_PARSER
117 static NSString *numDateFormats[] = { /* dateformats starting with a number */
119 day short-month year hour:minute:second timezoneoffset
120 eg: 01 Oct 1999 18:20:12 +0200
122 @"%d %b %Y %H:%M:%S %z",
125 day short-month year hour:minute:second timezonename
126 eg: 01 Oct 1999 18:20:12 EST
128 @"%d %b %Y %H:%M:%S %Z",
131 day short-month year hour:minute:second (timezoneoffset)
132 eg: 30 Sep 1999 21:00:05 (+0200)
134 @"%d %b %Y %H:%M:%S (%z)",
137 day short-month year hour:minute:second (timezonename)
138 eg: 30 Sep 1999 21:00:05 (MEST)
140 @"%d %b %Y %H:%M:%S (%Z)",
142 /* eg: '16 Jun 2002 10:28 GMT' */
143 @"%d %b %Y %H:%M %Z",
145 /* eg: '16 Jun 2002 10:28 +0000' */
146 @"%d %b %Y %H:%M %z",
153 static int parseMonthOfYear(unsigned char *s, unsigned int len) {
155 This one is *extremely* forgiving, it only checks what is
156 necessary for the set below. This should work for both, English
159 English: Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
160 J F M A M J J A S O N D
163 NSLog(@"RFC822 Parser: cannot process month name: '%s'", s);
166 switch (toupper(*s)) {
167 case 'A': // April, August
168 if (toupper(s[1]) == 'P') return 4; // Apr
170 case 'D': return 12; // Dec
171 case 'F': return 2; // Feb
172 case 'J': // Jan, Jun, Jul
173 if (toupper(s[1]) == 'A') return 1; // Jan
174 if (toupper(s[2]) == 'N') return 6; // Jun
176 case 'M': // Mar, May
177 if (toupper(s[2]) == 'Y' || toupper(s[2]) == 'I') // May or Mai (German ;-)
180 case 'N': return 11; // Nov
181 case 'O': return 10; // Oct
182 case 'S': return 9; // Sep
184 NSLog(@"RFC822 Parser: cannot process month name: '%s'", s);
189 static NSTimeZone *parseTimeZone(unsigned char *s, unsigned int len) {
191 WARNING: failed to parse RFC822 timezone: '+0530' \
192 (value='Tue, 13 Jul 2004 21:39:28 +0530')
194 unsigned char *p = s;
201 if (*s == '+' || *s == '-') {
203 if (p[1] == '0' && p[2] == '0') // '+00' or '-00'
206 if (p[1] == '0' && p[2] == '1') // '+01'
208 if (p[1] == '0' && p[2] == '2') // '+02'
213 if (p[3] == '0' && p[4] == '0' && p[1] == '0') { // '?0x00'
214 if (p[2] == '0') // '+0000'
218 if (p[2] == '1') return gmt01; // '+0100'
219 if (p[2] == '2') return gmt02; // '+0200'
220 if (p[2] == '3') return gmt03; // '+0300'
221 if (p[2] == '4') return gmt04; // '+0400'
222 if (p[2] == '5') return gmt05; // '+0500'
223 if (p[2] == '6') return gmt06; // '+0600'
224 if (p[2] == '7') return gmt07; // '+0700'
225 if (p[2] == '8') return gmt08; // '+0800'
226 if (p[2] == '9') return gmt09; // '+0900'
228 else if (*s == '-') {
229 if (p[2] == '1') return gmtM01; // '-0100'
230 if (p[2] == '2') return gmtM02; // '-0200'
231 if (p[2] == '3') return gmtM03; // '-0300'
232 if (p[2] == '4') return gmtM04; // '-0400'
233 if (p[2] == '5') return gmtM05; // '-0500'
234 if (p[2] == '6') return gmtM06; // '-0600'
235 if (p[2] == '7') return gmtM07; // '-0700'
236 if (p[2] == '8') return gmtM08; // '-0800'
237 if (p[2] == '9') return gmtM09; // '-0900'
240 else if (p[3] == '0' && p[4] == '0' && p[1] == '1') { // "?1x00"
242 if (p[2] == '0') return gmt10; // '+1000'
243 if (p[2] == '1') return gmt11; // '+1100'
244 if (p[2] == '2') return gmt12; // '+1200'
246 else if (*s == '-') {
247 if (p[2] == '0') return gmtM10; // '-1000'
248 if (p[2] == '1') return gmtM11; // '-1100'
249 if (p[2] == '2') return gmtM12; // '-1200'
250 if (p[2] == '3') return gmtM13; // '-1300'
251 if (p[2] == '4') return gmtM14; // '-1400'
257 "MultiMail" submits timezones like this:
258 "Tue, 9 Mar 2004 9:43:00 -05-500",
259 don't know what the "-500" trailer is supposed to mean? Apparently
260 Thunderbird just uses the "-05", so do we.
263 if (isdigit(p[1]) && isdigit(p[2]) && (p[3] == '-'||p[3] == '+')) {
264 unsigned char tmp[8];
270 return parseTimeZone(tmp, 5);
274 else if (*s == '0') {
275 if (len == 2) { // '00'
276 if (p[1] == '0') return gmt;
277 if (p[1] == '1') return gmt01;
278 if (p[1] == '2') return gmt02;
281 if (p[2] == '0' && p[3] == '0') { // '0x00'
282 if (p[1] == '0') return gmt;
283 if (p[1] == '1') return gmt01;
284 if (p[1] == '2') return gmt02;
289 if (strcasecmp(s, "GMT") == 0) return gmt;
290 if (strcasecmp(s, "MET") == 0) return met;
294 ts = [[NSString alloc] initWithCString:s length:len];
299 buf[0] = 'G'; buf[1] = 'M'; buf[2] = 'T';
300 if (*s == '+' || *s == '-') {
301 strcpy(&(buf[3]), s);
305 strcpy(&(buf[4]), s);
307 ts = [[NSString alloc] initWithCString:buf];
310 NSLog(@"RFC822 TZ Parser: expensive: '%@'", ts);
312 tz = [NSTimeZone timeZoneWithAbbreviation:ts];
317 - (id)parseValue:(NSString *)_data ofHeaderField:(id)_field {
319 NSCalendarDate *date = nil;
320 unsigned char buf[256];
321 unsigned char *bytes = buf, *pe;
323 #if USE_CUSTOM_PARSER
324 NSTimeZone *tz = nil;
325 char dayOfMonth, monthOfYear, hour, minute, second;
329 NSString *dateString = nil;
332 if ((length = [_data cStringLength]) > 254) {
334 @"header field value to large for date parsing: '%@'(%i)",
339 [_data getCString:buf maxLength:length];
342 /* remove leading chars (skip to first digit, the day of the month) */
343 while (length > 0 && (!isdigit(*bytes))) {
349 NSLog(@"WARNING(%s): empty value for header field %@ ..",
350 __PRETTY_FUNCTION__, _field);
351 return [CalDateClass date];
354 #if USE_CUSTOM_PARSER // TODO: should be a category on NSCalendarDate
355 // TODO: optimize much further!
356 // first part: '16 Jun 2002'
357 // snd part: '12:28[:11]'
358 // trd part: 'GMT' '+0000' '(MET)' '(+0200)'
360 /* defaults for early aborts */
365 /* parse day of month */
367 for (pe = bytes; isdigit(*pe); pe++)
369 if (*pe == 0) goto failed;
371 dayOfMonth = atoi(bytes);
374 /* parse month-abbrev (should be English, could be other langs) */
376 while (!isalpha(*bytes)) { /* go to first char */
377 if (*bytes == '\0') goto failed;
380 for (pe = bytes; isalpha(*pe); pe++) /* find end of string */
382 if (*pe == 0) goto failed;
384 monthOfYear = parseMonthOfYear(bytes, (pe - bytes));
389 while (!isdigit(*bytes)) { /* go to first digit */
390 if (*bytes == '\0') goto failed;
393 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
395 if (*pe == 0) goto failed;
399 if (year >= 70 && year < 135) // Y2K
401 else if (year >= 0 && year < 70) // Y2K
406 while (!isdigit(*bytes)) { /* go to first digit */
407 if (*bytes == '\0') goto failed;
410 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
414 hour = bytes != pe ? atoi(bytes) : 0;
415 if (flag) goto finished; // this is: '12\0'
420 while (!isdigit(*bytes)) { /* go to first digit */
421 if (*bytes == '\0') goto finished; // this is: '12 \0'
424 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
428 minute = bytes != pe ? atoi(bytes) : 0;
429 if (flag) goto finished; // this is: '12:23\0'
432 /* parse second - if available '13:13:23' vs '12:23\0' or '12:12 (MET)' */
434 while (isspace(*bytes)) /* skip spaces */
436 if (*bytes == 0) goto finished; // this is: '12:23 \0'
437 if (isdigit(*bytes) || *bytes == ':') {
439 while (!isdigit(*bytes)) { /* go to first digit, skip the ':' */
440 if (*bytes == '\0') goto finished;
444 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
448 second = bytes != pe ? atoi(bytes) : 0;
449 if (flag) goto finished; // this is: '12:23:12\0'
453 /* parse timezone: 'GMT' '+0000' '(MET)' '(+0200)' */
454 // TODO: do we need to parse: "-0700 (PDT)" as "PDT"?
456 while (isspace(*bytes) || *bytes == '(') /* skip spaces */
458 if (*bytes == 0) goto finished; // this is: '12:23:12 \0' or '12:12 ('
460 for (pe = bytes; isalnum(*pe) || *pe == '-' || *pe == '+'; pe++)
463 if ((tz = parseTimeZone(bytes, (pe - bytes))) == nil) {
465 @"WARNING: failed to parse RFC822 timezone: '%s' (value='%@')",
470 /* construct and return */
472 date = [CalDateClass dateWithYear:year month:monthOfYear day:dayOfMonth
473 hour:hour minute:minute second:second
475 if (date == nil) goto failed;
478 printf("parsed '%s' to date: %s\n",
479 [_data cString], [[date description] cString]);
480 //[self logWithFormat:@"parsed '%@' to date: %@", _data, date];
485 [self logWithFormat:@"WARNING: failed to parse RFC822 date field: '%@'",
492 [[NSString alloc] initWithCString:bytes length:length];
494 /* check various date formats */
498 for (i = 0, date = nil; (date == nil) && (numDateFormats[i] != nil); i++) {
499 date = [CalDateClass dateWithString:dateString
500 calendarFormat:numDateFormats[i]];
504 [dateString release]; dateString = nil;
506 return [date y2kDate];
510 @end /* NGMimeRFC822DateHeaderFieldParser */