xref: /freebsd/crypto/openssh/openbsd-compat/strptime.c (revision 1170f3d12ebd03d02f8bb4f075319f461e38d7d9)
1 /*	$OpenBSD: strptime.c,v 1.12 2008/06/26 05:42:05 ray Exp $ */
2 /*	$NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $	*/
3 
4 /*-
5  * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code was contributed to The NetBSD Foundation by Klaus Klein.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /* OPENBSD ORIGINAL: lib/libc/time/strptime.c */
33 
34 #include "includes.h"
35 
36 #ifndef HAVE_STRPTIME
37 
38 #define TM_YEAR_BASE 1900	/* from tzfile.h */
39 
40 #include <ctype.h>
41 #include <locale.h>
42 #include <string.h>
43 #include <time.h>
44 
45 /* #define	_ctloc(x)		(_CurrentTimeLocale->x) */
46 
47 /*
48  * We do not implement alternate representations. However, we always
49  * check whether a given modifier is allowed for a certain conversion.
50  */
51 #define _ALT_E			0x01
52 #define _ALT_O			0x02
53 #define	_LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
54 
55 
56 static	int _conv_num(const unsigned char **, int *, int, int);
57 static	char *_strptime(const char *, const char *, struct tm *, int);
58 
59 
60 char *
strptime(const char * buf,const char * fmt,struct tm * tm)61 strptime(const char *buf, const char *fmt, struct tm *tm)
62 {
63 	return(_strptime(buf, fmt, tm, 1));
64 }
65 
66 static char *
_strptime(const char * buf,const char * fmt,struct tm * tm,int initialize)67 _strptime(const char *buf, const char *fmt, struct tm *tm, int initialize)
68 {
69 	unsigned char c;
70 	const unsigned char *bp;
71 	size_t len;
72 	int alt_format, i;
73 	static int century, relyear;
74 
75 	if (initialize) {
76 		century = TM_YEAR_BASE;
77 		relyear = -1;
78 	}
79 
80 	bp = (unsigned char *)buf;
81 	while ((c = *fmt) != '\0') {
82 		/* Clear `alternate' modifier prior to new conversion. */
83 		alt_format = 0;
84 
85 		/* Eat up white-space. */
86 		if (isspace(c)) {
87 			while (isspace(*bp))
88 				bp++;
89 
90 			fmt++;
91 			continue;
92 		}
93 
94 		if ((c = *fmt++) != '%')
95 			goto literal;
96 
97 
98 again:		switch (c = *fmt++) {
99 		case '%':	/* "%%" is converted to "%". */
100 literal:
101 		if (c != *bp++)
102 			return (NULL);
103 
104 		break;
105 
106 		/*
107 		 * "Alternative" modifiers. Just set the appropriate flag
108 		 * and start over again.
109 		 */
110 		case 'E':	/* "%E?" alternative conversion modifier. */
111 			_LEGAL_ALT(0);
112 			alt_format |= _ALT_E;
113 			goto again;
114 
115 		case 'O':	/* "%O?" alternative conversion modifier. */
116 			_LEGAL_ALT(0);
117 			alt_format |= _ALT_O;
118 			goto again;
119 
120 		/*
121 		 * "Complex" conversion rules, implemented through recursion.
122 		 */
123 #if 0
124 		case 'c':	/* Date and time, using the locale's format. */
125 			_LEGAL_ALT(_ALT_E);
126 			if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, 0)))
127 				return (NULL);
128 			break;
129 #endif
130 		case 'D':	/* The date as "%m/%d/%y". */
131 			_LEGAL_ALT(0);
132 			if (!(bp = _strptime(bp, "%m/%d/%y", tm, 0)))
133 				return (NULL);
134 			break;
135 
136 		case 'R':	/* The time as "%H:%M". */
137 			_LEGAL_ALT(0);
138 			if (!(bp = _strptime(bp, "%H:%M", tm, 0)))
139 				return (NULL);
140 			break;
141 
142 		case 'r':	/* The time as "%I:%M:%S %p". */
143 			_LEGAL_ALT(0);
144 			if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, 0)))
145 				return (NULL);
146 			break;
147 
148 		case 'T':	/* The time as "%H:%M:%S". */
149 			_LEGAL_ALT(0);
150 			if (!(bp = _strptime(bp, "%H:%M:%S", tm, 0)))
151 				return (NULL);
152 			break;
153 #if 0
154 		case 'X':	/* The time, using the locale's format. */
155 			_LEGAL_ALT(_ALT_E);
156 			if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, 0)))
157 				return (NULL);
158 			break;
159 
160 		case 'x':	/* The date, using the locale's format. */
161 			_LEGAL_ALT(_ALT_E);
162 			if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, 0)))
163 				return (NULL);
164 			break;
165 #endif
166 		/*
167 		 * "Elementary" conversion rules.
168 		 */
169 #if 0
170 		case 'A':	/* The day of week, using the locale's form. */
171 		case 'a':
172 			_LEGAL_ALT(0);
173 			for (i = 0; i < 7; i++) {
174 				/* Full name. */
175 				len = strlen(_ctloc(day[i]));
176 				if (strncasecmp(_ctloc(day[i]), bp, len) == 0)
177 					break;
178 
179 				/* Abbreviated name. */
180 				len = strlen(_ctloc(abday[i]));
181 				if (strncasecmp(_ctloc(abday[i]), bp, len) == 0)
182 					break;
183 			}
184 
185 			/* Nothing matched. */
186 			if (i == 7)
187 				return (NULL);
188 
189 			tm->tm_wday = i;
190 			bp += len;
191 			break;
192 
193 		case 'B':	/* The month, using the locale's form. */
194 		case 'b':
195 		case 'h':
196 			_LEGAL_ALT(0);
197 			for (i = 0; i < 12; i++) {
198 				/* Full name. */
199 				len = strlen(_ctloc(mon[i]));
200 				if (strncasecmp(_ctloc(mon[i]), bp, len) == 0)
201 					break;
202 
203 				/* Abbreviated name. */
204 				len = strlen(_ctloc(abmon[i]));
205 				if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0)
206 					break;
207 			}
208 
209 			/* Nothing matched. */
210 			if (i == 12)
211 				return (NULL);
212 
213 			tm->tm_mon = i;
214 			bp += len;
215 			break;
216 #endif
217 
218 		case 'C':	/* The century number. */
219 			_LEGAL_ALT(_ALT_E);
220 			if (!(_conv_num(&bp, &i, 0, 99)))
221 				return (NULL);
222 
223 			century = i * 100;
224 			break;
225 
226 		case 'd':	/* The day of month. */
227 		case 'e':
228 			_LEGAL_ALT(_ALT_O);
229 			if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
230 				return (NULL);
231 			break;
232 
233 		case 'k':	/* The hour (24-hour clock representation). */
234 			_LEGAL_ALT(0);
235 			/* FALLTHROUGH */
236 		case 'H':
237 			_LEGAL_ALT(_ALT_O);
238 			if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
239 				return (NULL);
240 			break;
241 
242 		case 'l':	/* The hour (12-hour clock representation). */
243 			_LEGAL_ALT(0);
244 			/* FALLTHROUGH */
245 		case 'I':
246 			_LEGAL_ALT(_ALT_O);
247 			if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
248 				return (NULL);
249 			break;
250 
251 		case 'j':	/* The day of year. */
252 			_LEGAL_ALT(0);
253 			if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
254 				return (NULL);
255 			tm->tm_yday--;
256 			break;
257 
258 		case 'M':	/* The minute. */
259 			_LEGAL_ALT(_ALT_O);
260 			if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
261 				return (NULL);
262 			break;
263 
264 		case 'm':	/* The month. */
265 			_LEGAL_ALT(_ALT_O);
266 			if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
267 				return (NULL);
268 			tm->tm_mon--;
269 			break;
270 
271 #if 0
272 		case 'p':	/* The locale's equivalent of AM/PM. */
273 			_LEGAL_ALT(0);
274 			/* AM? */
275 			len = strlen(_ctloc(am_pm[0]));
276 			if (strncasecmp(_ctloc(am_pm[0]), bp, len) == 0) {
277 				if (tm->tm_hour > 12)	/* i.e., 13:00 AM ?! */
278 					return (NULL);
279 				else if (tm->tm_hour == 12)
280 					tm->tm_hour = 0;
281 
282 				bp += len;
283 				break;
284 			}
285 			/* PM? */
286 			len = strlen(_ctloc(am_pm[1]));
287 			if (strncasecmp(_ctloc(am_pm[1]), bp, len) == 0) {
288 				if (tm->tm_hour > 12)	/* i.e., 13:00 PM ?! */
289 					return (NULL);
290 				else if (tm->tm_hour < 12)
291 					tm->tm_hour += 12;
292 
293 				bp += len;
294 				break;
295 			}
296 
297 			/* Nothing matched. */
298 			return (NULL);
299 #endif
300 		case 'S':	/* The seconds. */
301 			_LEGAL_ALT(_ALT_O);
302 			if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
303 				return (NULL);
304 			break;
305 
306 		case 'U':	/* The week of year, beginning on sunday. */
307 		case 'W':	/* The week of year, beginning on monday. */
308 			_LEGAL_ALT(_ALT_O);
309 			/*
310 			 * XXX This is bogus, as we can not assume any valid
311 			 * information present in the tm structure at this
312 			 * point to calculate a real value, so just check the
313 			 * range for now.
314 			 */
315 			 if (!(_conv_num(&bp, &i, 0, 53)))
316 				return (NULL);
317 			 break;
318 
319 		case 'w':	/* The day of week, beginning on sunday. */
320 			_LEGAL_ALT(_ALT_O);
321 			if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
322 				return (NULL);
323 			break;
324 
325 		case 'Y':	/* The year. */
326 			_LEGAL_ALT(_ALT_E);
327 			if (!(_conv_num(&bp, &i, 0, 9999)))
328 				return (NULL);
329 
330 			relyear = -1;
331 			tm->tm_year = i - TM_YEAR_BASE;
332 			break;
333 
334 		case 'y':	/* The year within the century (2 digits). */
335 			_LEGAL_ALT(_ALT_E | _ALT_O);
336 			if (!(_conv_num(&bp, &relyear, 0, 99)))
337 				return (NULL);
338 			break;
339 
340 		/*
341 		 * Miscellaneous conversions.
342 		 */
343 		case 'n':	/* Any kind of white-space. */
344 		case 't':
345 			_LEGAL_ALT(0);
346 			while (isspace(*bp))
347 				bp++;
348 			break;
349 
350 
351 		default:	/* Unknown/unsupported conversion. */
352 			return (NULL);
353 		}
354 
355 
356 	}
357 
358 	/*
359 	 * We need to evaluate the two digit year spec (%y)
360 	 * last as we can get a century spec (%C) at any time.
361 	 */
362 	if (relyear != -1) {
363 		if (century == TM_YEAR_BASE) {
364 			if (relyear <= 68)
365 				tm->tm_year = relyear + 2000 - TM_YEAR_BASE;
366 			else
367 				tm->tm_year = relyear + 1900 - TM_YEAR_BASE;
368 		} else {
369 			tm->tm_year = relyear + century - TM_YEAR_BASE;
370 		}
371 	}
372 
373 	return ((char *)bp);
374 }
375 
376 
377 static int
_conv_num(const unsigned char ** buf,int * dest,int llim,int ulim)378 _conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
379 {
380 	int result = 0;
381 	int rulim = ulim;
382 
383 	if (**buf < '0' || **buf > '9')
384 		return (0);
385 
386 	/* we use rulim to break out of the loop when we run out of digits */
387 	do {
388 		result *= 10;
389 		result += *(*buf)++ - '0';
390 		rulim /= 10;
391 	} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
392 
393 	if (result < llim || result > ulim)
394 		return (0);
395 
396 	*dest = result;
397 	return (1);
398 }
399 
400 #endif /* HAVE_STRPTIME */
401 
402