1/*	$NetBSD: strptime.c,v 1.4 2006/10/26 07:24:14 lukem Exp $	*/
2/*	from	NetBSD: strptime.c,v 1.18 1999/04/29 02:58:30 tv 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 * 3. All advertising materials mentioning features or use of this software
19 *    must display the following acknowledgement:
20 *        This product includes software developed by the NetBSD
21 *        Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 *    contributors may be used to endorse or promote products derived
24 *    from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39#include "tnftp.h"
40
41/*
42 * We do not implement alternate representations. However, we always
43 * check whether a given modifier is allowed for a certain conversion.
44 */
45#define ALT_E			0x01
46#define ALT_O			0x02
47#define	LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
48
49
50static	int conv_num(const char **, int *, int, int);
51
52static const char *day[7] = {
53	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
54	"Friday", "Saturday"
55};
56static const char *abday[7] = {
57	"Sun","Mon","Tue","Wed","Thu","Fri","Sat"
58};
59static const char *mon[12] = {
60	"January", "February", "March", "April", "May", "June", "July",
61	"August", "September", "October", "November", "December"
62};
63static const char *abmon[12] = {
64	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
65	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
66};
67static const char *am_pm[2] = {
68	"AM", "PM"
69};
70
71
72char *
73strptime(const char *buf, const char *fmt, struct tm *tm)
74{
75	char c;
76	const char *bp;
77	size_t len = 0;
78	int alt_format, i, split_year = 0;
79
80	bp = buf;
81
82	while ((c = *fmt) != '\0') {
83		/* Clear `alternate' modifier prior to new conversion. */
84		alt_format = 0;
85
86		/* Eat up white-space. */
87		if (isspace(c)) {
88			while (isspace(*bp))
89				bp++;
90
91			fmt++;
92			continue;
93		}
94
95		if ((c = *fmt++) != '%')
96			goto literal;
97
98
99again:		switch (c = *fmt++) {
100		case '%':	/* "%%" is converted to "%". */
101literal:
102			if (c != *bp++)
103				return (0);
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		case 'c':	/* Date and time, using the locale's format. */
124			LEGAL_ALT(ALT_E);
125			if (!(bp = strptime(bp, "%x %X", tm)))
126				return (0);
127			break;
128
129		case 'D':	/* The date as "%m/%d/%y". */
130			LEGAL_ALT(0);
131			if (!(bp = strptime(bp, "%m/%d/%y", tm)))
132				return (0);
133			break;
134
135		case 'R':	/* The time as "%H:%M". */
136			LEGAL_ALT(0);
137			if (!(bp = strptime(bp, "%H:%M", tm)))
138				return (0);
139			break;
140
141		case 'r':	/* The time in 12-hour clock representation. */
142			LEGAL_ALT(0);
143			if (!(bp = strptime(bp, "%I:%M:%S %p", tm)))
144				return (0);
145			break;
146
147		case 'T':	/* The time as "%H:%M:%S". */
148			LEGAL_ALT(0);
149			if (!(bp = strptime(bp, "%H:%M:%S", tm)))
150				return (0);
151			break;
152
153		case 'X':	/* The time, using the locale's format. */
154			LEGAL_ALT(ALT_E);
155			if (!(bp = strptime(bp, "%H:%M:%S", tm)))
156				return (0);
157			break;
158
159		case 'x':	/* The date, using the locale's format. */
160			LEGAL_ALT(ALT_E);
161			if (!(bp = strptime(bp, "%m/%d/%y", tm)))
162				return (0);
163			break;
164
165		/*
166		 * "Elementary" conversion rules.
167		 */
168		case 'A':	/* The day of week, using the locale's form. */
169		case 'a':
170			LEGAL_ALT(0);
171			for (i = 0; i < 7; i++) {
172				/* Full name. */
173				len = strlen(day[i]);
174				if (strncasecmp(day[i], bp, len) == 0)
175					break;
176
177				/* Abbreviated name. */
178				len = strlen(abday[i]);
179				if (strncasecmp(abday[i], bp, len) == 0)
180					break;
181			}
182
183			/* Nothing matched. */
184			if (i == 7)
185				return (0);
186
187			tm->tm_wday = i;
188			bp += len;
189			break;
190
191		case 'B':	/* The month, using the locale's form. */
192		case 'b':
193		case 'h':
194			LEGAL_ALT(0);
195			for (i = 0; i < 12; i++) {
196				/* Full name. */
197				len = strlen(mon[i]);
198				if (strncasecmp(mon[i], bp, len) == 0)
199					break;
200
201				/* Abbreviated name. */
202				len = strlen(abmon[i]);
203				if (strncasecmp(abmon[i], bp, len) == 0)
204					break;
205			}
206
207			/* Nothing matched. */
208			if (i == 12)
209				return (0);
210
211			tm->tm_mon = i;
212			bp += len;
213			break;
214
215		case 'C':	/* The century number. */
216			LEGAL_ALT(ALT_E);
217			if (!(conv_num(&bp, &i, 0, 99)))
218				return (0);
219
220			if (split_year) {
221				tm->tm_year = (tm->tm_year % 100) + (i * 100);
222			} else {
223				tm->tm_year = i * 100;
224				split_year = 1;
225			}
226			break;
227
228		case 'd':	/* The day of month. */
229		case 'e':
230			LEGAL_ALT(ALT_O);
231			if (!(conv_num(&bp, &tm->tm_mday, 1, 31)))
232				return (0);
233			break;
234
235		case 'k':	/* The hour (24-hour clock representation). */
236			LEGAL_ALT(0);
237			/* FALLTHROUGH */
238		case 'H':
239			LEGAL_ALT(ALT_O);
240			if (!(conv_num(&bp, &tm->tm_hour, 0, 23)))
241				return (0);
242			break;
243
244		case 'l':	/* The hour (12-hour clock representation). */
245			LEGAL_ALT(0);
246			/* FALLTHROUGH */
247		case 'I':
248			LEGAL_ALT(ALT_O);
249			if (!(conv_num(&bp, &tm->tm_hour, 1, 12)))
250				return (0);
251			if (tm->tm_hour == 12)
252				tm->tm_hour = 0;
253			break;
254
255		case 'j':	/* The day of year. */
256			LEGAL_ALT(0);
257			if (!(conv_num(&bp, &i, 1, 366)))
258				return (0);
259			tm->tm_yday = i - 1;
260			break;
261
262		case 'M':	/* The minute. */
263			LEGAL_ALT(ALT_O);
264			if (!(conv_num(&bp, &tm->tm_min, 0, 59)))
265				return (0);
266			break;
267
268		case 'm':	/* The month. */
269			LEGAL_ALT(ALT_O);
270			if (!(conv_num(&bp, &i, 1, 12)))
271				return (0);
272			tm->tm_mon = i - 1;
273			break;
274
275		case 'p':	/* The locale's equivalent of AM/PM. */
276			LEGAL_ALT(0);
277			/* AM? */
278			len = strlen(am_pm[0]);
279			if (strncasecmp(am_pm[0], bp, len) == 0) {
280				if (tm->tm_hour > 11)
281					return (0);
282
283				bp += len;
284				break;
285			}
286
287			/* PM? */
288			len = strlen(am_pm[1]);
289			if (strncasecmp(am_pm[1], bp, len) == 0) {
290				if (tm->tm_hour > 11)
291					return (0);
292
293				tm->tm_hour += 12;
294				bp += len;
295				break;
296			}
297
298			/* Nothing matched. */
299			return (0);
300
301		case 'S':	/* The seconds. */
302			LEGAL_ALT(ALT_O);
303			if (!(conv_num(&bp, &tm->tm_sec, 0, 61)))
304				return (0);
305			break;
306
307		case 'U':	/* The week of year, beginning on sunday. */
308		case 'W':	/* The week of year, beginning on monday. */
309			LEGAL_ALT(ALT_O);
310			/*
311			 * XXX This is bogus, as we can not assume any valid
312			 * information present in the tm structure at this
313			 * point to calculate a real value, so just check the
314			 * range for now.
315			 */
316			 if (!(conv_num(&bp, &i, 0, 53)))
317				return (0);
318			 break;
319
320		case 'w':	/* The day of week, beginning on sunday. */
321			LEGAL_ALT(ALT_O);
322			if (!(conv_num(&bp, &tm->tm_wday, 0, 6)))
323				return (0);
324			break;
325
326		case 'Y':	/* The year. */
327			LEGAL_ALT(ALT_E);
328			if (!(conv_num(&bp, &i, 0, 9999)))
329				return (0);
330
331			tm->tm_year = i - TM_YEAR_BASE;
332			break;
333
334		case 'y':	/* The year within 100 years of the epoch. */
335			LEGAL_ALT(ALT_E | ALT_O);
336			if (!(conv_num(&bp, &i, 0, 99)))
337				return (0);
338
339			if (split_year) {
340				tm->tm_year = ((tm->tm_year / 100) * 100) + i;
341				break;
342			}
343			split_year = 1;
344			if (i <= 68)
345				tm->tm_year = i + 2000 - TM_YEAR_BASE;
346			else
347				tm->tm_year = i + 1900 - TM_YEAR_BASE;
348			break;
349
350		/*
351		 * Miscellaneous conversions.
352		 */
353		case 'n':	/* Any kind of white-space. */
354		case 't':
355			LEGAL_ALT(0);
356			while (isspace(*bp))
357				bp++;
358			break;
359
360
361		default:	/* Unknown/unsupported conversion. */
362			return (0);
363		}
364
365
366	}
367
368	/* LINTED functional specification */
369	return ((char *)bp);
370}
371
372
373static int
374conv_num(const char **buf, int *dest, int llim, int ulim)
375{
376	int result = 0;
377
378	/* The limit also determines the number of valid digits. */
379	int rulim = ulim;
380
381	if (**buf < '0' || **buf > '9')
382		return (0);
383
384	do {
385		result *= 10;
386		result += *(*buf)++ - '0';
387		rulim /= 10;
388	} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
389
390	if (result < llim || result > ulim)
391		return (0);
392
393	*dest = result;
394	return (1);
395}
396