/* * Copyright 1988 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. * * Maintenance by Ken Lalonde, 1999. */ /* From: Ken Lalonde To: mea@nic.funet.fi Subject: Bug fix for zmailer-2.99.50s11/router/dateparse.c Date: Fri, 5 Feb 1999 16:39:45 -0500 Hi Matti, The router/dateparse.c code doesn't handle year 2000 dates correctly. I don't think this actually matters very much, but just in case, here's a version that does. All the best, Ken Lalonde Network Engineering UUNET, an MCI WorldCom Company Phone: +1 416-216-5133 Fax: +1 416-368-4080 */ /* Yes, however USAGE of this code is limited; while it does get called by the router, it's result is effectively ignored. People do produce copious amounts of "Date:" headers that are so ingenuinely wrong in syntax, that spending time in parsing them is wasted time.. :-( Ignoring that wastage, current Mailer passes on arriving "Date:" header, and generates one only when it doesn't exist in the arriving headers. However somebody may decide to reuse this code somewhere else; at some userspace program, for example, which can live with that ugly fact of life.. /Matti Aarnio */ /* * RFC822 date routines. * * The functions in this file will parse and generate RFC822 format dates. * In fact, the date parser accepts date strings in quite a variety of * formats, in accordance with the lack of adhered-to standards in this * area. The actual parsing code was originally inspired by the complexity * of a yacc-based date parser by Rich Wales. The language spanned by that * parser was very simple: * * (MONTH_NAME [.] | num)[/- ](day-number)[/- ](yy[yy]) (DAY_NAME[.]) time * * time: [ hh[[:.]mm[[:.]ss]] ] [ AM/PM ] [ zone ] * * zone: [-] STD_ZONE [ DST_SUFFIX ] * | [-] DST_ZONE * | {+|-} [+|-] hh[[:.]mm] * * The token-based parser here is intended to implement a more flexible * version of this grammar. Individual tokens are identified as to their * possible meaning, and are then pinned down by a relaxation scheme. * Seems to work pretty well, but I didn't lose the complexity, alas. */ #include "hostenv.h" #include "mailer.h" #include extern int cistrncmp(); /* The following table and binary search code originally by Rich Wales */ typedef enum { nilDate, Month, AmPm, StdZone, DstZone, Day } DateType; static DateType was; /* Binary-search symbol table. */ static struct wordtable { char *text; DateType type; int value; } wordtable[] = { /* text token lexval */ { "A", StdZone, 60 }, /* UTC+1h */ { "ACSST", DstZone, 630 }, /* Cent. Australia */ { "ACST", StdZone, 570 }, /* Cent. Australia */ { "ADT", DstZone, -180 }, /* Atlantic Daylight Time */ { "AESST", DstZone, 660 }, /* E. Australia */ { "AEST", StdZone, 600 }, /* Australia Eastern Std Time */ { "AHST", StdZone, 600 }, /* Alaska-Hawaii Std Time */ { "AM", AmPm, 0 }, { "APR", Month, 4 }, { "APRIL", Month, 4 }, { "AST", StdZone, -240 }, /* Atlantic Std Time (Canada) */ { "AT", nilDate, 0 }, /* "at" (throwaway) */ { "AUG", Month, 8 }, { "AUGUST", Month, 8 }, { "AWSST", DstZone, 540 }, /* W. Australia */ { "AWST", StdZone, 480 }, /* W. Australia */ { "B", StdZone, 120 }, /* UTC+2h */ { "BST", StdZone, 60 }, /* British Summer Time */ { "BT", StdZone, 180 }, /* Baghdad Time */ { "C", StdZone, 180 }, /* UTC+3h */ { "CADT", DstZone, 630 }, /* Central Australian DST */ { "CAST", StdZone, 570 }, /* Central Australian ST */ { "CAT", StdZone, -600 }, /* Central Alaska Time */ { "CCT", StdZone, 480 }, /* China Coast */ { "CDT", DstZone, -300 }, /* Central Daylight Time */ { "CET", StdZone, 60 }, /* Central European Time */ { "CETDST", DstZone, 120 }, /* Central European Dayl.Time */ { "CST", StdZone, -360 }, /* Centrail Standard Time */ { "D", StdZone, 240 }, /* UTC+4h */ { "DEC", Month, 12 }, { "DECEMBER", Month, 12 }, { "DNT", StdZone, 60 }, /* Dansk Normal Tid */ { "DST", nilDate, 0 }, { "E", StdZone, 300 }, /* UTC+5h */ { "EAST", StdZone, -600 }, /* East Australian Std Time */ { "EDT", DstZone, -240 }, /* Eastern Daylight Time */ { "EET", StdZone, 120 }, /* East. Europe, USSR Zone 1 */ { "EETDST", DstZone, 180 }, /* Eastern Europe */ { "EST", StdZone, -300 }, /* Eastern Standard Time */ { "F", StdZone, 360 }, /* UTC+6h */ { "FEB", Month, 2 }, { "FEBRUARY", Month, 2 }, { "FRI", Day, 5 }, { "FRIDAY", Day, 5 }, { "FST", StdZone, 60 }, /* French Summer Time */ { "FWT", DstZone, 120 }, /* French Winter Time */ { "G", StdZone, 420 }, /* UTC+7h */ { "GMT", StdZone, 0 }, /* Greenwish Mean Time */ { "GST", StdZone, 600 }, /* Guam Std Time, USSR Zone 9 */ { "H", StdZone, 480 }, /* UTC+8h */ { "HDT", DstZone, -540 }, /* Hawaii/Alaska */ { "HMT", DstZone, 180 }, /* Hellas ? ? */ { "HST", StdZone, -600 }, /* Hawaii Std Time */ { "I", StdZone, 540 }, /* UTC+9h */ { "IDLE", StdZone, 720 }, /* Intl. Date Line, East */ { "IDLW", StdZone, -720 }, /* Intl. Date Line, West */ { "IST", StdZone, 120 }, /* Israel */ { "IT", StdZone, 220 }, /* Iran Time */ { "JAN", Month, 1 }, { "JANUARY", Month, 1 }, { "JST", StdZone, 540 }, /* Japan Std Time,USSR Zone 8 */ { "JT", StdZone, 450 }, /* Java Time */ { "JUL", Month, 7 }, { "JULY", Month, 7 }, { "JUN", Month, 6 }, { "JUNE", Month, 6 }, { "K", StdZone, 600 }, /* UTC+10h */ { "KST", StdZone, 540 }, /* Korea Standard Time */ { "L", StdZone, 660 }, /* UTC+11h */ { "LIGT", StdZone, 600 }, /* From Melbourne, Australia */ { "M", StdZone, 720 }, /* UTC+12h */ { "MAR", Month, 3 }, { "MARCH", Month, 3 }, { "MAY", Month, 5 }, { "MDT", DstZone, -360 }, /* Mountain Daylight Time */ { "MEST", DstZone, 120 }, /* Middle Europe Summer Time */ { "MET", StdZone, 60 }, /* Middle Europe Time */ { "METDST", DstZone, 120 }, /* Middle Europe Daylight Time*/ { "MEWT", StdZone, 60 }, /* Middle Europe Winter Time */ { "MEZ", StdZone, 60 }, /* Mittel Europaeische Zeigt */ { "MON", Day, 1 }, { "MONDAY", Day, 1 }, { "MST", StdZone, -420 }, /* Mountain Standard Time */ { "MT", StdZone, 510 }, /* Moluccas Time */ { "N", StdZone, -60 }, /* UTC-1h */ { "NDT", DstZone, -150 }, /* Nfld. Daylight Time */ { "NFT", StdZone, -210 }, /* Newfoundland Standard Time */ { "NOR", StdZone, 60 }, /* Norway Standard Time */ { "NOV", Month, 11 }, { "NOVEMBER", Month, 11 }, { "NST", StdZone, -210 }, /* Nfld. Standard Time */ { "NT", StdZone, -660 }, /* Nome Time */ { "NZDT", DstZone, 780 }, /* New Zealand Daylight Time */ { "NZST", StdZone, 720 }, /* New Zealand Standard Time */ { "NZT", StdZone, 720 }, /* New Zealand Time */ { "O", StdZone, -120 }, /* UTC-2h */ { "OCT", Month, 10 }, { "OCTOBER", Month, 10 }, { "ON", nilDate, 0 }, /* "on" (throwaway) */ { "P", StdZone, -180 }, /* UTC-3h */ { "PDT", DstZone, -420 }, /* Pacific Daylight Time */ { "PM", AmPm, 720 }, { "PST", StdZone, -480 }, /* Pacific Standard Time */ { "Q", StdZone, -240 }, /* UTC-4h */ { "R", StdZone, -300 }, /* UTC-5h */ { "S", StdZone, -360 }, /* UTC-6h */ { "SADT", DstZone, 630 }, /* S. Australian Dayl. Time */ { "SAST", StdZone, 570 }, /* South Australian Std Time */ { "SAT", Day, 6 }, { "SATURDAY", Day, 6 }, { "SEP", Month, 9 }, { "SEPT", Month, 9 }, { "SEPTEMBER", Month, 9 }, { "SET", StdZone, -60 }, /* Seychelles Time ?? */ { "SST", DstZone, 120 }, /* Swedish Summer Time */ { "SUN", Day, 0 }, { "SUNDAY", Day, 0 }, { "SWT", StdZone, 60 }, /* Swedish Winter Time */ { "T", StdZone, -420 }, /* UTC-7h */ { "THU", Day, 4 }, { "THUR", Day, 4 }, { "THURS", Day, 4 }, { "THURSDAY", Day, 4 }, { "TUE", Day, 2 }, { "TUES", Day, 2 }, { "TUESDAY", Day, 2 }, { "U", StdZone, -480 }, /* UTC-8h */ { "UT", StdZone, 0 }, { "UTC", StdZone, 0 }, { "V", StdZone, -540 }, /* UTC-9h */ { "W", StdZone, -600 }, /* UTC-10h */ { "WADT", DstZone, 480 }, /* West Australian DST */ { "WAST", StdZone, 420 }, /* West Australian Std Time */ { "WAT", StdZone, -60 }, /* West Africa Time */ { "WDT", DstZone, 540 }, /* West Australian DST */ { "WED", Day, 3 }, { "WEDNESDAY", Day, 3 }, { "WEDS", Day, 3 }, { "WET", StdZone, 0 }, /* Western Europe */ { "WETDST", DstZone, 60 }, /* Western Europe */ { "WST", StdZone, 480 }, /* West Australian Std Time */ { "X", StdZone, -660 }, /* UTC-11h */ { "Y", StdZone, -720 }, /* UTC-12h */ { "YDT", DstZone, -480 }, /* Yukon Daylight Time */ { "YST", StdZone, -540 }, /* Yukon Standard Time */ { "Z", StdZone, 0 }, /* UTC */ { "ZP4", StdZone, -240 }, /* GMT +4 hours. */ { "ZP5", StdZone, -300 }, /* GMT +5 hours. */ { "ZP6", StdZone, -360 } /* GMT +6 hours. */ }; #if 0 /* These time zones are orphans, i.e. the name is also used by a more likely-to-appear time zone */ "AT", StdZone, -120, /* Azores Time */ "BST", StdZone, -180, /* Brazil Std Time */ "BT", StdZone, -660, /* Bering Time */ "EDT", StdZone, 660, /* Australian Eastern DaylTime*/ "EST", StdZone, 600, /* Australian Eastern Std Time*/ "IST", StdZone, 330, /* Indian Standard Time */ "NST", StdZone, 510, /* North Sumatra Time */ "SST", StdZone, 420, /* South Sumatra, USSR Zone 6 */ "SST", StdZone, 480, /* Singapore Std Time */ "WET", StdZone, 60, /* Western European Time */ #endif /* * Lilian day calculation, from * http://www.software.ibm.com/year2000/tips15.html */ #define INT(x) (x) /* just to keep the formulae the same */ static int lilian __((int yyyy, int mm, int dd)); static int lilian(yyyy, mm, dd) int yyyy, mm, dd; { int ly, nnn, lil; if (mm < 1) mm = 1; /* For BAD input this may be zero.. */ if (dd < 1) dd = 1; /* For BAD input this may be zero.. */ /* Determine day in year (nnn) */ ly = yyyy%4 == 0 ? 1 : 0; if (yyyy%100 == 0) ly = 0; if (yyyy%400 == 0) ly = 1; nnn = INT(3 / (mm + 1)) * (31 * (mm -1) + dd) + INT((mm + 9) / 12) * (INT(((305 * (mm - 1) - 15) + INT((mm + 3) / 12) * 5 * INT(18 / mm)) / 10) + dd + ly); /* Determine Lilian day from YYYY NNN */ lil = INT(((yyyy - 1201) * 36525) / 100) - 139444 + nnn - INT((yyyy - 1201) / 100)+ INT((yyyy - 1201) / 400); return lil; } /* Multiple-value return of token type and value */ static int dateToken(s, len) register char *s; register int len; { register int low, mid, high; register int comparison; low = 0; high = sizeof wordtable / sizeof wordtable[0] - 1; while (high >= low) { mid = (low + high) / 2; comparison = *wordtable[mid].text - (islower(*s) ? toupper(*s) : *s); if (comparison == 0) { comparison = cistrncmp(wordtable[mid].text, s, len); if (comparison == 0) { if (wordtable[mid].text[len] != '\0') { comparison = 1; } else { was = wordtable[mid].type; return wordtable[mid].value; } } } if (comparison > 0) high = mid - 1; else low = mid + 1; } was = nilDate; return 0; } /* Bits in a flag describing possible semantics of each token */ #define HHMMSS 01 /* hours-minutes-seconds, e.g. 213245 */ #define HHMM 02 /* hours-minutes, e.g. 2132 */ #define HH 04 /* hours, 0 <= HH < 24 */ #define MM 010 /* minutes, 0 <= MM < 60 */ #define SS 020 /* seconds, 0 <= SS < 60 */ #define MIY 040 /* month-in-year, e.g. Jan */ #define DD 0100 /* day-in-month, e.g. 5 */ #define YY 0200 /* year-in-century, e.g. 88 */ #define YYYY 0400 /* anno domini */ #define MAXFLAG YYYY /* used to tell if one & only one bit is set */ /* * Parse a tokenlist scanned from a date-string. */ long dateParse(localtmptr, t) struct tm *localtmptr; token822 *t; { register int val = 0, i, could_be; int alreadyhave = 0; int century, year, month, dayinmonth, days; int *prev_could_be, j, index, zone, aval, zoneindex, expect_zone; #define TYPESMAX 50 int values[TYPESMAX], types[TYPESMAX]; /* TODO: fix limits */ long sec; int have_year = 0; static int this_century = -1; if (this_century < 0) this_century = (localtmptr->tm_year + 1900) / 100; index = 0; zone = 0; /* minutes offset from UTC */ zoneindex = 0; expect_zone = 0; types[0] = 0; prev_could_be = &types[0]; for (; t != NULL; t = t->t_next) { /* if (t->t_type != Atom) continue; */ if (t->t_type == Comment || t->t_type == Space) continue; /* X: need to take care of embedded slashes */ could_be = 0; if (*(t->t_pname) >= '0' && *(t->t_pname) <= '9') { if (t->t_len == 0) val = atoi((char *)t->t_pname); else { val = 0; for (i = 0; i < t->t_len; ++i) val = 10*val + (*(t->t_pname+i) - '0'); } if (expect_zone != 0) { aval = abs(val); if (aval < 24) { zone += val * 60 * expect_zone; continue; } else if (aval >= 100 && aval%100 < 60) { zone += (val%100 + (val/100)*60) * expect_zone; continue; } } if (val < 13) could_be |= MIY; if (val < 24) could_be |= HH; if (val < 32) could_be |= DD; if (val < 60) { if ((*prev_could_be) & HH) could_be |= MM; if ((*prev_could_be) & MM) could_be |= SS; if (this_century >= 20) could_be |= YY; } if (val > 59) { if (val < 100) could_be |= YY; else { if (val%100 < 60) { if (val < 2400) could_be |= HHMM; else if (val > 10000 && val < 240000 && (val%10000) < 2400) could_be |= HHMMSS; } if (val/100 == this_century || (val/100 == this_century+1 && val%100 <= 20)) could_be |= YYYY; } } } else if (*(t->t_pname) == '+' || *(t->t_pname) == '-') { /* * Either it is GMT+5 or similar, or we are looking * at 7-APR-88 (or APR-7-88 or 88-APR-7 etc) type thing, * or it is -0400 timezone offset like MH spits out. */ if ((zoneindex > 0 && zoneindex == index - 1) || (zoneindex == 0 && t->t_len > 1)) { if ((*prev_could_be) & (SS|MM|HH|HHMM|HHMMSS)) *prev_could_be &= SS|MM|HH|HHMM|HHMMSS; j = 1; if (*(t->t_pname) == '-') j = -j; if (t->t_len == 1) { if (t->t_next != NULL) t = t->t_next; else break; } if (t->t_len == 0) val = atoi((char *)t->t_pname); else { val = 0; i = *(t->t_pname) == '-' || *(t->t_pname) == '+'; for (; i < t->t_len; ++i) { val = 10*val + (*(t->t_pname+i) - '0'); } } val *= j; aval = abs(val); if (aval < 24) zone += val * 60; else /* be careful about minutes */ zone += val%100 + (val/100)*60; } else if (*(t->t_pname) == '+') expect_zone = 1; else expect_zone = -1; continue; /* don't null expect_zone at bottom */ } else if (*(t->t_pname) == '.') { /* ignore this one and the next one */ if (t->t_next != NULL) t = t->t_next; } else if (*(t->t_pname) == ':') { /* colon is typically always after HH or MM */ if ((*prev_could_be) & (HH|MM)) *prev_could_be &= HH|MM; } else { val = dateToken((char *)t->t_pname, (int)t->t_len); switch (was) { case Month: could_be = MIY; break; case AmPm: case StdZone: case DstZone: if ((*prev_could_be) & (SS|MM|HH|HHMM|HHMMSS)) *prev_could_be &= SS|MM|HH|HHMM|HHMMSS; zone += val; zoneindex = index; break; default: break; } } if (could_be) { values[index] = val; types[index] = could_be; prev_could_be = &types[index]; ++index; } expect_zone = 0; } /* internal constraints */ again: /* \relax */ val = 0; for (i = 0; i < index; ++i) { /* If types[i] has exactly one bit set... */ if (types[i] != 0 && (MAXFLAG/types[i])*types[i] == MAXFLAG) val |= types[i]; } for (i = 0; i < index; ++i) { if ((types[i]&val) != types[i]) types[i] &= ~val; } /* the day of month is either just before or just after the month */ if (val & MIY) { for (i = 0; i < index; ++i) if (types[i] == MIY) { if (i > 0 && (types[i-1] & DD) && (i+1 == index || !(types[i+1] & DD))) { types[i-1] = DD; val |= DD; } if (i+1 < index && (types[i+1] & DD) && (i == 0 || !(types[i-1] & DD))) { types[i+1] = DD; val |= DD; } break; } } /* enforce HH MM SS order */ if (val & HHMM) { for (i = 0; i < index; ++i) if (types[i]&(HH|MM|HHMMSS)) types[i] &= ~(HH|MM|HHMMSS); } if (val & HHMMSS) { for (i = 0; i < index; ++i) if (types[i]&(HH|MM|SS|HHMM)) types[i] &= ~(HH|MM|SS|HHMM); } if (val & SS) { for (i = 0; i < index; ++i) if (types[i] == SS) { if (i > 0 && (types[i-1] & MM)) { types[i-1] = MM; val |= MM; } break; } } if (val & HH) { for (i = 0; i < index; ++i) if (types[i] == HH) { if (i+1 < index && (types[i+1] & MM)) { types[i+1] = MM; val |= MM; } break; } } if (val & MM) { for (i = 0; i < index; ++i) if (types[i] == MM) { if (i > 0 && (types[i-1] & HH)) { types[i-1] = HH; val |= HH; } if (i+1 < index && (types[i+1] & SS)) { types[i+1] = SS; val |= SS; } break; } } for (i = 0; i < index; ++i) { if ((types[i]&val) != types[i]) types[i] &= ~val; } /* disambiguate stuff... */ for (i = 0; i < index; ++i) { if (types[i] == 0) continue; if ((MAXFLAG/types[i])*types[i] != MAXFLAG) { /* There's more than one bit set */ if (zoneindex >= i+1 && zoneindex <= i+3) { if (types[zoneindex-1] & (MM|SS)) types[--zoneindex] &= (MM|SS); if (types[zoneindex-1] & (HH|MM|HHMM)) types[--zoneindex] &= (HH|MM|HHMM); if (zoneindex > 0 && (types[zoneindex-1] & HH)) types[--zoneindex] &= HH; zoneindex = 0; goto again; } else if ((types[i] & MIY) && i+1 < index && (types[i+1] & DD)) { types[i] &= MIY; types[i+1] &= DD; goto again; } else if ((types[i] & MM) && i+1 < index && (types[i+1] & SS)) { types[i] &= MM; types[i+1] &= SS; goto again; } else if ((types[i] & YYYY) && ((i+1 < index && (types[i+1] & (HH|HHMM|HHMMSS)) == types[i+1]) || (i > 0 && types[i-1] == DD))) { types[i] &= YYYY; } } } century = month = dayinmonth = -1; year = 0; sec = 0; alreadyhave = 0; for (i = 0; i < index; ++i) { switch (types[i]) { case HHMMSS: if (!(alreadyhave & HHMMSS)) sec = values[i]%100; alreadyhave |= HHMMSS; values[i] /= 100; case HHMM: if (!(alreadyhave & HHMM)) sec += 60 * values[i]%100; alreadyhave |= HHMM; values[i] /= 100; case HH: if (!(alreadyhave & HH)) values[i] *= 60; alreadyhave |= HH; case MM: if (!(alreadyhave & MM)) values[i] *= 60; alreadyhave |= MM; case SS: if (!(alreadyhave & SS)) sec += values[i]; alreadyhave |= SS; break; case MIY: if (!(alreadyhave & MIY)) month = values[i]; alreadyhave |= MIY; break; case DD: if (!(alreadyhave & DD)) dayinmonth = values[i]; alreadyhave |= DD; break; case YY: /* 00..38 => next century */ /* 39 .. 99 => this one */ if (!(alreadyhave & YY)) values[i] += values[i] > 38 ? 1900 : 2000; alreadyhave |= YY; case YYYY: if (!(alreadyhave & YYYY)) { if (types[i] == YYYY) have_year++; /* explicit year */ year += values[i]%100; century = values[i]/100; } alreadyhave |= YYYY; break; } } if (century < 0) /* default century */ century = this_century; if (year <= 0 && !have_year) /* default year (wraparound buggy...) */ year += localtmptr->tm_year%100; if (month <= 0) /* default month */ month = localtmptr->tm_mon + 1; if (dayinmonth < 0) /* default day in month */ dayinmonth = localtmptr->tm_mday; days = lilian(century*100+year, month, dayinmonth) - lilian(1970, 1, 1); sec += (days * 24 * 60 - zone) * 60; return sec < 0 ? 0L : sec; }