parsedate: refactor
No binary (build failed)
—
(log yok)
| @@ -99,9 +99,7 @@ const char * const Curl_month[] = { | |
| #ifndef CURL_DISABLE_PARSEDATE | |
| #define PARSEDATE_LATER 1 | |
| - | #if defined(HAVE_TIME_T_UNSIGNED) || (SIZEOF_TIME_T < 5) |
| #define PARSEDATE_SOONER 2 | |
| - | #endif |
| static const char * const weekday[] = { | |
| "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" | |
| @@ -257,6 +255,18 @@ static void skip(const char **date) | |
| (*date)++; | |
| } | |
| + | /* each field is exactly -1 when unknown */ |
| + | struct when { |
| + | int wday; /* day of the week, 0-6 (mon-sun) */ |
| + | int mon; /* month of the year, 0-11 */ |
| + | int mday; /* day of month, 1 - 31 */ |
| + | int hour; /* hour of day, 0 - 23 */ |
| + | int min; /* minute of hour, 0 - 59 */ |
| + | int sec; /* second of minute, 0 - 60 (leap second) */ |
| + | int year; /* year, >= 1583 */ |
| + | int tzoff; /* time zone offset in seconds */ |
| + | }; |
| + | |
| enum assume { | |
| DATE_MDAY, | |
| DATE_YEAR, | |
| @@ -270,18 +280,17 @@ enum assume { | |
| * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to | |
| * mktime but for GMT only. | |
| */ | |
| - | static time_t time2epoch(int sec, int min, int hour, |
| - | int mday, int mon, int year) |
| + | static curl_off_t time2epoch(struct when *w) |
| { | |
| static const int cumulative_days[12] = { | |
| 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 | |
| }; | |
| - | int y = year - (mon <= 1); |
| + | int y = w->year - (w->mon <= 1); |
| int leap_days = (y / 4) - (y / 100) + (y / 400) - LEAP_DAYS_BEFORE_1969; | |
| - | time_t days = (time_t)(year - 1970) * 365 + leap_days + |
| - | cumulative_days[mon] + mday - 1; |
| + | curl_off_t days = (curl_off_t)(w->year - 1970) * 365 + leap_days + |
| + | cumulative_days[w->mon] + w->mday - 1; |
| - | return (((days * 24 + hour) * 60 + min) * 60) + sec; |
| + | return (((days * 24 + w->hour) * 60 + w->min) * 60) + w->sec; |
| } | |
| /* Returns the value of a single-digit or two-digit decimal number, return | |
| @@ -299,8 +308,7 @@ static int oneortwodigit(const char *date, const char **endp) | |
| } | |
| /* HH:MM:SS or HH:MM and accept single-digits too */ | |
| - | static bool match_time(const char *date, |
| - | int *h, int *m, int *s, char **endp) |
| + | static bool match_time(const char *date, struct when *w, char **endp) |
| { | |
| const char *p; | |
| int hh, mm, ss = 0; | |
| @@ -323,9 +331,9 @@ static bool match_time(const char *date, | |
| } | |
| return FALSE; /* not a time string */ | |
| match: | |
| - | *h = hh; |
| - | *m = mm; |
| - | *s = ss; |
| + | w->hour = hh; |
| + | w->min = mm; |
| + | w->sec = ss; |
| *endp = (char *)CURL_UNCONST(p); | |
| return TRUE; | |
| } | |
| @@ -344,209 +352,221 @@ match: | |
| /* Wednesday is the longest name this parser knows about */ | |
| #define NAME_LEN 12 | |
| - | static int parsedate(const char *date, time_t *output) |
| + | static void initwhen(struct when *w) |
| { | |
| - | time_t t = 0; |
| - | int wdaynum = -1; /* day of the week number, 0-6 (mon-sun) */ |
| - | int monnum = -1; /* month of the year number, 0-11 */ |
| - | int mdaynum = -1; /* day of month, 1 - 31 */ |
| - | int hournum = -1; |
| - | int minnum = -1; |
| - | int secnum = -1; |
| - | int yearnum = -1; |
| - | int tzoff = -1; |
| - | enum assume dignext = DATE_MDAY; |
| - | const char *indate = date; /* save the original pointer */ |
| - | int part = 0; /* max 6 parts */ |
| - | |
| - | while(*date && (part < 6)) { |
| - | bool found = FALSE; |
| - | |
| - | skip(&date); |
| - | |
| - | if(ISALPHA(*date)) { |
| - | /* a name coming up */ |
| - | size_t len = 0; |
| - | const char *p = date; |
| - | while(ISALPHA(*p) && (len < NAME_LEN)) { |
| - | p++; |
| - | len++; |
| - | } |
| + | w->wday = w->mon = w->mday = w->hour = w->min = w->sec = w->year = w->tzoff = |
| + | -1; |
| + | } |
| - | if(len != NAME_LEN) { |
| - | if(wdaynum == -1) { |
| - | wdaynum = checkday(date, len); |
| - | if(wdaynum != -1) |
| - | found = TRUE; |
| - | } |
| - | if(!found && (monnum == -1)) { |
| - | monnum = checkmonth(date, len); |
| - | if(monnum != -1) |
| - | found = TRUE; |
| - | } |
| + | static int datestring(const char **datep, struct when *w) |
| + | { |
| + | /* a name coming up */ |
| + | size_t len = 0; |
| + | const char *p = *datep; |
| + | bool found = FALSE; |
| + | while(ISALPHA(*p) && (len < NAME_LEN)) { |
| + | p++; |
| + | len++; |
| + | } |
| - | if(!found && (tzoff == -1)) { |
| - | /* this must be a time zone string */ |
| - | tzoff = checktz(date, len); |
| - | if(tzoff != -1) |
| - | found = TRUE; |
| - | } |
| - | } |
| - | if(!found) |
| - | return PARSEDATE_FAIL; /* bad string */ |
| + | if(len != NAME_LEN) { |
| + | if(w->wday == -1) { |
| + | w->wday = checkday(*datep, len); |
| + | if(w->wday != -1) |
| + | found = TRUE; |
| + | } |
| + | if(!found && (w->mon == -1)) { |
| + | w->mon = checkmonth(*datep, len); |
| + | if(w->mon != -1) |
| + | found = TRUE; |
| + | } |
| - | date += len; |
| + | if(!found && (w->tzoff == -1)) { |
| + | /* this must be a time zone string */ |
| + | w->tzoff = checktz(*datep, len); |
| + | if(w->tzoff != -1) |
| + | found = TRUE; |
| } | |
| - | else if(ISDIGIT(*date)) { |
| - | /* a digit */ |
| - | unsigned int val; |
| - | char *end; |
| - | if((secnum == -1) && |
| - | match_time(date, &hournum, &minnum, &secnum, &end)) { |
| - | /* time stamp */ |
| - | date = end; |
| - | } |
| - | else { |
| - | curl_off_t lval; |
| - | int num_digits = 0; |
| - | const char *p = date; |
| - | if(curlx_str_number(&p, &lval, 99999999)) |
| - | return PARSEDATE_FAIL; |
| - | |
| - | /* we know num_digits cannot be larger than 8 */ |
| - | num_digits = (int)(p - date); |
| - | val = (unsigned int)lval; |
| - | |
| - | if((tzoff == -1) && |
| - | (num_digits == 4) && |
| - | (val <= 1400) && |
| - | (indate < date) && |
| - | (date[-1] == '+' || date[-1] == '-')) { |
| - | /* four digits and a value less than or equal to 1400 (to take into |
| - | account all sorts of funny time zone diffs) and it is preceded |
| - | with a plus or minus. This is a time zone indication. 1400 is |
| - | picked since +1300 is frequently used and +1400 is mentioned as |
| - | an edge number in the document "ISO C 200X Proposal: Timezone |
| - | Functions" at http://david.tribble.com/text/c0xtimezone.html If |
| - | anyone has a more authoritative source for the exact maximum time |
| - | zone offsets, please speak up! */ |
| - | found = TRUE; |
| - | tzoff = ((val / 100 * 60) + (val % 100)) * 60; |
| - | |
| - | /* the + and - prefix indicates the local time compared to GMT, |
| - | this we need their reversed math to get what we want */ |
| - | tzoff = date[-1] == '+' ? -tzoff : tzoff; |
| - | } |
| + | } |
| + | if(!found) |
| + | return PARSEDATE_FAIL; /* bad string */ |
| - | else if((num_digits == 8) && |
| - | (yearnum == -1) && |
| - | (monnum == -1) && |
| - | (mdaynum == -1)) { |
| - | /* 8 digits, no year, month or day yet. This is YYYYMMDD */ |
| - | found = TRUE; |
| - | yearnum = val / 10000; |
| - | monnum = ((val % 10000) / 100) - 1; /* month is 0 - 11 */ |
| - | mdaynum = val % 100; |
| - | } |
| + | *datep += len; |
| + | return PARSEDATE_OK; |
| + | } |
| - | if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { |
| - | if((val > 0) && (val < 32)) { |
| - | mdaynum = val; |
| - | found = TRUE; |
| - | } |
| - | dignext = DATE_YEAR; |
| - | } |
| + | static int datenum(const char *indate, const char **datep, struct when *w, |
| + | enum assume *dignextp) |
| + | { |
| + | /* a digit */ |
| + | unsigned int val; |
| + | char *end; |
| + | const char *date = *datep; |
| + | enum assume dignext = *dignextp; |
| + | |
| + | if((w->sec == -1) && match_time(date, w, &end)) { |
| + | /* time stamp */ |
| + | date = end; |
| + | } |
| + | else { |
| + | bool found = FALSE; |
| + | curl_off_t lval; |
| + | int num_digits = 0; |
| + | const char *p = *datep; |
| + | if(curlx_str_number(&p, &lval, 99999999)) |
| + | return PARSEDATE_FAIL; |
| + | |
| + | /* we know num_digits cannot be larger than 8 */ |
| + | num_digits = (int)(p - *datep); |
| + | val = (unsigned int)lval; |
| + | |
| + | if((w->tzoff == -1) && |
| + | (num_digits == 4) && |
| + | (val <= 1400) && |
| + | (indate < date) && |
| + | (date[-1] == '+' || date[-1] == '-')) { |
| + | /* four digits and a value less than or equal to 1400 (to take into |
| + | account all sorts of funny time zone diffs) and it is preceded |
| + | with a plus or minus. This is a time zone indication. 1400 is |
| + | picked since +1300 is frequently used and +1400 is mentioned as |
| + | an edge number in the document "ISO C 200X Proposal: Timezone |
| + | Functions" at http://david.tribble.com/text/c0xtimezone.html If |
| + | anyone has a more authoritative source for the exact maximum time |
| + | zone offsets, please speak up! */ |
| + | found = TRUE; |
| + | w->tzoff = ((val / 100 * 60) + (val % 100)) * 60; |
| + | |
| + | /* the + and - prefix indicates the local time compared to GMT, |
| + | this we need their reversed math to get what we want */ |
| + | w->tzoff = date[-1] == '+' ? -w->tzoff : w->tzoff; |
| + | } |
| - | if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { |
| - | yearnum = val; |
| - | found = TRUE; |
| - | if(yearnum < 100) { |
| - | if(yearnum > 70) |
| - | yearnum += 1900; |
| - | else |
| - | yearnum += 2000; |
| - | } |
| - | if(mdaynum == -1) |
| - | dignext = DATE_MDAY; |
| - | } |
| + | else if((num_digits == 8) && (w->year == -1) && |
| + | (w->mon == -1) && (w->mday == -1)) { |
| + | /* 8 digits, no year, month or day yet. This is YYYYMMDD */ |
| + | found = TRUE; |
| + | w->year = val / 10000; |
| + | w->mon = ((val % 10000) / 100) - 1; /* month is 0 - 11 */ |
| + | w->mday = val % 100; |
| + | } |
| - | if(!found) |
| - | return PARSEDATE_FAIL; |
| + | if(!found && (dignext == DATE_MDAY) && (w->mday == -1)) { |
| + | if((val > 0) && (val < 32)) { |
| + | w->mday = val; |
| + | found = TRUE; |
| + | } |
| + | dignext = DATE_YEAR; |
| + | } |
| - | date = p; |
| + | if(!found && (dignext == DATE_YEAR) && (w->year == -1)) { |
| + | w->year = val; |
| + | found = TRUE; |
| + | if(w->year < 100) { |
| + | if(w->year > 70) |
| + | w->year += 1900; |
| + | else |
| + | w->year += 2000; |
| } | |
| + | if(w->mday == -1) |
| + | dignext = DATE_MDAY; |
| } | |
| - | part++; |
| + | if(!found) |
| + | return PARSEDATE_FAIL; |
| + | |
| + | date = p; |
| } | |
| + | *datep = date; |
| + | *dignextp = dignext; |
| + | return PARSEDATE_OK; |
| + | } |
| - | if(secnum == -1) |
| - | secnum = minnum = hournum = 0; /* no time, make it zero */ |
| + | static int datecheck(struct when *w) |
| + | { |
| + | if(w->sec == -1) |
| + | w->sec = w->min = w->hour = 0; /* no time, make it zero */ |
| - | if((mdaynum == -1) || |
| - | (monnum == -1) || |
| - | (yearnum == -1)) |
| + | if((w->mday == -1) || (w->mon == -1) || (w->year == -1)) |
| /* lacks vital info, fail */ | |
| return PARSEDATE_FAIL; | |
| - | #ifdef HAVE_TIME_T_UNSIGNED |
| - | if(yearnum < 1970) { |
| - | /* only positive numbers cannot return earlier */ |
| - | *output = TIME_T_MIN; |
| - | return PARSEDATE_SOONER; |
| - | } |
| - | #endif |
| + | /* The Gregorian calendar was introduced 1582 */ |
| + | else if(w->year < 1583) |
| + | return PARSEDATE_FAIL; |
| - | #if (SIZEOF_TIME_T < 5) |
| + | else if((w->mday > 31) || (w->mon > 11) || (w->hour > 23) || |
| + | (w->min > 59) || (w->sec > 60)) |
| + | return PARSEDATE_FAIL; /* clearly an illegal date */ |
| - | #ifdef HAVE_TIME_T_UNSIGNED |
| - | /* an unsigned 32-bit time_t can only hold dates to 2106 */ |
| - | if(yearnum > 2105) { |
| - | *output = TIME_T_MAX; |
| - | return PARSEDATE_LATER; |
| - | } |
| - | #else |
| - | /* a signed 32-bit time_t can only hold dates to the beginning of 2038 */ |
| - | if(yearnum > 2037) { |
| + | return PARSEDATE_OK; |
| + | } |
| + | |
| + | static void tzadjust(curl_off_t *tp, struct when *w) |
| + | { |
| + | if(w->tzoff == -1) /* unknown tz means no offset */ |
| + | w->tzoff = 0; |
| + | |
| + | /* Add the time zone diff between local time zone and GMT. */ |
| + | if((w->tzoff > 0) && (*tp > (curl_off_t)(CURL_OFF_T_MAX - w->tzoff))) |
| + | *tp = CURL_OFF_T_MAX; |
| + | else |
| + | *tp += w->tzoff; |
| + | /* this needs no minimum check since we require a year > 1582 */ |
| + | } |
| + | |
| + | static int mktimet(curl_off_t seconds, time_t *output) |
| + | { |
| + | #if SIZEOF_TIME_T < 5 |
| + | if(seconds > TIME_T_MAX) { |
| *output = TIME_T_MAX; | |
| return PARSEDATE_LATER; | |
| } | |
| - | if(yearnum < 1903) { |
| + | else if(seconds < TIME_T_MIN) { |
| *output = TIME_T_MIN; | |
| return PARSEDATE_SOONER; | |
| } | |
| + | #elif defined(HAVE_TIME_T_UNSIGNED) |
| + | if(seconds < 0) { |
| + | *output = 0; |
| + | return PARSEDATE_SOONER; |
| + | } |
| #endif | |
| + | *output = (time_t)seconds; |
| + | return PARSEDATE_OK; |
| + | } |
| - | #else |
| - | /* The Gregorian calendar was introduced 1582 */ |
| - | if(yearnum < 1583) |
| - | return PARSEDATE_FAIL; |
| - | #endif |
| - | |
| - | if((mdaynum > 31) || (monnum > 11) || |
| - | (hournum > 23) || (minnum > 59) || (secnum > 60)) |
| - | return PARSEDATE_FAIL; /* clearly an illegal date */ |
| + | static int parsedate(const char *date, time_t *output) |
| + | { |
| + | curl_off_t seconds = 0; |
| + | enum assume dignext = DATE_MDAY; |
| + | const char *indate = date; /* save the original pointer */ |
| + | int part = 0; /* max 6 parts */ |
| + | int rc = 0; |
| + | struct when w; |
| + | initwhen(&w); |
| - | /* time2epoch() returns a time_t. time_t is often 32 bits, sometimes even on |
| - | architectures that feature a 64 bits 'long' but ultimately time_t is the |
| - | correct data type to use. |
| - | */ |
| - | t = time2epoch(secnum, minnum, hournum, mdaynum, monnum, yearnum); |
| + | while(*date && (part < 6)) { |
| + | skip(&date); |
| - | /* Add the time zone diff between local time zone and GMT. */ |
| - | if(tzoff == -1) |
| - | tzoff = 0; |
| + | if(ISALPHA(*date)) |
| + | rc = datestring(&date, &w); |
| + | else if(ISDIGIT(*date)) |
| + | rc = datenum(indate, &date, &w, &dignext); |
| + | if(rc) |
| + | return rc; |
| - | if((tzoff > 0) && (t > (time_t)(TIME_T_MAX - tzoff))) { |
| - | *output = TIME_T_MAX; |
| - | return PARSEDATE_LATER; /* time_t overflow */ |
| + | part++; |
| } | |
| - | t += tzoff; |
| + | rc = datecheck(&w); |
| + | if(rc) |
| + | return rc; |
| - | *output = t; |
| + | seconds = time2epoch(&w); /* get number of seconds */ |
| + | tzadjust(&seconds, &w); /* handle the time zone offset */ |
| + | rc = mktimet(seconds, output); /* squeeze seconds into a time_t */ |
| - | return PARSEDATE_OK; |
| + | return rc; |
| } | |
| #else | |
| /* disabled */ | |
| @@ -127,6 +127,7 @@ static CURLcode test_lib517(const char *URL) | |
| { "IAintNoDateFool", -1 }, | |
| { "Thu Apr 18 22:50 2007 GMT", 1176936600 }, | |
| { "20110623 12:34:56", 1308832496 }, | |
| + | { "20110023 12:34:56", -1 }, |
| { "20110632 12:34:56", -1 }, | |
| { "20110623 56:34:56", -1 }, | |
| { "20111323 12:34:56", -1 }, | |
| @@ -219,9 +220,8 @@ static CURLcode test_lib517(const char *URL) | |
| { "1994-11-06 08:49:37.123 GMT", -1 }, | |
| { "19941106T084937Z", -1 }, | |
| /* Y2K38 & Historical Boundaries */ | |
| - | #if SIZEOF_TIME_T > 4 |
| - | /* for 32 bit time_t, we bail on >year 2037 */ |
| { "19 Jan 2038 03:14:07 GMT", 2147483647 }, | |
| + | #if SIZEOF_TIME_T > 4 |
| { "19 Jan 2038 03:14:08 GMT", 2147483648 }, | |
| { "01 Jan 69 00:00:00 GMT", 3124224000 }, | |
| #endif | |