import of openssh-5.8p1
[openssh.git] / openbsd-compat / strptime.c
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 *
61 strptime(const char *buf, const char *fmt, struct tm *tm)
62 {
63         return(_strptime(buf, fmt, tm, 1));
64 }
65
66 static char *
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
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