1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel (at) haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at https://curl.haxx.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ***************************************************************************/ 22 /* 23 A brief summary of the date string formats this parser groks: 24 25 RFC 2616 3.3.1 26 27 Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 28 Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 29 Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format 30 31 we support dates without week day name: 32 33 06 Nov 1994 08:49:37 GMT 34 06-Nov-94 08:49:37 GMT 35 Nov 6 08:49:37 1994 36 37 without the time zone: 38 39 06 Nov 1994 08:49:37 40 06-Nov-94 08:49:37 41 42 weird order: 43 44 1994 Nov 6 08:49:37 (GNU date fails) 45 GMT 08:49:37 06-Nov-94 Sunday 46 94 6 Nov 08:49:37 (GNU date fails) 47 48 time left out: 49 50 1994 Nov 6 51 06-Nov-94 52 Sun Nov 6 94 53 54 unusual separators: 55 56 1994.Nov.6 57 Sun/Nov/6/94/GMT 58 59 commonly used time zone names: 60 61 Sun, 06 Nov 1994 08:49:37 CET 62 06 Nov 1994 08:49:37 EST 63 64 time zones specified using RFC822 style: 65 66 Sun, 12 Sep 2004 15:05:58 -0700 67 Sat, 11 Sep 2004 21:32:11 +0200 68 69 compact numerical date strings: 70 71 20040912 15:05:58 -0700 72 20040911 +0200 73 74 */ 75 76 #include "curl_setup.h" 77 78 #include <limits.h> 79 80 #include <curl/curl.h> 81 #include "strcase.h" 82 #include "warnless.h" 83 #include "parsedate.h" 84 85 const char * const Curl_wkday[] = 86 {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; 87 static const char * const weekday[] = 88 { "Monday", "Tuesday", "Wednesday", "Thursday", 89 "Friday", "Saturday", "Sunday" }; 90 const char * const Curl_month[]= 91 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", 92 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 93 94 struct tzinfo { 95 char name[5]; 96 int offset; /* +/- in minutes */ 97 }; 98 99 /* 100 * parsedate() 101 * 102 * Returns: 103 * 104 * PARSEDATE_OK - a fine conversion 105 * PARSEDATE_FAIL - failed to convert 106 * PARSEDATE_LATER - time overflow at the far end of time_t 107 * PARSEDATE_SOONER - time underflow at the low end of time_t 108 */ 109 110 static int parsedate(const char *date, time_t *output); 111 112 #define PARSEDATE_OK 0 113 #define PARSEDATE_FAIL -1 114 #define PARSEDATE_LATER 1 115 #define PARSEDATE_SOONER 2 116 117 /* Here's a bunch of frequently used time zone names. These were supported 118 by the old getdate parser. */ 119 #define tDAYZONE -60 /* offset for daylight savings time */ 120 static const struct tzinfo tz[]= { 121 {"GMT", 0}, /* Greenwich Mean */ 122 {"UTC", 0}, /* Universal (Coordinated) */ 123 {"WET", 0}, /* Western European */ 124 {"BST", 0 tDAYZONE}, /* British Summer */ 125 {"WAT", 60}, /* West Africa */ 126 {"AST", 240}, /* Atlantic Standard */ 127 {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */ 128 {"EST", 300}, /* Eastern Standard */ 129 {"EDT", 300 tDAYZONE}, /* Eastern Daylight */ 130 {"CST", 360}, /* Central Standard */ 131 {"CDT", 360 tDAYZONE}, /* Central Daylight */ 132 {"MST", 420}, /* Mountain Standard */ 133 {"MDT", 420 tDAYZONE}, /* Mountain Daylight */ 134 {"PST", 480}, /* Pacific Standard */ 135 {"PDT", 480 tDAYZONE}, /* Pacific Daylight */ 136 {"YST", 540}, /* Yukon Standard */ 137 {"YDT", 540 tDAYZONE}, /* Yukon Daylight */ 138 {"HST", 600}, /* Hawaii Standard */ 139 {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */ 140 {"CAT", 600}, /* Central Alaska */ 141 {"AHST", 600}, /* Alaska-Hawaii Standard */ 142 {"NT", 660}, /* Nome */ 143 {"IDLW", 720}, /* International Date Line West */ 144 {"CET", -60}, /* Central European */ 145 {"MET", -60}, /* Middle European */ 146 {"MEWT", -60}, /* Middle European Winter */ 147 {"MEST", -60 tDAYZONE}, /* Middle European Summer */ 148 {"CEST", -60 tDAYZONE}, /* Central European Summer */ 149 {"MESZ", -60 tDAYZONE}, /* Middle European Summer */ 150 {"FWT", -60}, /* French Winter */ 151 {"FST", -60 tDAYZONE}, /* French Summer */ 152 {"EET", -120}, /* Eastern Europe, USSR Zone 1 */ 153 {"WAST", -420}, /* West Australian Standard */ 154 {"WADT", -420 tDAYZONE}, /* West Australian Daylight */ 155 {"CCT", -480}, /* China Coast, USSR Zone 7 */ 156 {"JST", -540}, /* Japan Standard, USSR Zone 8 */ 157 {"EAST", -600}, /* Eastern Australian Standard */ 158 {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */ 159 {"GST", -600}, /* Guam Standard, USSR Zone 9 */ 160 {"NZT", -720}, /* New Zealand */ 161 {"NZST", -720}, /* New Zealand Standard */ 162 {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */ 163 {"IDLE", -720}, /* International Date Line East */ 164 /* Next up: Military timezone names. RFC822 allowed these, but (as noted in 165 RFC 1123) had their signs wrong. Here we use the correct signs to match 166 actual military usage. 167 */ 168 {"A", 1 * 60}, /* Alpha */ 169 {"B", 2 * 60}, /* Bravo */ 170 {"C", 3 * 60}, /* Charlie */ 171 {"D", 4 * 60}, /* Delta */ 172 {"E", 5 * 60}, /* Echo */ 173 {"F", 6 * 60}, /* Foxtrot */ 174 {"G", 7 * 60}, /* Golf */ 175 {"H", 8 * 60}, /* Hotel */ 176 {"I", 9 * 60}, /* India */ 177 /* "J", Juliet is not used as a timezone, to indicate the observer's local 178 time */ 179 {"K", 10 * 60}, /* Kilo */ 180 {"L", 11 * 60}, /* Lima */ 181 {"M", 12 * 60}, /* Mike */ 182 {"N", -1 * 60}, /* November */ 183 {"O", -2 * 60}, /* Oscar */ 184 {"P", -3 * 60}, /* Papa */ 185 {"Q", -4 * 60}, /* Quebec */ 186 {"R", -5 * 60}, /* Romeo */ 187 {"S", -6 * 60}, /* Sierra */ 188 {"T", -7 * 60}, /* Tango */ 189 {"U", -8 * 60}, /* Uniform */ 190 {"V", -9 * 60}, /* Victor */ 191 {"W", -10 * 60}, /* Whiskey */ 192 {"X", -11 * 60}, /* X-ray */ 193 {"Y", -12 * 60}, /* Yankee */ 194 {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */ 195 }; 196 197 /* returns: 198 -1 no day 199 0 monday - 6 sunday 200 */ 201 202 static int checkday(const char *check, size_t len) 203 { 204 int i; 205 const char * const *what; 206 bool found = FALSE; 207 if(len > 3) 208 what = &weekday[0]; 209 else 210 what = &Curl_wkday[0]; 211 for(i = 0; i<7; i++) { 212 if(strcasecompare(check, what[0])) { 213 found = TRUE; 214 break; 215 } 216 what++; 217 } 218 return found?i:-1; 219 } 220 221 static int checkmonth(const char *check) 222 { 223 int i; 224 const char * const *what; 225 bool found = FALSE; 226 227 what = &Curl_month[0]; 228 for(i = 0; i<12; i++) { 229 if(strcasecompare(check, what[0])) { 230 found = TRUE; 231 break; 232 } 233 what++; 234 } 235 return found?i:-1; /* return the offset or -1, no real offset is -1 */ 236 } 237 238 /* return the time zone offset between GMT and the input one, in number 239 of seconds or -1 if the timezone wasn't found/legal */ 240 241 static int checktz(const char *check) 242 { 243 unsigned int i; 244 const struct tzinfo *what; 245 bool found = FALSE; 246 247 what = tz; 248 for(i = 0; i< sizeof(tz)/sizeof(tz[0]); i++) { 249 if(strcasecompare(check, what->name)) { 250 found = TRUE; 251 break; 252 } 253 what++; 254 } 255 return found?what->offset*60:-1; 256 } 257 258 static void skip(const char **date) 259 { 260 /* skip everything that aren't letters or digits */ 261 while(**date && !ISALNUM(**date)) 262 (*date)++; 263 } 264 265 enum assume { 266 DATE_MDAY, 267 DATE_YEAR, 268 DATE_TIME 269 }; 270 271 /* this is a clone of 'struct tm' but with all fields we don't need or use 272 cut out */ 273 struct my_tm { 274 int tm_sec; 275 int tm_min; 276 int tm_hour; 277 int tm_mday; 278 int tm_mon; 279 int tm_year; 280 }; 281 282 /* struct tm to time since epoch in GMT time zone. 283 * This is similar to the standard mktime function but for GMT only, and 284 * doesn't suffer from the various bugs and portability problems that 285 * some systems' implementations have. 286 */ 287 static time_t my_timegm(struct my_tm *tm) 288 { 289 static const int month_days_cumulative [12] = 290 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; 291 int month, year, leap_days; 292 293 if(tm->tm_year < 70) 294 /* we don't support years before 1970 as they will cause this function 295 to return a negative value */ 296 return -1; 297 298 year = tm->tm_year + 1900; 299 month = tm->tm_mon; 300 if(month < 0) { 301 year += (11 - month) / 12; 302 month = 11 - (11 - month) % 12; 303 } 304 else if(month >= 12) { 305 year -= month / 12; 306 month = month % 12; 307 } 308 309 leap_days = year - (tm->tm_mon <= 1); 310 leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400) 311 - (1969 / 4) + (1969 / 100) - (1969 / 400)); 312 313 return ((((time_t) (year - 1970) * 365 314 + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24 315 + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; 316 } 317 318 /* 319 * parsedate() 320 * 321 * Returns: 322 * 323 * PARSEDATE_OK - a fine conversion 324 * PARSEDATE_FAIL - failed to convert 325 * PARSEDATE_LATER - time overflow at the far end of time_t 326 * PARSEDATE_SOONER - time underflow at the low end of time_t 327 */ 328 329 static int parsedate(const char *date, time_t *output) 330 { 331 time_t t = 0; 332 int wdaynum = -1; /* day of the week number, 0-6 (mon-sun) */ 333 int monnum = -1; /* month of the year number, 0-11 */ 334 int mdaynum = -1; /* day of month, 1 - 31 */ 335 int hournum = -1; 336 int minnum = -1; 337 int secnum = -1; 338 int yearnum = -1; 339 int tzoff = -1; 340 struct my_tm tm; 341 enum assume dignext = DATE_MDAY; 342 const char *indate = date; /* save the original pointer */ 343 int part = 0; /* max 6 parts */ 344 345 while(*date && (part < 6)) { 346 bool found = FALSE; 347 348 skip(&date); 349 350 if(ISALPHA(*date)) { 351 /* a name coming up */ 352 char buf[32]=""; 353 size_t len; 354 if(sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZ" 355 "abcdefghijklmnopqrstuvwxyz]", buf)) 356 len = strlen(buf); 357 else 358 len = 0; 359 360 if(wdaynum == -1) { 361 wdaynum = checkday(buf, len); 362 if(wdaynum != -1) 363 found = TRUE; 364 } 365 if(!found && (monnum == -1)) { 366 monnum = checkmonth(buf); 367 if(monnum != -1) 368 found = TRUE; 369 } 370 371 if(!found && (tzoff == -1)) { 372 /* this just must be a time zone string */ 373 tzoff = checktz(buf); 374 if(tzoff != -1) 375 found = TRUE; 376 } 377 378 if(!found) 379 return PARSEDATE_FAIL; /* bad string */ 380 381 date += len; 382 } 383 else if(ISDIGIT(*date)) { 384 /* a digit */ 385 int val; 386 char *end; 387 int len = 0; 388 if((secnum == -1) && 389 (3 == sscanf(date, "%02d:%02d:%02d%n", 390 &hournum, &minnum, &secnum, &len))) { 391 /* time stamp! */ 392 date += len; 393 } 394 else if((secnum == -1) && 395 (2 == sscanf(date, "%02d:%02d%n", &hournum, &minnum, &len))) { 396 /* time stamp without seconds */ 397 date += len; 398 secnum = 0; 399 } 400 else { 401 long lval; 402 int error; 403 int old_errno; 404 405 old_errno = errno; 406 errno = 0; 407 lval = strtol(date, &end, 10); 408 error = errno; 409 if(errno != old_errno) 410 errno = old_errno; 411 412 if(error) 413 return PARSEDATE_FAIL; 414 415 #if LONG_MAX != INT_MAX 416 if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) 417 return PARSEDATE_FAIL; 418 #endif 419 420 val = curlx_sltosi(lval); 421 422 if((tzoff == -1) && 423 ((end - date) == 4) && 424 (val <= 1400) && 425 (indate< date) && 426 ((date[-1] == '+' || date[-1] == '-'))) { 427 /* four digits and a value less than or equal to 1400 (to take into 428 account all sorts of funny time zone diffs) and it is preceded 429 with a plus or minus. This is a time zone indication. 1400 is 430 picked since +1300 is frequently used and +1400 is mentioned as 431 an edge number in the document "ISO C 200X Proposal: Timezone 432 Functions" at http://david.tribble.com/text/c0xtimezone.html If 433 anyone has a more authoritative source for the exact maximum time 434 zone offsets, please speak up! */ 435 found = TRUE; 436 tzoff = (val/100 * 60 + val%100)*60; 437 438 /* the + and - prefix indicates the local time compared to GMT, 439 this we need ther reversed math to get what we want */ 440 tzoff = date[-1]=='+'?-tzoff:tzoff; 441 } 442 443 if(((end - date) == 8) && 444 (yearnum == -1) && 445 (monnum == -1) && 446 (mdaynum == -1)) { 447 /* 8 digits, no year, month or day yet. This is YYYYMMDD */ 448 found = TRUE; 449 yearnum = val/10000; 450 monnum = (val%10000)/100-1; /* month is 0 - 11 */ 451 mdaynum = val%100; 452 } 453 454 if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { 455 if((val > 0) && (val<32)) { 456 mdaynum = val; 457 found = TRUE; 458 } 459 dignext = DATE_YEAR; 460 } 461 462 if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { 463 yearnum = val; 464 found = TRUE; 465 if(yearnum < 1900) { 466 if(yearnum > 70) 467 yearnum += 1900; 468 else 469 yearnum += 2000; 470 } 471 if(mdaynum == -1) 472 dignext = DATE_MDAY; 473 } 474 475 if(!found) 476 return PARSEDATE_FAIL; 477 478 date = end; 479 } 480 } 481 482 part++; 483 } 484 485 if(-1 == secnum) 486 secnum = minnum = hournum = 0; /* no time, make it zero */ 487 488 if((-1 == mdaynum) || 489 (-1 == monnum) || 490 (-1 == yearnum)) 491 /* lacks vital info, fail */ 492 return PARSEDATE_FAIL; 493 494 #if SIZEOF_TIME_T < 5 495 /* 32 bit time_t can only hold dates to the beginning of 2038 */ 496 if(yearnum > 2037) { 497 *output = 0x7fffffff; 498 return PARSEDATE_LATER; 499 } 500 #endif 501 502 if(yearnum < 1970) { 503 *output = 0; 504 return PARSEDATE_SOONER; 505 } 506 507 if((mdaynum > 31) || (monnum > 11) || 508 (hournum > 23) || (minnum > 59) || (secnum > 60)) 509 return PARSEDATE_FAIL; /* clearly an illegal date */ 510 511 tm.tm_sec = secnum; 512 tm.tm_min = minnum; 513 tm.tm_hour = hournum; 514 tm.tm_mday = mdaynum; 515 tm.tm_mon = monnum; 516 tm.tm_year = yearnum - 1900; 517 518 /* my_timegm() returns a time_t. time_t is often 32 bits, even on many 519 architectures that feature 64 bit 'long'. 520 521 Some systems have 64 bit time_t and deal with years beyond 2038. However, 522 even on some of the systems with 64 bit time_t mktime() returns -1 for 523 dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06) 524 */ 525 t = my_timegm(&tm); 526 527 /* time zone adjust (cast t to int to compare to negative one) */ 528 if(-1 != (int)t) { 529 530 /* Add the time zone diff between local time zone and GMT. */ 531 long delta = (long)(tzoff!=-1?tzoff:0); 532 533 if((delta>0) && (t > LONG_MAX - delta)) { 534 *output = 0x7fffffff; 535 return PARSEDATE_LATER; /* time_t overflow */ 536 } 537 538 t += delta; 539 } 540 541 *output = t; 542 543 return PARSEDATE_OK; 544 } 545 546 time_t curl_getdate(const char *p, const time_t *now) 547 { 548 time_t parsed = -1; 549 int rc = parsedate(p, &parsed); 550 (void)now; /* legacy argument from the past that we ignore */ 551 552 switch(rc) { 553 case PARSEDATE_OK: 554 case PARSEDATE_LATER: 555 case PARSEDATE_SOONER: 556 return parsed; 557 } 558 /* everything else is fail */ 559 return -1; 560 } 561 562 /* 563 * Curl_gmtime() is a gmtime() replacement for portability. Do not use the 564 * gmtime_r() or gmtime() functions anywhere else but here. 565 * 566 */ 567 568 CURLcode Curl_gmtime(time_t intime, struct tm *store) 569 { 570 const struct tm *tm; 571 #ifdef HAVE_GMTIME_R 572 /* thread-safe version */ 573 tm = (struct tm *)gmtime_r(&intime, store); 574 #else 575 tm = gmtime(&intime); 576 if(tm) 577 *store = *tm; /* copy the pointed struct to the local copy */ 578 #endif 579 580 if(!tm) 581 return CURLE_BAD_FUNCTION_ARGUMENT; 582 return CURLE_OK; 583 } 584