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