Coverage Report

Created: 2023-05-19 06:16

/src/ntp-dev/sntp/libopts/parse-duration.c
Line
Count
Source (jump to first uncovered line)
1
/* Parse a time duration and return a seconds count
2
   Copyright (C) 2008-2015 Free Software Foundation, Inc.
3
   Written by Bruce Korb <bkorb@gnu.org>, 2008.
4
5
   This program is free software: you can redistribute it and/or modify
6
   it under the terms of the GNU Lesser General Public License as published by
7
   the Free Software Foundation; either version 2.1 of the License, or
8
   (at your option) any later version.
9
10
   This program is distributed in the hope that it will be useful,
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
   GNU Lesser General Public License for more details.
14
15
   You should have received a copy of the GNU Lesser General Public License
16
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18
#include <config.h>
19
20
/* Specification.  */
21
#include "parse-duration.h"
22
23
#include <ctype.h>
24
#include <errno.h>
25
#include <limits.h>
26
#include <stdio.h>
27
#include <stdlib.h>
28
#include <string.h>
29
30
#include "intprops.h"
31
32
#ifndef NUL
33
#define NUL '\0'
34
#endif
35
36
0
#define cch_t char const
37
38
typedef enum {
39
  NOTHING_IS_DONE,
40
  YEAR_IS_DONE,
41
  MONTH_IS_DONE,
42
  WEEK_IS_DONE,
43
  DAY_IS_DONE,
44
  HOUR_IS_DONE,
45
  MINUTE_IS_DONE,
46
  SECOND_IS_DONE
47
} whats_done_t;
48
49
0
#define SEC_PER_MIN     60
50
0
#define SEC_PER_HR      (SEC_PER_MIN * 60)
51
0
#define SEC_PER_DAY     (SEC_PER_HR  * 24)
52
0
#define SEC_PER_WEEK    (SEC_PER_DAY * 7)
53
0
#define SEC_PER_MONTH   (SEC_PER_DAY * 30)
54
0
#define SEC_PER_YEAR    (SEC_PER_DAY * 365)
55
56
#undef  MAX_DURATION
57
0
#define MAX_DURATION    TYPE_MAXIMUM(time_t)
58
59
/* Wrapper around strtoul that does not require a cast.  */
60
static unsigned long
61
str_const_to_ul (cch_t * str, cch_t ** ppz, int base)
62
0
{
63
0
  return strtoul (str, (char **)ppz, base);
64
0
}
65
66
/* Wrapper around strtol that does not require a cast.  */
67
static long
68
str_const_to_l (cch_t * str, cch_t ** ppz, int base)
69
0
{
70
0
  return strtol (str, (char **)ppz, base);
71
0
}
72
73
/* Returns BASE + VAL * SCALE, interpreting BASE = BAD_TIME
74
   with errno set as an error situation, and returning BAD_TIME
75
   with errno set in an error situation.  */
