Coverage Report

Created: 2026-01-10 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/isc/tm.c
Line
Count
Source
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3
 *
4
 * SPDX-License-Identifier: MPL-2.0 AND BSD-2-Clause
5
 *
6
 * This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9
 *
10
 * See the COPYRIGHT file distributed with this work for additional
11
 * information regarding copyright ownership.
12
 */
13
14
/*-
15
 * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
16
 * All rights reserved.
17
 *
18
 * This code was contributed to The NetBSD Foundation by Klaus Klein.
19
 *
20
 * Redistribution and use in source and binary forms, with or without
21
 * modification, are permitted provided that the following conditions
22
 * are met:
23
 * 1. Redistributions of source code must retain the above copyright
24
 *    notice, this list of conditions and the following disclaimer.
25
 * 2. Redistributions in binary form must reproduce the above copyright
26
 *    notice, this list of conditions and the following disclaimer in the
27
 *    documentation and/or other materials provided with the distribution.
28
 *
29
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
30
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
31
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
33
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39
 * POSSIBILITY OF SUCH DAMAGE.
40
 */
41
42
#include <ctype.h>
43
#include <stdio.h>
44
#include <stdlib.h>
45
#include <string.h>
46
#include <time.h>
47
48
#include <isc/tm.h>
49
#include <isc/util.h>
50
51
/*
52
 * Portable conversion routines for struct tm, replacing
53
 * timegm() and strptime(), which are not available on all
54
 * platforms and don't always behave the same way when they
55
 * are.
56
 */
57
58
/*
59
 * We do not implement alternate representations. However, we always
60
 * check whether a given modifier is allowed for a certain conversion.
61
 */
62
0
#define ALT_E 0x01
63
0
#define ALT_O 0x02
64
#define LEGAL_ALT(x)                          \
65
0
  {                                     \
66
0
    if ((alt_format & ~(x)) != 0) \
67
0
      return ((0));         \
68
0
  }
69
70
#ifndef TM_YEAR_BASE
71
0
#define TM_YEAR_BASE 1900
72
#endif /* ifndef TM_YEAR_BASE */
73
74
static const char *day[7] = { "Sunday",   "Monday", "Tuesday", "Wednesday",
75
            "Thursday", "Friday", "Saturday" };
76
static const char *abday[7] = {
77
  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
78
};
79
static const char *mon[12] = {
80
  "January", "February", "March",     "April",   "May",    "June",
81
  "July",    "August",   "September", "October", "November", "December"
82
};
83
static const char *abmon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
84
         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
