Coverage Report

Created: 2026-07-14 07:09

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