76
static time_t
77
scale_n_add (time_t base, time_t val, int scale)
78
0
{
79
0
  if (base == BAD_TIME)
80
0
    {
81
0
      if (errno == 0)
82
0
        errno = EINVAL;
83
0
      return BAD_TIME;
84
0
    }
85
86
0
  if (val > MAX_DURATION / scale)
87
0
    {
88
0
      errno = ERANGE;
89
0
      return BAD_TIME;
90
0
    }
91
92
0
  val *= scale;
93
0
  if (base > MAX_DURATION - val)
94
0
    {
95
0
      errno = ERANGE;
96
0
      return BAD_TIME;
97
0
    }
98
99
0
  return base + val;
100
0
}
101
102
/* After a number HH has been parsed, parse subsequent :MM or :MM:SS.  */
103
static time_t
104
parse_hr_min_sec (time_t start, cch_t * pz)
105
0
{
106
0
  int lpct = 0;
107
108
0
  errno = 0;
109
110
  /* For as long as our scanner pointer points to a colon *AND*
111
     we've not looped before, then keep looping.  (two iterations max) */
112
0
  while ((*pz == ':') && (lpct++ <= 1))
113
0
    {
114
0
      unsigned long v = str_const_to_ul (pz+1, &pz, 10);
115
116
0
      if (errno != 0)
117
0
        return BAD_TIME;
118
119
0
      start = scale_n_add (v, start, 60);
120
121
0
      if (errno != 0)
122
0
        return BAD_TIME;
123
0
    }
124
125
  /* allow for trailing spaces */
126
0
  while (isspace ((unsigned char)*pz))
127
0
    pz++;
128
0
  if (*pz != NUL)
129
0
    {
130
0
      errno = EINVAL;
131
0
      return BAD_TIME;
132
0
    }
133
134
0
  return start;
135
0
}
136
137
/* Parses a value and returns BASE + value * SCALE, interpreting
138
   BASE = BAD_TIME with errno set as an error situation, and returning
139
   BAD_TIME with errno set in an error situation.  */
140
static time_t
141
parse_scaled_value (time_t base, cch_t ** ppz, cch_t * endp, int scale)
142
0
{
143
0
  cch_t * pz = *ppz;
144
0
  time_t val;
145
146
0
  if (base == BAD_TIME)
147
0
    return base;
148
149
0
  errno = 0;
150
0
  val = str_const_to_ul (pz, &pz, 10);
151
0
  if (errno != 0)
152
0
    return BAD_TIME;
153
0
  while (isspace ((unsigned char)*pz))
154
0
    pz++;
155
0
  if (pz != endp)
156
0
    {
157
0
      errno = EINVAL;
158
0
      return BAD_TIME;
159
0
    }
160
161
0
  *ppz = pz;
162
0
  return scale_n_add (base, val, scale);
163
0
}
164
165
/* Parses the syntax YEAR-MONTH-DAY.
166
   PS points into the string, after "YEAR", before "-MONTH-DAY".  */
167
static time_t
168
parse_year_month_day (cch_t * pz, cch_t * ps)
169
0
{
170
0
  time_t res = 0;
171
172
0
  res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
173
174
0
  pz++; /* over the first '-' */
175
0
  ps = strchr (pz, '-');
176
0
  if (ps == NULL)
177
0
    {
178
0
      errno = EINVAL;
179
0
      return BAD_TIME;
180
0
    }
181
0
  res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
182
183
0
  pz++; /* over the second '-' */
184
0
  ps = pz + strlen (pz);
185
0
  return parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
186
0
}
187
188
/* Parses the syntax YYYYMMDD.  */
189
static time_t
190
parse_yearmonthday (cch_t * in_pz)
191
0
{
192
0
  time_t res = 0;
193
0
  char   buf[8];
194
0
  cch_t * pz;
195
196
0
  if (strlen (in_pz) != 8)
197
0
    {
198
0
      errno = EINVAL;
199
0
      return BAD_TIME;
200
0
    }
201
202
0
  memcpy (buf, in_pz, 4);
203
0
  buf[4] = NUL;
204
0
  pz = buf;
205
0
  res = parse_scaled_value (0, &pz, buf + 4, SEC_PER_YEAR);
206
207
0
  memcpy (buf, in_pz + 4, 2);
208
0
  buf[2] = NUL;
209
0
  pz =   buf;
210
0
  res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MONTH);
211
212
0
  memcpy (buf, in_pz + 6, 2);
213
0
  buf[2] = NUL;
214
0
  pz =   buf;
215
0
  return parse_scaled_value (res, &pz, buf + 2, SEC_PER_DAY);
