2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE 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 SOPE 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 SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "NGMimeHeaderFieldParser.h"
23 #include "NGMimeHeaderFields.h"
24 #include "NGMimeUtilities.h"
28 @implementation NGMimeRFC822DateHeaderFieldParser
30 static Class CalDateClass = Nil;
31 static NSTimeZone *gmt = nil;
32 static NSTimeZone *gmt01 = nil;
33 static NSTimeZone *gmt02 = nil;
34 static NSTimeZone *gmt03 = nil;
35 static NSTimeZone *gmt04 = nil;
36 static NSTimeZone *gmt05 = nil;
37 static NSTimeZone *gmt06 = nil;
38 static NSTimeZone *gmt07 = nil;
39 static NSTimeZone *gmt08 = nil;
40 static NSTimeZone *gmt09 = nil;
41 static NSTimeZone *gmt10 = nil;
42 static NSTimeZone *gmt11 = nil;
43 static NSTimeZone *gmt12 = nil;
44 static NSTimeZone *gmt0530 = nil;
45 static NSTimeZone *gmtM01 = nil;
46 static NSTimeZone *gmtM02 = nil;
47 static NSTimeZone *gmtM03 = nil;
48 static NSTimeZone *gmtM04 = nil;
49 static NSTimeZone *gmtM05 = nil;
50 static NSTimeZone *gmtM06 = nil;
51 static NSTimeZone *gmtM07 = nil;
52 static NSTimeZone *gmtM08 = nil;
53 static NSTimeZone *gmtM09 = nil;
54 static NSTimeZone *gmtM10 = nil;
55 static NSTimeZone *gmtM11 = nil;
56 static NSTimeZone *gmtM12 = nil;
57 static NSTimeZone *gmtM13 = nil;
58 static NSTimeZone *gmtM14 = nil;
59 static NSTimeZone *met = nil;
65 static BOOL didInit = NO;
70 CalDateClass = [NSCalendarDate class];
72 /* timezones which were actually used in a maillist mailbox */
73 TzClass = [NSTimeZone class];
74 gmt = [[TzClass timeZoneWithName:@"GMT"] retain];
75 met = [[TzClass timeZoneWithName:@"MET"] retain];
76 gmt01 = [[TzClass timeZoneForSecondsFromGMT: 1 * (60 * 60)] retain];
77 gmt02 = [[TzClass timeZoneForSecondsFromGMT: 2 * (60 * 60)] retain];
78 gmt03 = [[TzClass timeZoneForSecondsFromGMT: 3 * (60 * 60)] retain];
79 gmt04 = [[TzClass timeZoneForSecondsFromGMT: 4 * (60 * 60)] retain];
80 gmt05 = [[TzClass timeZoneForSecondsFromGMT: 5 * (60 * 60)] retain];
81 gmt06 = [[TzClass timeZoneForSecondsFromGMT: 6 * (60 * 60)] retain];
82 gmt07 = [[TzClass timeZoneForSecondsFromGMT: 7 * (60 * 60)] retain];
83 gmt08 = [[TzClass timeZoneForSecondsFromGMT: 8 * (60 * 60)] retain];
84 gmt09 = [[TzClass timeZoneForSecondsFromGMT: 9 * (60 * 60)] retain];
85 gmt10 = [[TzClass timeZoneForSecondsFromGMT: 10 * (60 * 60)] retain];
86 gmt11 = [[TzClass timeZoneForSecondsFromGMT: 11 * (60 * 60)] retain];
87 gmt12 = [[TzClass timeZoneForSecondsFromGMT: 12 * (60 * 60)] retain];
88 gmtM01 = [[TzClass timeZoneForSecondsFromGMT: -1 * (60 * 60)] retain];
89 gmtM02 = [[TzClass timeZoneForSecondsFromGMT: -2 * (60 * 60)] retain];
90 gmtM03 = [[TzClass timeZoneForSecondsFromGMT: -3 * (60 * 60)] retain];
91 gmtM04 = [[TzClass timeZoneForSecondsFromGMT: -4 * (60 * 60)] retain];
92 gmtM05 = [[TzClass timeZoneForSecondsFromGMT: -5 * (60 * 60)] retain];
93 gmtM06 = [[TzClass timeZoneForSecondsFromGMT: -6 * (60 * 60)] retain];
94 gmtM07 = [[TzClass timeZoneForSecondsFromGMT: -7 * (60 * 60)] retain];
95 gmtM08 = [[TzClass timeZoneForSecondsFromGMT: -8 * (60 * 60)] retain];
96 gmtM09 = [[TzClass timeZoneForSecondsFromGMT: -9 * (60 * 60)] retain];
97 gmtM10 = [[TzClass timeZoneForSecondsFromGMT:-10 * (60 * 60)] retain];
98 gmtM11 = [[TzClass timeZoneForSecondsFromGMT:-11 * (60 * 60)] retain];
99 gmtM12 = [[TzClass timeZoneForSecondsFromGMT:-12 * (60 * 60)] retain];
100 gmtM13 = [[TzClass timeZoneForSecondsFromGMT:-13 * (60 * 60)] retain];
101 gmtM14 = [[TzClass timeZoneForSecondsFromGMT:-14 * (60 * 60)] retain];
103 gmt0530 = [[TzClass timeZoneForSecondsFromGMT:5 * (60*60) + (30*60)] retain];
107 All the date formats are more or less the same. If they start with a char
108 those can be skipped to the first digit (since it is the weekday name that
109 is unnecessary for date construction).
111 TODO: use an own parser for that.
114 static int parseMonthOfYear(unsigned char *s, unsigned int len) {
116 This one is *extremely* forgiving, it only checks what is
117 necessary for the set below. This should work for both, English
120 English: Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
121 J F M A M J J A S O N D
124 NSLog(@"RFC822 Parser: cannot process month name: '%s'", s);
127 switch (toupper(*s)) {
128 case 'A': // April, August
129 if (toupper(s[1]) == 'P') return 4; // Apr
131 case 'D': return 12; // Dec
132 case 'F': return 2; // Feb
133 case 'J': // Jan, Jun, Jul
134 if (toupper(s[1]) == 'A') return 1; // Jan
135 if (toupper(s[2]) == 'N') return 6; // Jun
137 case 'M': // Mar, May
138 if (toupper(s[2]) == 'Y' || toupper(s[2]) == 'I') // May or Mai (German ;-)
141 case 'N': return 11; // Nov
142 case 'O': return 10; // Oct
143 case 'S': return 9; // Sep
145 NSLog(@"RFC822 Parser: cannot process month name: '%s'", s);
150 static NSTimeZone *parseTimeZone(unsigned char *s, unsigned int len) {
152 WARNING: failed to parse RFC822 timezone: '+0530' \
153 (value='Tue, 13 Jul 2004 21:39:28 +0530')
154 TODO: this is because libFoundation doesn't accept 'GMT+0530' as input.
163 if (*s == '+' || *s == '-') {
165 if (p[1] == '0' && p[2] == '0') // '+00' or '-00'
168 if (p[1] == '0' && p[2] == '1') // '+01'
170 if (p[1] == '0' && p[2] == '2') // '+02'
175 if (p[3] == '0' && p[4] == '0' && p[1] == '0') { // '?0x00'
176 if (p[2] == '0') // '+0000'
180 if (p[2] == '1') return gmt01; // '+0100'
181 if (p[2] == '2') return gmt02; // '+0200'
182 if (p[2] == '3') return gmt03; // '+0300'
183 if (p[2] == '4') return gmt04; // '+0400'
184 if (p[2] == '5') return gmt05; // '+0500'
185 if (p[2] == '6') return gmt06; // '+0600'
186 if (p[2] == '7') return gmt07; // '+0700'
187 if (p[2] == '8') return gmt08; // '+0800'
188 if (p[2] == '9') return gmt09; // '+0900'
190 else if (*s == '-') {
191 if (p[2] == '1') return gmtM01; // '-0100'
192 if (p[2] == '2') return gmtM02; // '-0200'
193 if (p[2] == '3') return gmtM03; // '-0300'
194 if (p[2] == '4') return gmtM04; // '-0400'
195 if (p[2] == '5') return gmtM05; // '-0500'
196 if (p[2] == '6') return gmtM06; // '-0600'
197 if (p[2] == '7') return gmtM07; // '-0700'
198 if (p[2] == '8') return gmtM08; // '-0800'
199 if (p[2] == '9') return gmtM09; // '-0900'
202 else if (p[3] == '0' && p[4] == '0' && p[1] == '1') { // "?1x00"
204 if (p[2] == '0') return gmt10; // '+1000'
205 if (p[2] == '1') return gmt11; // '+1100'
206 if (p[2] == '2') return gmt12; // '+1200'
208 else if (*s == '-') {
209 if (p[2] == '0') return gmtM10; // '-1000'
210 if (p[2] == '1') return gmtM11; // '-1100'
211 if (p[2] == '2') return gmtM12; // '-1200'
212 if (p[2] == '3') return gmtM13; // '-1300'
213 if (p[2] == '4') return gmtM14; // '-1400'
217 /* special case for GMT+0530 */
218 if (strncmp((char *)s, "+0530", 5) == 0)
223 "MultiMail" submits timezones like this:
224 "Tue, 9 Mar 2004 9:43:00 -05-500",
225 don't know what the "-500" trailer is supposed to mean? Apparently
226 Thunderbird just uses the "-05", so do we.
229 if (isdigit(p[1]) && isdigit(p[2]) && (p[3] == '-'||p[3] == '+')) {
230 unsigned char tmp[8];
232 strncpy((char *)tmp, p, 3);
236 return parseTimeZone(tmp, 5);
240 else if (*s == '0') {
241 if (len == 2) { // '00'
242 if (p[1] == '0') return gmt;
243 if (p[1] == '1') return gmt01;
244 if (p[1] == '2') return gmt02;
247 if (p[2] == '0' && p[3] == '0') { // '0x00'
248 if (p[1] == '0') return gmt;
249 if (p[1] == '1') return gmt01;
250 if (p[1] == '2') return gmt02;
255 if (strcasecmp((char *)s, "GMT") == 0) return gmt;
256 if (strcasecmp((char *)s, "UTC") == 0) return gmt;
257 if (strcasecmp((char *)s, "MET") == 0) return met;
258 if (strcasecmp((char *)s, "CET") == 0) return met;
262 ts = [[NSString alloc] initWithCString:(char *)s length:len];
267 buf[0] = 'G'; buf[1] = 'M'; buf[2] = 'T';
268 if (*s == '+' || *s == '-') {
269 strcpy(&(buf[3]), (char *)s);
273 strcpy(&(buf[4]), (char *)s);
275 ts = [[NSString alloc] initWithCString:buf];
278 NSLog(@"%s: RFC822 TZ Parser: expensive: '%@'", __PRETTY_FUNCTION__, ts);
280 tz = [NSTimeZone timeZoneWithAbbreviation:ts];
285 - (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
287 NSCalendarDate *date = nil;
288 unsigned char buf[256];
289 unsigned char *bytes = buf, *pe;
291 NSTimeZone *tz = nil;
292 char dayOfMonth, monthOfYear, hour, minute, second;
296 if ((length = [_data cStringLength]) > 254) {
298 @"header field value to large for date parsing: '%@'(%i)",
303 [_data getCString:(char *)buf maxLength:length];
306 /* remove leading chars (skip to first digit, the day of the month) */
307 while (length > 0 && (!isdigit(*bytes))) {
313 NSLog(@"WARNING(%s): empty value for header field %@ ..",
314 __PRETTY_FUNCTION__, _field);
315 return [CalDateClass date];
318 // TODO: should be a category on NSCalendarDate
319 // TODO: optimize much further!
320 // first part: '16 Jun 2002'
321 // snd part: '12:28[:11]'
322 // trd part: 'GMT' '+0000' '(MET)' '(+0200)'
324 /* defaults for early aborts */
329 /* parse day of month */
331 for (pe = bytes; isdigit(*pe); pe++)
333 if (*pe == 0) goto failed;
335 dayOfMonth = atoi((char *)bytes);
338 /* parse month-abbrev (should be English, could be other langs) */
340 while (!isalpha(*bytes)) { /* go to first char */
341 if (*bytes == '\0') goto failed;
344 for (pe = bytes; isalpha(*pe); pe++) /* find end of string */
346 if (*pe == 0) goto failed;
348 if ((monthOfYear = parseMonthOfYear(bytes, (pe - bytes))) == 0) {
349 [self logWithFormat:@"WARNING(%s): cannot parse month in date: %@",
350 __PRETTY_FUNCTION__, _data];
356 while (!isdigit(*bytes)) { /* go to first digit */
357 if (*bytes == '\0') goto failed;
360 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
362 if (*pe == 0) goto failed;
364 year = atoi((char *)bytes);
366 if (year >= 70 && year < 135) // Y2K
368 else if (year >= 0 && year < 70) // Y2K
371 #if LIB_FOUNDATION_LIBRARY
373 NSLog(@"ERROR(%s): got invalid year in date header %d: '%s'",
374 __PRETTY_FUNCTION__, year, buf);
375 year = 2000; /* no choice is good ..., maybe return nil? */
381 while (!isdigit(*bytes)) { /* go to first digit */
382 if (*bytes == '\0') goto failed;
385 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
389 hour = bytes != pe ? atoi((char *)bytes) : 0;
390 if (flag) goto finished; // this is: '12\0'
395 while (!isdigit(*bytes)) { /* go to first digit */
396 if (*bytes == '\0') goto finished; // this is: '12 \0'
399 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
403 minute = bytes != pe ? atoi((char *)bytes) : 0;
404 if (flag) goto finished; // this is: '12:23\0'
407 /* parse second - if available '13:13:23' vs '12:23\0' or '12:12 (MET)' */
409 while (isspace(*bytes)) /* skip spaces */
411 if (*bytes == 0) goto finished; // this is: '12:23 \0'
412 if (isdigit(*bytes) || *bytes == ':') {
414 while (!isdigit(*bytes)) { /* go to first digit, skip the ':' */
415 if (*bytes == '\0') goto finished;
419 for (pe = bytes; isdigit(*pe); pe++) /* find end of number */
423 second = bytes != pe ? atoi((char *)bytes) : 0;
424 if (flag) goto finished; // this is: '12:23:12\0'
428 /* parse timezone: 'GMT' '+0000' '(MET)' '(+0200)' */
429 // TODO: do we need to parse: "-0700 (PDT)" as "PDT"?
431 while (isspace(*bytes) || *bytes == '(') /* skip spaces */
433 if (*bytes == 0) goto finished; // this is: '12:23:12 \0' or '12:12 ('
435 for (pe = bytes; isalnum(*pe) || *pe == '-' || *pe == '+'; pe++)
438 if ((tz = parseTimeZone(bytes, (pe - bytes))) == nil) {
440 @"WARNING: failed to parse RFC822 timezone: '%s' (value='%@')",
445 /* construct and return */
447 date = [CalDateClass dateWithYear:year month:monthOfYear day:dayOfMonth
448 hour:hour minute:minute second:second
450 if (date == nil) goto failed;
453 printf("parsed '%s' to date: %s\n",
454 [_data cString], [[date description] cString]);
455 //[self logWithFormat:@"parsed '%@' to date: %@", _data, date];
460 // TODO: 'Sun, May 18 2003 14:20:55 -0700' - why does this fail?
461 [self logWithFormat:@"WARNING: failed to parse RFC822 date field: '%@'",
466 @end /* NGMimeRFC822DateHeaderFieldParser */