Coverage Report

Created: 2026-01-25 06:10

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