216
0
}
217
218
/* Parses the syntax yy Y mm M ww W dd D.  */
219
static time_t
220
parse_YMWD (cch_t * pz)
221
0
{
222
0
  time_t res = 0;
223
0
  cch_t * ps = strchr (pz, 'Y');
224
0
  if (ps != NULL)
225
0
    {
226
0
      res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
227
0
      pz++;
228
0
    }
229
230
0
  ps = strchr (pz, 'M');
231
0
  if (ps != NULL)
232
0
    {
233
0
      res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
234
0
      pz++;
235
0
    }
236
237
0
  ps = strchr (pz, 'W');
238
0
  if (ps != NULL)
239
0
    {
240
0
      res = parse_scaled_value (res, &pz, ps, SEC_PER_WEEK);
241
0
      pz++;
242
0
    }
243
244
0
  ps = strchr (pz, 'D');
245
0
  if (ps != NULL)
246
0
    {
247
0
      res = parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
248
0
      pz++;
249
0
    }
250
251
0
  while (isspace ((unsigned char)*pz))
252
0
    pz++;
253
0
  if (*pz != NUL)
254
0
    {
255
0
      errno = EINVAL;
256
0
      return BAD_TIME;
257
0
    }
258
259
0
  return res;
260
0
}
261
262
/* Parses the syntax HH:MM:SS.
263
   PS points into the string, after "HH", before ":MM:SS".  */
264
static time_t
265
parse_hour_minute_second (cch_t * pz, cch_t * ps)
266
0
{
267
0
  time_t res = 0;
268
269
0
  res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
270
271
0
  pz++;
272
0
  ps = strchr (pz, ':');
273
0
  if (ps == NULL)
274
0
    {
275
0
      errno = EINVAL;
276
0
      return BAD_TIME;
277
0
    }
278
279
0
  res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
280
281
0
  pz++;
282
0
  ps = pz + strlen (pz);
283
0
  return parse_scaled_value (res, &pz, ps, 1);
284
0
}
285
286
/* Parses the syntax HHMMSS.  */
287
static time_t
288
parse_hourminutesecond (cch_t * in_pz)
289
0
{
290
0
  time_t res = 0;
291
0
  char   buf[4];
292
0
  cch_t * pz;
293
294
0
  if (strlen (in_pz) != 6)
295
0
    {
296
0
      errno = EINVAL;
297
0
      return BAD_TIME;
298
0
    }
299
300
0
  memcpy (buf, in_pz, 2);
301
0
  buf[2] = NUL;
302
0
  pz = buf;
303
0
  res = parse_scaled_value (0, &pz, buf + 2, SEC_PER_HR);
304
305
0
  memcpy (buf, in_pz + 2, 2);
306
0
  buf[2] = NUL;
307
0
  pz =   buf;
308
0
  res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MIN);
309
310
0
  memcpy (buf, in_pz + 4, 2);
311
0
  buf[2] = NUL;
312
0
  pz =   buf;
313
0
  return parse_scaled_value (res, &pz, buf + 2, 1);
314
0
}
315
316
/* Parses the syntax hh H mm M ss S.  */
317
static time_t
318
parse_HMS (cch_t * pz)
319
0
{
320
0
  time_t res = 0;
321
0
  cch_t * ps = strchr (pz, 'H');
322
0
  if (ps != NULL)
323
0
    {
324
0
      res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
325
0
      pz++;
326
0
    }
327
328
0
  ps = strchr (pz, 'M');
329
0
  if (ps != NULL)
330
0
    {
331
0
      res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
332
0
      pz++;
333
0
    }
334
335
0
  ps = strchr (pz, 'S');
336
0
  if (ps != NULL)
337
0
    {
338
0
      res = parse_scaled_value (res, &pz, ps, 1);
339
0
      pz++;
340
0
    }
341
342
0
  while (isspace ((unsigned char)*pz))
343
0
    pz++;
344
0
  if (*pz != NUL)
345
0
    {
346
0
      errno = EINVAL;
347
0
      return BAD_TIME;
348
0
    }
349
350
0
  return res;
351
0
}
352
353
/* Parses a time (hours, minutes, seconds) specification in either syntax.  */
354
static time_t
355
parse_time (cch_t * pz)
356
0
{
357
0
  cch_t * ps;
358
0
  time_t  res = 0;
359
360
  /*
361
   *  Scan for a hyphen
362
   */
363
0
  ps = strchr (pz, ':');
364
0
  if (ps != NULL)
365
0
    {
366
0
      res = parse_hour_minute_second (pz, ps);
367
0
    }
368
369
  /*
370
   *  Try for a 'H', 'M' or 'S' suffix
371
   */
372
0
  else if (ps = strpbrk (pz, "HMS"),
373
0
           ps == NULL)
374
0
    {
375
      /* Its a YYYYMMDD format: */
376
0
      res = parse_hourminutesecond (pz);
377
0
    }
378
379
0
  else
380
0
    res = parse_HMS (pz);
381
382
0
  return res;
383
0
}
384
385
/* Returns a substring of the given string, with spaces at the beginning and at
386
   the end destructively removed, per SNOBOL.  */
