1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2014 Gary Mills
5 * Copyright 2011, Nexenta Systems, Inc.  All rights reserved.
6 * Copyright (c) 1994 Powerdog Industries.  All rights reserved.
7 *
8 * Copyright (c) 2011 The FreeBSD Foundation
9 *
10 * Portions of this software were developed by David Chisnall
11 * under sponsorship from the FreeBSD Foundation.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 *    notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 *    notice, this list of conditions and the following disclaimer
20 *    in the documentation and/or other materials provided with the
21 *    distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
30 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
32 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * The views and conclusions contained in the software and documentation
36 * are those of the authors and should not be interpreted as representing
37 * official policies, either expressed or implied, of Powerdog Industries.
38 */
39
40#include "namespace.h"
41#include <time.h>
42#include <ctype.h>
43#include <errno.h>
44#include <stdlib.h>
45#include <string.h>
46#include <pthread.h>
47#include "private.h"
48#include "un-namespace.h"
49#include "libc_private.h"
50#include "timelocal.h"
51#include "tzfile.h"
52
53static char * _strptime(const char *, const char *, struct tm *, int *, locale_t);
54
55#define	asizeof(a)	(sizeof(a) / sizeof((a)[0]))
56
57#define	FLAG_NONE	(1 << 0)
58#define	FLAG_YEAR	(1 << 1)
59#define	FLAG_MONTH	(1 << 2)
60#define	FLAG_YDAY	(1 << 3)
61#define	FLAG_MDAY	(1 << 4)
62#define	FLAG_WDAY	(1 << 5)
63
64/*
65 * Calculate the week day of the first day of a year. Valid for
66 * the Gregorian calendar, which began Sept 14, 1752 in the UK
67 * and its colonies. Ref:
68 * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
69 */
70
71static int
72first_wday_of(int year)
73{
74	return (((2 * (3 - (year / 100) % 4)) + (year % 100) +
75		((year % 100) / 4) + (isleap(year) ? 6 : 0) + 1) % 7);
76}
77
78static char *
79_strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp,
80		locale_t locale)
81{
82	char	c;
83	const char *ptr;
84	int	day_offset = -1, wday_offset;
85	int week_offset;
86	int	i, len;
87	int flags;
88	int Ealternative, Oalternative;
89	int century, year;
90	const struct lc_time_T *tptr = __get_current_time_locale(locale);
91	static int start_of_month[2][13] = {
92		{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
93		{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
94	};
95
96	flags = FLAG_NONE;
97	century = -1;
98	year = -1;
99
100	ptr = fmt;
101	while (*ptr != 0) {
102		c = *ptr++;
103
104		if (c != '%') {
105			if (isspace_l((unsigned char)c, locale))
106				while (*buf != 0 &&
107				       isspace_l((unsigned char)*buf, locale))
108					buf++;
109			else if (c != *buf++)
110				return (NULL);
111			continue;
112		}
113
114		Ealternative = 0;
115		Oalternative = 0;
116label:
117		c = *ptr++;
118		switch (c) {
119		case '%':
120			if (*buf++ != '%')
121				return (NULL);
122			break;
123
124		case '+':
125			buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale);
126			if (buf == NULL)
127				return (NULL);
128			flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
129			break;
130
131		case 'C':
132			if (!isdigit_l((unsigned char)*buf, locale))
133				return (NULL);
134
135			/* XXX This will break for 3-digit centuries. */
136			len = 2;
137			for (i = 0; len && *buf != 0 &&
138			     isdigit_l((unsigned char)*buf, locale); buf++) {
139				i *= 10;
140				i += *buf - '0';
141				len--;
142			}
143
144			century = i;
145			flags |= FLAG_YEAR;
146
147			break;
148
149		case 'c':
150			buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale);
151			if (buf == NULL)
152				return (NULL);
153			flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
154			break;
155
156		case 'D':
157			buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale);
158			if (buf == NULL)
159				return (NULL);
160			flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
161			break;
162
163		case 'E':
164			if (Ealternative || Oalternative)
165				break;
166			Ealternative++;
167			goto label;
168
169		case 'O':
170			if (Ealternative || Oalternative)
171				break;
172			Oalternative++;
173			goto label;
174
175		case 'F':
176			buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale);
177			if (buf == NULL)
178				return (NULL);
179			flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
180			break;
181
182		case 'R':
183			buf = _strptime(buf, "%H:%M", tm, GMTp, locale);
184			if (buf == NULL)
185				return (NULL);
186			break;
187
188		case 'r':
189			buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale);
190			if (buf == NULL)
191				return (NULL);
192			break;
193
194		case 'T':
195			buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale);
196			if (buf == NULL)
197				return (NULL);
198			break;
199
200		case 'X':
201			buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale);
202			if (buf == NULL)
203				return (NULL);
204			break;
205
206		case 'x':
207			buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale);
208			if (buf == NULL)
209				return (NULL);
210			flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
211			break;
212
213		case 'j':
214			if (!isdigit_l((unsigned char)*buf, locale))
215				return (NULL);
216
217			len = 3;
218			for (i = 0; len && *buf != 0 &&
219			     isdigit_l((unsigned char)*buf, locale); buf++){
220				i *= 10;
221				i += *buf - '0';
222				len--;
223			}
224			if (i < 1 || i > 366)
225				return (NULL);
226
227			tm->tm_yday = i - 1;
228			flags |= FLAG_YDAY;
229
230			break;
231
232		case 'M':
233		case 'S':
234			if (*buf == 0 ||
235				isspace_l((unsigned char)*buf, locale))
236				break;
237
238			if (!isdigit_l((unsigned char)*buf, locale))
239				return (NULL);
240
241			len = 2;
242			for (i = 0; len && *buf != 0 &&
243				isdigit_l((unsigned char)*buf, locale); buf++){
244				i *= 10;
245				i += *buf - '0';
246				len--;
247			}
248
249			if (c == 'M') {
250				if (i > 59)
251					return (NULL);
252				tm->tm_min = i;
253			} else {
254				if (i > 60)
255					return (NULL);
256				tm->tm_sec = i;
257			}
258
259			break;
260
261		case 'H':
262		case 'I':
263		case 'k':
264		case 'l':
265			/*
266			 * %k and %l specifiers are documented as being
267			 * blank-padded.  However, there is no harm in
268			 * allowing zero-padding.
269			 *
270			 * XXX %k and %l specifiers may gobble one too many
271			 * digits if used incorrectly.
272			 */
273
274			len = 2;
275			if ((c == 'k' || c == 'l') &&
276			    isblank_l((unsigned char)*buf, locale)) {
277				buf++;
278				len = 1;
279			}
280
281			if (!isdigit_l((unsigned char)*buf, locale))
282				return (NULL);
283
284			for (i = 0; len && *buf != 0 &&
285			     isdigit_l((unsigned char)*buf, locale); buf++) {
286				i *= 10;
287				i += *buf - '0';
288				len--;
289			}
290			if (c == 'H' || c == 'k') {
291				if (i > 23)
292					return (NULL);
293			} else if (i == 0 || i > 12)
294				return (NULL);
295
296			tm->tm_hour = i;
297
298			break;
299
300		case 'p':
301			/*
302			 * XXX This is bogus if parsed before hour-related
303			 * specifiers.
304			 */
305			if (tm->tm_hour > 12)
306				return (NULL);
307
308			len = strlen(tptr->am);
309			if (strncasecmp_l(buf, tptr->am, len, locale) == 0) {
310				if (tm->tm_hour == 12)
311					tm->tm_hour = 0;
312				buf += len;
313				break;
314			}
315
316			len = strlen(tptr->pm);
317			if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) {
318				if (tm->tm_hour != 12)
319					tm->tm_hour += 12;
320				buf += len;
321				break;
322			}
323
324			return (NULL);
325
326		case 'A':
327		case 'a':
328			for (i = 0; i < asizeof(tptr->weekday); i++) {
329				len = strlen(tptr->weekday[i]);
330				if (strncasecmp_l(buf, tptr->weekday[i],
331						len, locale) == 0)
332					break;
333				len = strlen(tptr->wday[i]);
334				if (strncasecmp_l(buf, tptr->wday[i],
335						len, locale) == 0)
336					break;
337			}
338			if (i == asizeof(tptr->weekday))
339				return (NULL);
340
341			buf += len;
342			tm->tm_wday = i;
343			flags |= FLAG_WDAY;
344			break;
345
346		case 'U':
347		case 'W':
348			/*
349			 * XXX This is bogus, as we can not assume any valid
350			 * information present in the tm structure at this
351			 * point to calculate a real value, so just check the
352			 * range for now.
353			 */
354			if (!isdigit_l((unsigned char)*buf, locale))
355				return (NULL);
356
357			len = 2;
358			for (i = 0; len && *buf != 0 &&
359			     isdigit_l((unsigned char)*buf, locale); buf++) {
360				i *= 10;
361				i += *buf - '0';
362				len--;
363			}
364			if (i > 53)
365				return (NULL);
366
367			if (c == 'U')
368				day_offset = TM_SUNDAY;
369			else
370				day_offset = TM_MONDAY;
371
372
373			week_offset = i;
374
375			break;
376
377		case 'u':
378		case 'w':
379			if (!isdigit_l((unsigned char)*buf, locale))
380				return (NULL);
381
382			i = *buf++ - '0';
383			if (i < 0 || i > 7 || (c == 'u' && i < 1) ||
384			    (c == 'w' && i > 6))
385				return (NULL);
386
387			tm->tm_wday = i % 7;
388			flags |= FLAG_WDAY;
389
390			break;
391
392		case 'e':
393			/*
394			 * With %e format, our strftime(3) adds a blank space
395			 * before single digits.
396			 */
397			if (*buf != 0 &&
398			    isspace_l((unsigned char)*buf, locale))
399			       buf++;
400			/* FALLTHROUGH */
401		case 'd':
402			/*
403			 * The %e specifier was once explicitly documented as
404			 * not being zero-padded but was later changed to
405			 * equivalent to %d.  There is no harm in allowing
406			 * such padding.
407			 *
408			 * XXX The %e specifier may gobble one too many
409			 * digits if used incorrectly.
410			 */
411			if (!isdigit_l((unsigned char)*buf, locale))
412				return (NULL);
413
414			len = 2;
415			for (i = 0; len && *buf != 0 &&
416			     isdigit_l((unsigned char)*buf, locale); buf++) {
417				i *= 10;
418				i += *buf - '0';
419				len--;
420			}
421			if (i == 0 || i > 31)
422				return (NULL);
423
424			tm->tm_mday = i;
425			flags |= FLAG_MDAY;
426
427			break;
428
429		case 'B':
430		case 'b':
431		case 'h':
432			for (i = 0; i < asizeof(tptr->month); i++) {
433				if (Oalternative) {
434					if (c == 'B') {
435						len = strlen(tptr->alt_month[i]);
436						if (strncasecmp_l(buf,
437								tptr->alt_month[i],
438								len, locale) == 0)
439							break;
440					}
441				} else {
442					len = strlen(tptr->month[i]);
443					if (strncasecmp_l(buf, tptr->month[i],
444							len, locale) == 0)
445						break;
446				}
447			}
448			/*
449			 * Try the abbreviated month name if the full name
450			 * wasn't found and Oalternative was not requested.
451			 */
452			if (i == asizeof(tptr->month) && !Oalternative) {
453				for (i = 0; i < asizeof(tptr->month); i++) {
454					len = strlen(tptr->mon[i]);
455					if (strncasecmp_l(buf, tptr->mon[i],
456							len, locale) == 0)
457						break;
458				}
459			}
460			if (i == asizeof(tptr->month))
461				return (NULL);
462
463			tm->tm_mon = i;
464			buf += len;
465			flags |= FLAG_MONTH;
466
467			break;
468
469		case 'm':
470			if (!isdigit_l((unsigned char)*buf, locale))
471				return (NULL);
472
473			len = 2;
474			for (i = 0; len && *buf != 0 &&
475			     isdigit_l((unsigned char)*buf, locale); buf++) {
476				i *= 10;
477				i += *buf - '0';
478				len--;
479			}
480			if (i < 1 || i > 12)
481				return (NULL);
482
483			tm->tm_mon = i - 1;
484			flags |= FLAG_MONTH;
485
486			break;
487
488		case 's':
489			{
490			char *cp;
491			int sverrno;
492			long n;
493			time_t t;
494
495			sverrno = errno;
496			errno = 0;
497			n = strtol_l(buf, &cp, 10, locale);
498			if (errno == ERANGE || (long)(t = n) != n) {
499				errno = sverrno;
500				return (NULL);
501			}
502			errno = sverrno;
503			buf = cp;
504			if (gmtime_r(&t, tm) == NULL)
505				return (NULL);
506			*GMTp = 1;
507			flags |= FLAG_YDAY | FLAG_WDAY | FLAG_MONTH |
508			    FLAG_MDAY | FLAG_YEAR;
509			}
510			break;
511
512		case 'Y':
513		case 'y':
514			if (*buf == 0 ||
515			    isspace_l((unsigned char)*buf, locale))
516				break;
517
518			if (!isdigit_l((unsigned char)*buf, locale))
519				return (NULL);
520
521			len = (c == 'Y') ? 4 : 2;
522			for (i = 0; len && *buf != 0 &&
523			     isdigit_l((unsigned char)*buf, locale); buf++) {
524				i *= 10;
525				i += *buf - '0';
526				len--;
527			}
528			if (c == 'Y')
529				century = i / 100;
530			year = i % 100;
531
532			flags |= FLAG_YEAR;
533
534			break;
535
536		case 'Z':
537			{
538			const char *cp;
539			char *zonestr;
540
541			for (cp = buf; *cp &&
542			     isupper_l((unsigned char)*cp, locale); ++cp) {
543				/*empty*/}
544			if (cp - buf) {
545				zonestr = alloca(cp - buf + 1);
546				strncpy(zonestr, buf, cp - buf);
547				zonestr[cp - buf] = '\0';
548				tzset();
549				if (0 == strcmp(zonestr, "GMT") ||
550				    0 == strcmp(zonestr, "UTC")) {
551				    *GMTp = 1;
552				} else if (0 == strcmp(zonestr, tzname[0])) {
553				    tm->tm_isdst = 0;
554				} else if (0 == strcmp(zonestr, tzname[1])) {
555				    tm->tm_isdst = 1;
556				} else {
557				    return (NULL);
558				}
559				buf += cp - buf;
560			}
561			}
562			break;
563
564		case 'z':
565			{
566			int sign = 1;
567
568			if (*buf != '+') {
569				if (*buf == '-')
570					sign = -1;
571				else
572					return (NULL);
573			}
574
575			buf++;
576			i = 0;
577			for (len = 4; len > 0; len--) {
578				if (isdigit_l((unsigned char)*buf, locale)) {
579					i *= 10;
580					i += *buf - '0';
581					buf++;
582				} else if (len == 2) {
583					i *= 100;
584					break;
585				} else
586					return (NULL);
587			}
588
589			if (i > 1400 || (sign == -1 && i > 1200) ||
590			    (i % 100) >= 60)
591				return (NULL);
592			tm->tm_hour -= sign * (i / 100);
593			tm->tm_min  -= sign * (i % 100);
594			*GMTp = 1;
595			}
596			break;
597
598		case 'n':
599		case 't':
600			while (isspace_l((unsigned char)*buf, locale))
601				buf++;
602			break;
603
604		default:
605			return (NULL);
606		}
607	}
608
609	if (century != -1 || year != -1) {
610		if (year == -1)
611			year = 0;
612		if (century == -1) {
613			if (year < 69)
614				year += 100;
615		} else
616			year += century * 100 - TM_YEAR_BASE;
617		tm->tm_year = year;
618	}
619
620	if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) {
621		if ((flags & (FLAG_MONTH | FLAG_MDAY)) ==
622		    (FLAG_MONTH | FLAG_MDAY)) {
623			tm->tm_yday = start_of_month[isleap(tm->tm_year +
624			    TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1);
625			flags |= FLAG_YDAY;
626		} else if (day_offset != -1) {
627			int tmpwday, tmpyday, fwo;
628
629			fwo = first_wday_of(tm->tm_year + TM_YEAR_BASE);
630			/* No incomplete week (week 0). */
631			if (week_offset == 0 && fwo == day_offset)
632				return (NULL);
633
634			/* Set the date to the first Sunday (or Monday)
635			 * of the specified week of the year.
636			 */
637			tmpwday = (flags & FLAG_WDAY) ? tm->tm_wday :
638			    day_offset;
639			tmpyday = (7 - fwo + day_offset) % 7 +
640			    (week_offset - 1) * 7 +
641			    (tmpwday - day_offset + 7) % 7;
642			/* Impossible yday for incomplete week (week 0). */
643			if (tmpyday < 0) {
644				if (flags & FLAG_WDAY)
645					return (NULL);
646				tmpyday = 0;
647			}
648			tm->tm_yday = tmpyday;
649			flags |= FLAG_YDAY;
650		}
651	}
652
653	if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) {
654		if (!(flags & FLAG_MONTH)) {
655			i = 0;
656			while (tm->tm_yday >=
657			    start_of_month[isleap(tm->tm_year +
658			    TM_YEAR_BASE)][i])
659				i++;
660			if (i > 12) {
661				i = 1;
662				tm->tm_yday -=
663				    start_of_month[isleap(tm->tm_year +
664				    TM_YEAR_BASE)][12];
665				tm->tm_year++;
666			}
667			tm->tm_mon = i - 1;
668			flags |= FLAG_MONTH;
669		}
670		if (!(flags & FLAG_MDAY)) {
671			tm->tm_mday = tm->tm_yday -
672			    start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)]
673			    [tm->tm_mon] + 1;
674			flags |= FLAG_MDAY;
675		}
676		if (!(flags & FLAG_WDAY)) {
677			i = 0;
678			wday_offset = first_wday_of(tm->tm_year);
679			while (i++ <= tm->tm_yday) {
680				if (wday_offset++ >= 6)
681					wday_offset = 0;
682			}
683			tm->tm_wday = wday_offset;
684			flags |= FLAG_WDAY;
685		}
686	}
687
688	return ((char *)buf);
689}
690
691char *
692strptime_l(const char * __restrict buf, const char * __restrict fmt,
693    struct tm * __restrict tm, locale_t loc)
694{
695	char *ret;
696	int gmt;
697	FIX_LOCALE(loc);
698
699	gmt = 0;
700	ret = _strptime(buf, fmt, tm, &gmt, loc);
701	if (ret && gmt) {
702		time_t t = timegm(tm);
703
704		localtime_r(&t, tm);
705	}
706
707	return (ret);
708}
709
710char *
711strptime(const char * __restrict buf, const char * __restrict fmt,
712    struct tm * __restrict tm)
713{
714	return strptime_l(buf, fmt, tm, __get_locale());
715}
716