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