387
static char *
388
trim (char * pz)
389
0
{
390
  /* trim leading white space */
391
0
  while (isspace ((unsigned char)*pz))
392
0
    pz++;
393
394
  /* trim trailing white space */
395
0
  {
396
0
    char * pe = pz + strlen (pz);
397
0
    while ((pe > pz) && isspace ((unsigned char)pe[-1]))
398
0
      pe--;
399
0
    *pe = NUL;
400
0
  }
401
402
0
  return pz;
403
0
}
404
405
/*
406
 *  Parse the year/months/days of a time period
407
 */
408
static time_t
409
parse_period (cch_t * in_pz)
410
0
{
411
0
  char * pT;
412
0
  char * ps;
413
0
  char * pz   = strdup (in_pz);
414
0
  void * fptr = pz;
415
0
  time_t res  = 0;
416
417
0
  if (pz == NULL)
418
0
    {
419
0
      errno = ENOMEM;
420
0
      return BAD_TIME;
421
0
    }
422
423
0
  pT = strchr (pz, 'T');
424
0
  if (pT != NULL)
425
0
    {
426
0
      *(pT++) = NUL;
427
0
      pz = trim (pz);
428
0
      pT = trim (pT);
429
0
    }
430
431
  /*
432
   *  Scan for a hyphen
433
   */
434
0
  ps = strchr (pz, '-');
435
0
  if (ps != NULL)
436
0
    {
437
0
      res = parse_year_month_day (pz, ps);
438
0
    }
439
440
  /*
441
   *  Try for a 'Y', 'M' or 'D' suffix
442
   */
443
0
  else if (ps = strpbrk (pz, "YMWD"),
444
0
           ps == NULL)
445
0
    {
446
      /* Its a YYYYMMDD format: */
447
0
      res = parse_yearmonthday (pz);
448
0
    }
449
450
0
  else
451
0
    res = parse_YMWD (pz);
452
453
0
  if ((errno == 0) && (pT != NULL))
454
0
    {
455
0
      time_t val = parse_time (pT);
456
0
      res = scale_n_add (res, val, 1);
457
0
    }
458
459
0
  free (fptr);
460
0
  return res;
461
0
}
462
463
static time_t
464
parse_non_iso8601 (cch_t * pz)
465
0
{
466
0
  whats_done_t whatd_we_do = NOTHING_IS_DONE;
467
468
0
  time_t res = 0;
469
470
0
  do  {
471
0
    time_t val;
472
473
0
    errno = 0;
474
0
    val = str_const_to_l (pz, &pz, 10);
475
0
    if (errno != 0)
476
0
      goto bad_time;
477
478
    /*  IF we find a colon, then we're going to have a seconds value.
479
        We will not loop here any more.  We cannot already have parsed
480
        a minute value and if we've parsed an hour value, then the result
481
        value has to be less than an hour. */
482
0
    if (*pz == ':')
483
0
      {
484
0
        if (whatd_we_do >= MINUTE_IS_DONE)
485
0
          break;
486
487
0
        val = parse_hr_min_sec (val, pz);
488
489
0
        if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR))
490
0
          break;
491
492
0
        return scale_n_add (res, val, 1);
493
0
      }
