Coverage Report

Created: 2026-04-29 07:01

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