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