494
495
0
    {
496
0
      unsigned int mult;
497
498
      /*  Skip over white space following the number we just parsed. */
499
0
      while (isspace ((unsigned char)*pz))
500
0
        pz++;
501
502
0
      switch (*pz)
503
0
        {
504
0
        default:  goto bad_time;
505
0
        case NUL:
506
0
          return scale_n_add (res, val, 1);
507
508
0
        case 'y': case 'Y':
509
0
          if (whatd_we_do >= YEAR_IS_DONE)
510
0
            goto bad_time;
511
0
          mult = SEC_PER_YEAR;
512
0
          whatd_we_do = YEAR_IS_DONE;
513
0
          break;
514
515
0
        case 'M':
516
0
          if (whatd_we_do >= MONTH_IS_DONE)
517
0
            goto bad_time;
518
0
          mult = SEC_PER_MONTH;
519
0
          whatd_we_do = MONTH_IS_DONE;
520
0
          break;
521
522
0
        case 'W':
523
0
          if (whatd_we_do >= WEEK_IS_DONE)
524
0
            goto bad_time;
525
0
          mult = SEC_PER_WEEK;
526
0
          whatd_we_do = WEEK_IS_DONE;
527
0
          break;
528
529
0
        case 'd': case 'D':
530
0
          if (whatd_we_do >= DAY_IS_DONE)
531
0
            goto bad_time;
532
0
          mult = SEC_PER_DAY;
533
0
          whatd_we_do = DAY_IS_DONE;
534
0
          break;
535
536
0
        case 'h':
537
0
          if (whatd_we_do >= HOUR_IS_DONE)
538
0
            goto bad_time;
539
0
          mult = SEC_PER_HR;
540
0
          whatd_we_do = HOUR_IS_DONE;
541
0
          break;
542
543
0
        case 'm':
544
0
          if (whatd_we_do >= MINUTE_IS_DONE)
545
0
            goto bad_time;
546
0
          mult = SEC_PER_MIN;
547
0
          whatd_we_do = MINUTE_IS_DONE;
548
0
          break;
549
550
0
        case 's':
551
0
          mult = 1;
552
0
          whatd_we_do = SECOND_IS_DONE;
553
0
          break;
554
0
        }
555
556
0
      res = scale_n_add (res, val, mult);
557
558
0
      pz++;
559
0
      while (isspace ((unsigned char)*pz))
560
0
        pz++;
561
0
      if (*pz == NUL)
562
0
        return res;
563
564
0
      if (! isdigit ((unsigned char)*pz))
565
0
        break;
566
0
    }
567
568
0
  } while (whatd_we_do < SECOND_IS_DONE);
569
570
0
 bad_time:
571
0
  errno = EINVAL;
572
0
  return BAD_TIME;
573
0
}
574
575
time_t
576
parse_duration (char const * pz)
577
0
{
578
0
  while (isspace ((unsigned char)*pz))
579
0
    pz++;
580
581
0
  switch (*pz)
582
0
    {
583
0
    case 'P':
584
0
      return parse_period (pz + 1);
585
586
0
    case 'T':
587
0
      return parse_time (pz + 1);
588
589
0
    default:
590
0
      if (isdigit ((unsigned char)*pz))
591
0
        return parse_non_iso8601 (pz);
592
593
0
      errno = EINVAL;
594
0
      return BAD_TIME;
595
0
    }
596
0
}
597
598
/*
599
 * Local Variables:
600
 * mode: C
601
 * c-file-style: "gnu"
602
 * indent-tabs-mode: nil
603
 * End:
604
 * end of parse-duration.c */