1 /*
2  * Powerdog Industries kindly requests feedback from anyone modifying
3  * this function:
4  *
5  * Date: Thu, 05 Jun 1997 23:17:17 -0400
6  * From: Kevin Ruddy <kevin.ruddy@powerdog.com>
7  * To: James FitzGibbon <james@nexis.net>
8  * Subject: Re: Use of your strptime(3) code (fwd)
9  *
10  * The reason for the "no mod" clause was so that modifications would
11  * come back and we could integrate them and reissue so that a wider
12  * audience could use it (thereby spreading the wealth).  This has
13  * made it possible to get strptime to work on many operating systems.
14  * I'm not sure why that's "plain unacceptable" to the FreeBSD team.
15  *
16  * Anyway, you can change it to "with or without modification" as
17  * you see fit.  Enjoy.
18  *
19  * Kevin Ruddy
20  * Powerdog Industries, Inc.
21  */
22 /*
23  * Copyright 2010, Nexenta Systems, Inc.  All rights reserved.
24  * Copyright (c) 1994 Powerdog Industries.  All rights reserved.
25  *
26  * Redistribution and use in source and binary forms, with or without
27  * modification, are permitted provided that the following conditions
28  * are met:
29  * 1. Redistributions of source code must retain the above copyright
30  *    notice, this list of conditions and the following disclaimer.
31  * 2. Redistributions in binary form must reproduce the above copyright
32  *    notice, this list of conditions and the following disclaimer
33  *    in the documentation and/or other materials provided with the
34  *    distribution.
35  * 3. All advertising materials mentioning features or use of this
36  *    software must display the following acknowledgement:
37  *      This product includes software developed by Powerdog Industries.
38  * 4. The name of Powerdog Industries may not be used to endorse or
39  *    promote products derived from this software without specific prior
40  *    written permission.
41  *
42  * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
43  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
45  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
46  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
47  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
48  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
49  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
50  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
51  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
52  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
53  */
54 #include "lint.h"
55 #include <time.h>
56 #include <ctype.h>
57 #include <errno.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <pthread.h>
61 #include <alloca.h>
62 #include "timelocal.h"
63 
64 #define	asizeof(a)	(sizeof (a) / sizeof ((a)[0]))
65 
66 static char *
67 __strptime(const char *buf, const char *fmt, struct tm *tm)
68 {
69 	char	c;
70 	const char *ptr;
71 	int	i, len;
72 	int Ealternative, Oalternative;
73 	struct lc_time_T *tptr = __get_current_time_locale();
74 
75 	ptr = fmt;
76 	while (*ptr != 0) {
77 		if (*buf == 0)
78 			break;
79 
80 		c = *ptr++;
81 
82 		if (c != '%') {
83 			if (isspace((unsigned char)c))
84 				while (*buf != 0 &&
85 				    isspace((unsigned char)*buf))
86 					buf++;
87 			else if (c != *buf++)
88 				return (0);
89 			continue;
90 		}
91 
92 		Ealternative = 0;
93 		Oalternative = 0;
94 label:
95 		c = *ptr++;
96 		switch (c) {
97 		case 0:
98 		case '%':
99 			if (*buf++ != '%')
100 				return (0);
101 			break;
102 
103 		case '+':
104 			buf = __strptime(buf, tptr->date_fmt, tm);
105 			if (buf == 0)
106 				return (0);
107 			break;
108 
109 		case 'C':
110 			if (!isdigit((unsigned char)*buf))
111 				return (0);
112 
113 			/* XXX This will break for 3-digit centuries. */
114 			len = 2;
115 			for (i = 0;
116 			    len && isdigit((unsigned char)*buf);
117 			    buf++) {
118 				i *= 10;
119 				i += *buf - '0';
120 				len--;
121 			}
122 			if (i < 19)
123 				return (0);
124 
125 			tm->tm_year = i * 100 - 1900;
126 			break;
127 
128 		case 'c':
129 			buf = __strptime(buf, tptr->c_fmt, tm);
130 			if (buf == 0)
131 				return (0);
132 			break;
133 
134 		case 'D':
135 			buf = __strptime(buf, "%m/%d/%y", tm);
136 			if (buf == 0)
137 				return (0);
138 			break;
139 
140 		case 'E':
141 			if (Ealternative || Oalternative)
142 				break;
143 			Ealternative++;
144 			goto label;
145 
146 		case 'O':
147 			if (Ealternative || Oalternative)
148 				break;
149 			Oalternative++;
150 			goto label;
151 
152 		case 'F':
153 			buf = __strptime(buf, "%Y-%m-%d", tm);
154 			if (buf == 0)
155 				return (0);
156 			break;
157 
158 		case 'R':
159 			buf = __strptime(buf, "%H:%M", tm);
160 			if (buf == 0)
161 				return (0);
162 			break;
163 
164 		case 'r':
165 			buf = __strptime(buf, tptr->ampm_fmt, tm);
166 			if (buf == 0)
167 				return (0);
168 			break;
169 
170 		case 'T':
171 			buf = __strptime(buf, "%H:%M:%S", tm);
172 			if (buf == 0)
173 				return (0);
174 			break;
175 
176 		case 'X':
177 			buf = __strptime(buf, tptr->X_fmt, tm);
178 			if (buf == 0)
179 				return (0);
180 			break;
181 
182 		case 'x':
183 			buf = __strptime(buf, tptr->x_fmt, tm);
184 			if (buf == 0)
185 				return (0);
186 			break;
187 
188 		case 'j':
189 			if (!isdigit((unsigned char)*buf))
190 				return (0);
191 
192 			len = 3;
193 			for (i = 0;
194 			    len && isdigit((unsigned char)*buf);
195 			    buf++) {
196 				i *= 10;
197 				i += *buf - '0';
198 				len--;
199 			}
200 			if (i < 1 || i > 366)
201 				return (0);
202 
203 			tm->tm_yday = i - 1;
204 			break;
205 
206 		case 'M':
207 		case 'S':
208 			if (*buf == 0 || isspace((unsigned char)*buf))
209 				break;
210 
211 			if (!isdigit((unsigned char)*buf))
212 				return (0);
213 
214 			len = 2;
215 			for (i = 0;
216 			    len && isdigit((unsigned char)*buf);
217 			    buf++) {
218 				i *= 10;
219 				i += *buf - '0';
220 				len--;
221 			}
222 
223 			if (c == 'M') {
224 				if (i > 59)
225 					return (0);
226 				tm->tm_min = i;
227 			} else {
228 				if (i > 60)
229 					return (0);
230 				tm->tm_sec = i;
231 			}
232 
233 			if (*buf != 0 && isspace((unsigned char)*buf))
234 				while (*ptr != 0 &&
235 				    !isspace((unsigned char)*ptr))
236 					ptr++;
237 			break;
238 
239 		case 'H':
240 		case 'I':
241 		case 'k':
242 		case 'l':
243 			/*
244 			 * Of these, %l is the only specifier explicitly
245 			 * documented as not being zero-padded.  However,
246 			 * there is no harm in allowing zero-padding.
247 			 *
248 			 * XXX The %l specifier may gobble one too many
249 			 * digits if used incorrectly.
250 			 */
251 			if (!isdigit((unsigned char)*buf))
252 				return (0);
253 
254 			len = 2;
255 			for (i = 0;
256 			    len && isdigit((unsigned char)*buf);
257 			    buf++) {
258 				i *= 10;
259 				i += *buf - '0';
260 				len--;
261 			}
262 			if (c == 'H' || c == 'k') {
263 				if (i > 23)
264 					return (0);
265 			} else if (i > 12)
266 				return (0);
267 
268 			tm->tm_hour = i;
269 
270 			if (*buf != 0 && isspace((unsigned char)*buf))
271 				while (*ptr != 0 &&
272 				    !isspace((unsigned char)*ptr))
273 					ptr++;
274 			break;
275 
276 		case 'p':
277 			/*
278 			 * XXX This is bogus if parsed before hour-related
279 			 * specifiers.
280 			 */
281 			len = strlen(tptr->am);
282 			if (strncasecmp(buf, tptr->am, len) == 0) {
283 				if (tm->tm_hour > 12)
284 					return (0);
285 				if (tm->tm_hour == 12)
286 					tm->tm_hour = 0;
287 				buf += len;
288 				break;
289 			}
290 
291 			len = strlen(tptr->pm);
292 			if (strncasecmp(buf, tptr->pm, len) == 0) {
293 				if (tm->tm_hour > 12)
294 					return (0);
295 				if (tm->tm_hour != 12)
296 					tm->tm_hour += 12;
297 				buf += len;
298 				break;
299 			}
300 
301 			return (0);
302 
303 		case 'A':
304 		case 'a':
305 			for (i = 0; i < asizeof(tptr->weekday); i++) {
306 				len = strlen(tptr->weekday[i]);
307 				if (strncasecmp(buf, tptr->weekday[i], len) ==
308 				    0)
309 					break;
310 				len = strlen(tptr->wday[i]);
311 				if (strncasecmp(buf, tptr->wday[i], len) == 0)
312 					break;
313 			}
314 			if (i == asizeof(tptr->weekday))
315 				return (0);
316 
317 			tm->tm_wday = i;
318 			buf += len;
319 			break;
320 
321 		case 'U':
322 		case 'W':
323 			/*
324 			 * XXX This is bogus, as we can not assume any valid
325 			 * information present in the tm structure at this
326 			 * point to calculate a real value, so just check the
327 			 * range for now.
328 			 */
329 			if (!isdigit((unsigned char)*buf))
330 				return (0);
331 
332 			len = 2;
333 			for (i = 0;
334 			    len && isdigit((unsigned char)*buf);
335 			    buf++) {
336 				i *= 10;
337 				i += *buf - '0';
338 				len--;
339 			}
340 			if (i > 53)
341 				return (0);
342 
343 			if (*buf != 0 && isspace((unsigned char)*buf))
344 				while (*ptr != 0 &&
345 				    !isspace((unsigned char)*ptr))
346 					ptr++;
347 			break;
348 
349 		case 'w':
350 			if (!isdigit((unsigned char)*buf))
351 				return (0);
352 
353 			i = *buf - '0';
354 			if (i > 6)
355 				return (0);
356 
357 			tm->tm_wday = i;
358 
359 			if (*buf != 0 && isspace((unsigned char)*buf))
360 				while (*ptr != 0 &&
361 				    !isspace((unsigned char)*ptr))
362 					ptr++;
363 			break;
364 
365 		case 'd':
366 		case 'e':
367 			/*
368 			 * The %e specifier is explicitly documented as not
369 			 * being zero-padded but there is no harm in allowing
370 			 * such padding.
371 			 *
372 			 * XXX The %e specifier may gobble one too many
373 			 * digits if used incorrectly.
374 			 */
375 			if (!isdigit((unsigned char)*buf))
376 				return (0);
377 
378 			len = 2;
379 			for (i = 0;
380 			    len && isdigit((unsigned char)*buf);
381 			    buf++) {
382 				i *= 10;
383 				i += *buf - '0';
384 				len--;
385 			}
386 			if (i > 31)
387 				return (0);
388 
389 			tm->tm_mday = i;
390 
391 			if (*buf != 0 && isspace((unsigned char)*buf))
392 				while (*ptr != 0 &&
393 				    !isspace((unsigned char)*ptr))
394 					ptr++;
395 			break;
396 
397 		case 'B':
398 		case 'b':
399 		case 'h':
400 			for (i = 0; i < asizeof(tptr->month); i++) {
401 				len = strlen(tptr->month[i]);
402 				if (strncasecmp(buf, tptr->month[i], len) == 0)
403 					break;
404 			}
405 			/*
406 			 * Try the abbreviated month name if the full name
407 			 * wasn't found.
408 			 */
409 			if (i == asizeof(tptr->month)) {
410 				for (i = 0; i < asizeof(tptr->month); i++) {
411 					len = strlen(tptr->mon[i]);
412 					if (strncasecmp(buf, tptr->mon[i],
413 					    len) == 0)
414 						break;
415 				}
416 			}
417 			if (i == asizeof(tptr->month))
418 				return (0);
419 
420 			tm->tm_mon = i;
421 			buf += len;
422 			break;
423 
424 		case 'm':
425 			if (!isdigit((unsigned char)*buf))
426 				return (0);
427 
428 			len = 2;
429 			for (i = 0;
430 			    len && isdigit((unsigned char)*buf);
431 			    buf++) {
432 				i *= 10;
433 				i += *buf - '0';
434 				len--;
435 			}
436 			if (i < 1 || i > 12)
437 				return (0);
438 
439 			tm->tm_mon = i - 1;
440 
441 			if (*buf != 0 && isspace((unsigned char)*buf))
442 				while (*ptr != 0 &&
443 				    !isspace((unsigned char)*ptr))
444 					ptr++;
445 			break;
446 
447 		case 'Y':
448 		case 'y':
449 			if (*buf == 0 || isspace((unsigned char)*buf))
450 				break;
451 
452 			if (!isdigit((unsigned char)*buf))
453 				return (0);
454 
455 			len = (c == 'Y') ? 4 : 2;
456 			for (i = 0;
457 			    len && isdigit((unsigned char)*buf);
458 			    buf++) {
459 				i *= 10;
460 				i += *buf - '0';
461 				len--;
462 			}
463 			if (c == 'Y')
464 				i -= 1900;
465 			if (c == 'y' && i < 69)
466 				i += 100;
467 			if (i < 0)
468 				return (0);
469 
470 			tm->tm_year = i;
471 
472 			if (*buf != 0 && isspace((unsigned char)*buf))
473 				while (*ptr != 0 &&
474 				    !isspace((unsigned char)*ptr))
475 					ptr++;
476 			break;
477 
478 		case 'Z':
479 			{
480 			const char *cp = buf;
481 			char *zonestr;
482 
483 			while (isupper((unsigned char)*cp))
484 				++cp;
485 			if (cp - buf) {
486 				zonestr = alloca(cp - buf + 1);
487 				(void) strncpy(zonestr, buf, cp - buf);
488 				zonestr[cp - buf] = '\0';
489 				tzset();
490 				/*
491 				 * Once upon a time this supported "GMT",
492 				 * for GMT, but we removed this as Solaris
493 				 * doesn't have it, and we lack the needed
494 				 * timegm function.
495 				 */
496 				if (0 == strcmp(zonestr, tzname[0])) {
497 					tm->tm_isdst = 0;
498 				} else if (0 == strcmp(zonestr, tzname[1])) {
499 					tm->tm_isdst = 1;
500 				} else {
501 					return (0);
502 				}
503 				buf += cp - buf;
504 			}
505 			}
506 			break;
507 
508 		/*
509 		 * Note that there used to be support %z and %s, but these
510 		 * are not supported by Solaris, so we have removed them.
511 		 * They would have required timegm() which is missing.
512 		 */
513 		}
514 	}
515 	return ((char *)buf);
516 }
517 
518 char *
519 strptime(const char *buf, const char *fmt, struct tm *tm)
520 {
521 	/* Legacy Solaris strptime clears the incoming tm structure. */
522 	(void) memset(tm, 0, sizeof (*tm));
523 
524 	return (__strptime(buf, fmt, tm));
525 }
526 
527 /*
528  * This is used by Solaris, and is a variant that does not clear the
529  * incoming tm.  It is triggered by -D_STRPTIME_DONTZERO.
530  */
531 char *
532 __strptime_dontzero(const char *buf, const char *fmt, struct tm *tm)
533 {
534 	return (__strptime(buf, fmt, tm));
535 }
536