85
static const char *am_pm[2] = { "AM", "PM" };
86
87
static int
88
0
conv_num(const char **buf, int *dest, int llim, int ulim) {
89
0
  int result = 0;
90
91
  /* The limit also determines the number of valid digits. */
92
0
  int rulim = ulim;
93
94
0
  if (!isdigit((unsigned char)**buf)) {
95
0
    return 0;
96
0
  }
97
98
0
  do {
99
0
    result = 10 * result + *(*buf)++ - '0';
100
0
    rulim /= 10;
101
0
  } while ((result * 10 <= ulim) && rulim &&
102
0
     isdigit((unsigned char)**buf));
103
104
0
  if (result < llim || result > ulim) {
105
0
    return 0;
106
0
  }
107
108
0
  *dest = result;
109
0
  return 1;
110
0
}
111
112
time_t
113
0
isc_tm_timegm(struct tm *tm) {
114
0
  time_t ret;
115
0
  int i, yday = 0, leapday;
116
0
  int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
117
118
0
  leapday = ((((tm->tm_year + 1900) % 4) == 0 &&
119
0
        ((tm->tm_year + 1900) % 100) != 0) ||
120
0
       ((tm->tm_year + 1900) % 400) == 0)
121
0
        ? 1
122
0
        : 0;
123
0
  mdays[1] += leapday;
124
125
0
  yday = tm->tm_mday - 1;
126
0
  for (i = 1; i <= tm->tm_mon; i++) {
127
0
    yday += mdays[i - 1];
128
0
  }
129
0
  ret = tm->tm_sec + (60 * tm->tm_min) + (3600 * tm->tm_hour) +
130
0
        (86400 *
131
0
         (yday + ((tm->tm_year - 70) * 365) + ((tm->tm_year - 69) / 4) -
132
0
    ((tm->tm_year - 1) / 100) + ((tm->tm_year + 299) / 400)));
133
0
  return ret;
134
0
}
135
136
char *
137
0
isc_tm_strptime(const char *buf, const char *fmt, struct tm *tm) {
138
0
  char c;
139
0
  const char *bp;
140
0
  size_t len = 0;
141
0
  int alt_format, i, split_year = 0;
142
143
0
  REQUIRE(buf != NULL);
144
0
  REQUIRE(fmt != NULL);
145
0
  REQUIRE(tm != NULL);
146
147
0
  memset(tm, 0, sizeof(struct tm));
148
149
0
  bp = buf;
150
151
0
  while ((c = *fmt) != '\0') {
152
    /* Clear `alternate' modifier prior to new conversion. */
153
0
    alt_format = 0;
154
155
    /* Eat up white-space. */
156
0
    if (isspace((unsigned char)c)) {
157
0
      while (isspace((unsigned char)*bp)) {
158
0
        bp++;
159
0
      }
160
161
0
      fmt++;
162
0
      continue;
163
0
    }
164
165
0
    if ((c = *fmt++) != '%') {
166
0
      goto literal;
167
0
    }
168
169
0
  again:
170
0
    switch (c = *fmt++) {
171
0
    case '%': /* "%%" is converted to "%". */
172
0
    literal:
173
0
      if (c != *bp++) {
174
0
        return 0;
175
0
      }
176
0
      break;
177
178
    /*
179
     * "Alternative" modifiers. Just set the appropriate flag
180
     * and start over again.
181
     */
182
0
    case 'E': /* "%E?" alternative conversion modifier. */
183
0
      LEGAL_ALT(0);
184
0
      alt_format |= ALT_E;
185
0
      goto again;
186
187
0
    case 'O': /* "%O?" alternative conversion modifier. */
188
0
      LEGAL_ALT(0);
189
0
      alt_format |= ALT_O;
190
0
      goto again;
191
192
    /*
193
     * "Complex" conversion rules, implemented through recursion.
194
     */
195
0
    case 'c': /* Date and time, using the locale's format. */
196
0
      LEGAL_ALT(ALT_E);
197
0
      if (!(bp = isc_tm_strptime(bp, "%x %X", tm))) {
198
0
        return 0;
199
0
      }
200
0
      break;
201
202
0
    case 'D': /* The date as "%m/%d/%y". */
203
0
      LEGAL_ALT(0);
204
0
      if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) {
205
0
        return 0;
206
0
      }
207
0
      break;
208
209
0
    case 'R': /* The time as "%H:%M". */
210
0
      LEGAL_ALT(0);
211
0
      if (!(bp = isc_tm_strptime(bp, "%H:%M", tm))) {
212
0
        return 0;
213
0
      }
214
0
      break;
215
216
0
    case 'r': /* The time in 12-hour clock representation. */
217
0
      LEGAL_ALT(0);
218
0
      if (!(bp = isc_tm_strptime(bp, "%I:%M:%S %p", tm))) {
219
0
        return 0;
220
0
      }
221
0
      break;
222
223
0
    case 'T': /* The time as "%H:%M:%S". */
224
0
      LEGAL_ALT(0);
225
0
      if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) {
226
0
        return 0;
227
0
      }
228
0
      break;
229
230
0
    case 'X': /* The time, using the locale's format. */
231
0
      LEGAL_ALT(ALT_E);
232
0
      if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) {
233
0
        return 0;
234
0
      }
235
0
      break;
236
237
0
    case 'x': /* The date, using the locale's format. */
238
0
      LEGAL_ALT(ALT_E);
239
0
      if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) {
240
0
        return 0;
241
0
      }
242
0
      break;
243
244
    /*
245
     * "Elementary" conversion rules.
246
     */
247
0
    case 'A': /* The day of week, using the locale's form. */
248
0
    case 'a':
249
0
      LEGAL_ALT(0);
250
0
      for (i = 0; i < 7; i++) {
251
        /* Full name. */
252
0
        len = strlen(day[i]);
253
0
        if (strncasecmp(day[i], bp, len) == 0) {
254
0
          break;
255
0
        }
256
257
        /* Abbreviated name. */
258
0
        len = strlen(abday[i]);
259
0
        if (strncasecmp(abday[i], bp, len) == 0) {
260
0
          break;
261
0
        }
262
0
      }
263
264
      /* Nothing matched. */
265
0
      if (i == 7) {
266
0
        return 0;
267
0
      }
268
269
0
      tm->tm_wday = i;
270
0
      bp += len;
271
0
      break;
272
273
0
    case 'B': /* The month, using the locale's form. */
274
0
    case 'b':
275
0
    case 'h':
276
0
      LEGAL_ALT(0);
277
0
      for (i = 0; i < 12; i++) {
278
        /* Full name. */
279
0
        len = strlen(mon[i]);
280
0
        if (strncasecmp(mon[i], bp, len) == 0) {
281
0
          break;
282
0
        }
283
284
        /* Abbreviated name. */
285
0
        len = strlen(abmon[i]);
286
0
        if (strncasecmp(abmon[i], bp, len) == 0) {
287
0
          break;
288
0
        }
289
0
      }
290
291
      /* Nothing matched. */
292
0
      if (i == 12) {
293
0
        return 0;
294
0
      }
295
296
0
      tm->tm_mon = i;
297
0
      bp += len;
298
0
      break;
299
300
0
    case 'C': /* The century number. */
301
0
      LEGAL_ALT(ALT_E);
302
0
      if (!(conv_num(&bp, &i, 0, 99))) {
303
0
        return 0;
304
0
      }
305
306
0
      if (split_year) {
307
0
        tm->tm_year = (tm->tm_year % 100) + (i * 100);
308
0
      } else {
309
0
        tm->tm_year = i * 100;
310
0
        split_year = 1;
311
0
      }
312
0
      break;
313
314
0
    case 'd': /* The day of month. */
