1 /* tdate_parse - parse string dates into internal form, stripped-down version
2 **
3 ** Copyright (C) 1995 by Jef Poskanzer <[email protected]>.  All rights reserved.
4 **
5 ** Redistribution and use in source and binary forms, with or without
6 ** modification, are permitted provided that the following conditions
7 ** are met:
8 ** 1. Redistributions of source code must retain the above copyright
9 **    notice, this list of conditions and the following disclaimer.
10 ** 2. Redistributions in binary form must reproduce the above copyright
11 **    notice, this list of conditions and the following disclaimer in the
12 **    documentation and/or other materials provided with the distribution.
13 **
14 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 ** SUCH DAMAGE.
25 */
26 
27 /* This is a stripped-down version of date_parse.c, available at
28 ** http://www.acme.com/software/date_parse/
29 */
30 
31 #include <sys/types.h>
32 
33 #include <ctype.h>
34 #ifdef HAVE_MEMORY_H
35 #include <memory.h>
36 #endif
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <time.h>
41 
42 #include "tdate_parse.h"
43 
44 
45 struct strlong {
46     char* s;
47     long l;
48     };
49 
50 
51 static void
pound_case(char * str)52 pound_case( char* str )
53     {
54     for ( ; *str != '\0'; ++str )
55 	{
56 	if ( isupper( *str ) )
57 	    *str = tolower( *str );
58 	}
59     }
60 
61 static int
strlong_compare(v1,v2)62 strlong_compare( v1, v2 )
63     char* v1;
64     char* v2;
65     {
66     return strcmp( ((struct strlong*) v1)->s, ((struct strlong*) v2)->s );
67     }
68 
69 
70 static int
strlong_search(char * str,struct strlong * tab,int n,long * lP)71 strlong_search( char* str, struct strlong* tab, int n, long* lP )
72     {
73     int i, h, l, r;
74 
75     l = 0;
76     h = n - 1;
77     for (;;)
78 	{
79 	i = ( h + l ) / 2;
80 	r = strcmp( str, tab[i].s );
81 	if ( r < 0 )
82 	    h = i - 1;
83 	else if ( r > 0 )
84 	    l = i + 1;
85 	else
86 	    {
87 	    *lP = tab[i].l;
88 	    return 1;
89 	    }
90 	if ( h < l )
91 	    return 0;
92 	}
93     }
94 
95 
96 static int
scan_wday(char * str_wday,long * tm_wdayP)97 scan_wday( char* str_wday, long* tm_wdayP )
98     {
99     static struct strlong wday_tab[] = {
100 	{ "sun", 0 }, { "sunday", 0 },
101 	{ "mon", 1 }, { "monday", 1 },
102 	{ "tue", 2 }, { "tuesday", 2 },
103 	{ "wed", 3 }, { "wednesday", 3 },
104 	{ "thu", 4 }, { "thursday", 4 },
105 	{ "fri", 5 }, { "friday", 5 },
106 	{ "sat", 6 }, { "saturday", 6 },
107 	};
108     static int sorted = 0;
109 
110     if ( ! sorted )
111 	{
112 	(void) qsort(
113 	    wday_tab, sizeof(wday_tab)/sizeof(struct strlong),
114 	    sizeof(struct strlong), strlong_compare );
115 	sorted = 1;
116 	}
117     pound_case( str_wday );
118     return strlong_search(
119 	str_wday, wday_tab, sizeof(wday_tab)/sizeof(struct strlong), tm_wdayP );
120     }
121 
122 
123 static int
scan_mon(char * str_mon,long * tm_monP)124 scan_mon( char* str_mon, long* tm_monP )
125     {
126     static struct strlong mon_tab[] = {
127 	{ "jan", 0 }, { "january", 0 },
128 	{ "feb", 1 }, { "february", 1 },
129 	{ "mar", 2 }, { "march", 2 },
130 	{ "apr", 3 }, { "april", 3 },
131 	{ "may", 4 },
132 	{ "jun", 5 }, { "june", 5 },
133 	{ "jul", 6 }, { "july", 6 },
134 	{ "aug", 7 }, { "august", 7 },
135 	{ "sep", 8 }, { "september", 8 },
136 	{ "oct", 9 }, { "october", 9 },
137 	{ "nov", 10 }, { "november", 10 },
138 	{ "dec", 11 }, { "december", 11 },
139 	};
140     static int sorted = 0;
141 
142     if ( ! sorted )
143 	{
144 	(void) qsort(
145 	    mon_tab, sizeof(mon_tab)/sizeof(struct strlong),
146 	    sizeof(struct strlong), strlong_compare );
147 	sorted = 1;
148 	}
149     pound_case( str_mon );
150     return strlong_search(
151 	str_mon, mon_tab, sizeof(mon_tab)/sizeof(struct strlong), tm_monP );
152     }
153 
154 
155 static int
is_leap(int year)156 is_leap( int year )
157     {
158     return year % 400? ( year % 100 ? ( year % 4 ? 0 : 1 ) : 0 ) : 1;
159     }
160 
161 
162 /* Basically the same as mktime(). */
163 static time_t
tm_to_time(struct tm * tmP)164 tm_to_time( struct tm* tmP )
165     {
166     time_t t;
167     static int monthtab[12] = {
168 	0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
169 
170     /* Years since epoch, converted to days. */
171     t = ( tmP->tm_year - 70 ) * 365;
172     /* Leap days for previous years. */
173     t += ( tmP->tm_year - 1 - 68 ) / 4; /* -1: don't count this year */
174     /* 100-divisible year is not a leap year
175        400-divisible year is a leap year */
176     if (tmP->tm_year > 200)
177       t -= (tmP->tm_year - 1 - 100) / 100;
178     if (tmP->tm_year > 500)
179       t += (tmP->tm_year - 1 - 100) / 400;
180 
181     /* Days for the beginning of this month. */
182     t += monthtab[tmP->tm_mon];
183     /* Leap day for this year. */
184     if ( tmP->tm_mon >= 2 && is_leap( tmP->tm_year ) )
185 	++t;
186     /* Days since the beginning of this month. */
187     t += tmP->tm_mday - 1;	/* 1-based field */
188     /* Hours, minutes, and seconds. */
189     t = t * 24 + tmP->tm_hour;
190     t = t * 60 + tmP->tm_min;
191     t = t * 60 + tmP->tm_sec;
192 
193     return t;
194     }
195 
196 
197 time_t
httpdate_to_timet(const char * str)198 httpdate_to_timet( const char* str )
199     {
200     struct tm tm;
201     const char* cp;
202     char str_mon[500], str_wday[500];
203     int tm_sec, tm_min, tm_hour, tm_mday, tm_year;
204     long tm_mon, tm_wday;
205     time_t t;
206 
207     /* Initialize. */
208     memset( (char*) &tm, 0, sizeof(struct tm) );
209 
210     /* Skip initial whitespace ourselves - sscanf is clumsy at this. */
211     for ( cp = str; *cp == ' ' || *cp == '\t'; ++cp )
212 	;
213 
214     /* And do the sscanfs.  WARNING: you can add more formats here,
215     ** but be careful!  You can easily screw up the parsing of existing
216     ** formats when you add new ones.  The order is important.
217     */
218 
219     /* DD-mth-YY HH:MM:SS GMT */
220     if ( sscanf( cp, "%d-%[a-zA-Z]-%d %d:%d:%d GMT",
221 		&tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
222 		&tm_sec ) == 6 &&
223 	    scan_mon( str_mon, &tm_mon ) )
224 	{
225 	tm.tm_mday = tm_mday;
226 	tm.tm_mon = tm_mon;
227 	tm.tm_year = tm_year;
228 	tm.tm_hour = tm_hour;
229 	tm.tm_min = tm_min;
230 	tm.tm_sec = tm_sec;
231 	}
232 
233     /* DD mth YY HH:MM:SS GMT */
234     else if ( sscanf( cp, "%d %[a-zA-Z] %d %d:%d:%d GMT",
235 		&tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
236 		&tm_sec) == 6 &&
237 	    scan_mon( str_mon, &tm_mon ) )
238 	{
239 	tm.tm_mday = tm_mday;
240 	tm.tm_mon = tm_mon;
241 	tm.tm_year = tm_year;
242 	tm.tm_hour = tm_hour;
243 	tm.tm_min = tm_min;
244 	tm.tm_sec = tm_sec;
245 	}
246 
247     /* HH:MM:SS GMT DD-mth-YY */
248     else if ( sscanf( cp, "%d:%d:%d GMT %d-%[a-zA-Z]-%d",
249 		&tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
250 		&tm_year ) == 6 &&
251 	    scan_mon( str_mon, &tm_mon ) )
252 	{
253 	tm.tm_hour = tm_hour;
254 	tm.tm_min = tm_min;
255 	tm.tm_sec = tm_sec;
256 	tm.tm_mday = tm_mday;
257 	tm.tm_mon = tm_mon;
258 	tm.tm_year = tm_year;
259 	}
260 
261     /* HH:MM:SS GMT DD mth YY */
262     else if ( sscanf( cp, "%d:%d:%d GMT %d %[a-zA-Z] %d",
263 		&tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
264 		&tm_year ) == 6 &&
265 	    scan_mon( str_mon, &tm_mon ) )
266 	{
267 	tm.tm_hour = tm_hour;
268 	tm.tm_min = tm_min;
269 	tm.tm_sec = tm_sec;
270 	tm.tm_mday = tm_mday;
271 	tm.tm_mon = tm_mon;
272 	tm.tm_year = tm_year;
273 	}
274 
275     /* wdy, DD-mth-YY HH:MM:SS GMT */
276     else if ( sscanf( cp, "%[a-zA-Z], %d-%[a-zA-Z]-%d %d:%d:%d GMT",
277 		str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
278 		&tm_sec ) == 7 &&
279 	    scan_wday( str_wday, &tm_wday ) &&
280 	    scan_mon( str_mon, &tm_mon ) )
281 	{
282 	tm.tm_wday = tm_wday;
283 	tm.tm_mday = tm_mday;
284 	tm.tm_mon = tm_mon;
285 	tm.tm_year = tm_year;
286 	tm.tm_hour = tm_hour;
287 	tm.tm_min = tm_min;
288 	tm.tm_sec = tm_sec;
289 	}
290 
291     /* wdy, DD mth YY HH:MM:SS GMT */
292     else if ( sscanf( cp, "%[a-zA-Z], %d %[a-zA-Z] %d %d:%d:%d GMT",
293 		str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
294 		&tm_sec ) == 7 &&
295 	    scan_wday( str_wday, &tm_wday ) &&
296 	    scan_mon( str_mon, &tm_mon ) )
297 	{
298 	tm.tm_wday = tm_wday;
299 	tm.tm_mday = tm_mday;
300 	tm.tm_mon = tm_mon;
301 	tm.tm_year = tm_year;
302 	tm.tm_hour = tm_hour;
303 	tm.tm_min = tm_min;
304 	tm.tm_sec = tm_sec;
305 	}
306 
307     /* wdy mth DD HH:MM:SS GMT YY */
308     else if ( sscanf( cp, "%[a-zA-Z] %[a-zA-Z] %d %d:%d:%d GMT %d",
309 		str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec,
310 		&tm_year ) == 7 &&
311 	    scan_wday( str_wday, &tm_wday ) &&
312 	    scan_mon( str_mon, &tm_mon ) )
313 	{
314 	tm.tm_wday = tm_wday;
315 	tm.tm_mon = tm_mon;
316 	tm.tm_mday = tm_mday;
317 	tm.tm_hour = tm_hour;
318 	tm.tm_min = tm_min;
319 	tm.tm_sec = tm_sec;
320 	tm.tm_year = tm_year;
321 	}
322     else
323 	return (time_t) -1;
324 
325     if ( tm.tm_year > 1900 )
326 	tm.tm_year -= 1900;
327     else if ( tm.tm_year < 70 )
328 	tm.tm_year += 100;
329 
330     t = tm_to_time( &tm );
331 
332     return t;
333 }
334 
335 /*
336    Convert 't' (in time_t format) into the HTTP date format
337    <input parameters>
338    t:      input (epoch-based time)
339    str:    output string that holds the HTTP date strinng
340    strlen: the buffer size of str
341 
342    <return value>
343     0 : in case of successful conversion
344    -1 : otherwise
345                                         by KyoungSoo Park
346 */
347 int
timet_to_httpdate(time_t t,char * str,int strlen)348 timet_to_httpdate(time_t t, char* str, int strlen )
349 {
350   static const char* day_of_week[] = {"Sun", "Mon","Tue",
351 									  "Wed", "Thu", "Fri", "Sat"};
352 
353   static const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
354 								 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
355   struct tm gm;
356 
357   if (gmtime_r(&t, &gm) == NULL)
358 	return(-1);
359 
360   /* example date: "Sat, 26 Mar 2011 05:53:57 GMT" */
361   if (snprintf(str, strlen,
362 			   "%s, %02d %s %4d %02d:%02d:%02d GMT",
363 			   day_of_week[gm.tm_wday],
364 			   gm.tm_mday,
365 			   months[gm.tm_mon],
366 			   gm.tm_year + 1900,
367 			   gm.tm_hour,
368 			   gm.tm_min,
369 			   gm.tm_sec) == strlen)
370 	/* probably str has an insufficient buffer size */
371 	return (-1);
372   return(0);
373 }
374