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