315
0
    case 'e':
316
0
      LEGAL_ALT(ALT_O);
317
0
      if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) {
318
0
        return 0;
319
0
      }
320
0
      break;
321
322
0
    case 'k': /* The hour (24-hour clock representation). */
323
0
      LEGAL_ALT(0);
324
0
      FALLTHROUGH;
325
0
    case 'H':
326
0
      LEGAL_ALT(ALT_O);
327
0
      if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) {
328
0
        return 0;
329
0
      }
330
0
      break;
331
332
0
    case 'l': /* The hour (12-hour clock representation). */
333
0
      LEGAL_ALT(0);
334
0
      FALLTHROUGH;
335
0
    case 'I':
336
0
      LEGAL_ALT(ALT_O);
337
0
      if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) {
338
0
        return 0;
339
0
      }
340
0
      if (tm->tm_hour == 12) {
341
0
        tm->tm_hour = 0;
342
0
      }
343
0
      break;
344
345
0
    case 'j': /* The day of year. */
346
0
      LEGAL_ALT(0);
347
0
      if (!(conv_num(&bp, &i, 1, 366))) {
348
0
        return 0;
349
0
      }
350
0
      tm->tm_yday = i - 1;
351
0
      break;
352
353
0
    case 'M': /* The minute. */
354
0
      LEGAL_ALT(ALT_O);
355
0
      if (!(conv_num(&bp, &tm->tm_min, 0, 59))) {
356
0
        return 0;
357
0
      }
358
0
      break;
359
360
0
    case 'm': /* The month. */
361
0
      LEGAL_ALT(ALT_O);
362
0
      if (!(conv_num(&bp, &i, 1, 12))) {
363
0
        return 0;
364
0
      }
365
0
      tm->tm_mon = i - 1;
366
0
      break;
367
368
0
    case 'p': /* The locale's equivalent of AM/PM. */
369
0
      LEGAL_ALT(0);
370
      /* AM? */
371
0
      if (strcasecmp(am_pm[0], bp) == 0) {
372
0
        if (tm->tm_hour > 11) {
373
0
          return 0;
374
0
        }
375
376
0
        bp += strlen(am_pm[0]);
377
0
        break;
378
0
      }
379
      /* PM? */
380
0
      else if (strcasecmp(am_pm[1], bp) == 0)
381
0
      {
382
0
        if (tm->tm_hour > 11) {
383
0
          return 0;
384
0
        }
385
386
0
        tm->tm_hour += 12;
387
0
        bp += strlen(am_pm[1]);
388
0
        break;
389
0
      }
390
391
      /* Nothing matched. */
392
0
      return 0;
393
394
0
    case 'S': /* The seconds. */
395
0
      LEGAL_ALT(ALT_O);
396
0
      if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) {
397
0
        return 0;
398
0
      }
399
0
      break;
400
401
0
    case 'U': /* The week of year, beginning on sunday. */
402
0
    case 'W': /* The week of year, beginning on monday. */
403
0
      LEGAL_ALT(ALT_O);
404
      /*
405
       * XXX This is bogus, as we can not assume any valid
406
       * information present in the tm structure at this
407
       * point to calculate a real value, so just check the
408
       * range for now.
409
       */
410
0
      if (!(conv_num(&bp, &i, 0, 53))) {
411
0
        return 0;
412
0
      }
413
0
      break;
414
415
0
    case 'w': /* The day of week, beginning on sunday. */
416
0
      LEGAL_ALT(ALT_O);
417
0
      if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) {
418
0
        return 0;
419
0
      }
420
0
      break;
421
422
0
    case 'Y': /* The year. */
423
0
      LEGAL_ALT(ALT_E);
424
0
      if (!(conv_num(&bp, &i, 0, 9999))) {
425
0
        return 0;
426
0
      }
427
428
0
      tm->tm_year = i - TM_YEAR_BASE;
429
0
      break;
430
431
0
    case 'y': /* The year within 100 years of the epoch. */
432
0
      LEGAL_ALT(ALT_E | ALT_O);
433
0
      if (!(conv_num(&bp, &i, 0, 99))) {
434
0
        return 0;
435
0
      }
436
437
0
      if (split_year) {
438
0
        tm->tm_year = ((tm->tm_year / 100) * 100) + i;
439
0
        break;
440
0
      }
441
0
      split_year = 1;
442
0
      if (i <= 68) {
443
0
        tm->tm_year = i + 2000 - TM_YEAR_BASE;
444
0
      } else {
445
0
        tm->tm_year = i + 1900 - TM_YEAR_BASE;
446
0
      }
447
0
      break;
448
449
    /*
450
     * Miscellaneous conversions.
451
     */
452
0
    case 'n': /* Any kind of white-space. */
453
0
    case 't':
454
0
      LEGAL_ALT(0);
455
0
      while (isspace((unsigned char)*bp)) {
456
0
        bp++;
457
0
      }
458
0
      break;
459
460
0
    default: /* Unknown/unsupported conversion. */
461
0
      return 0;
462
0
    }
463
0
  }
464
465
  /* LINTED functional specification */
466
0
  return UNCONST(bp);
467
0
}