/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 */ |