- proper type checking, thanks Sara
[m6w6/ext-http] / http_date_api.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2005, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 /* $Id$ */
14
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18
19 #include "php_http.h"
20 #include "php_http_api.h"
21 #include "php_http_date_api.h"
22
23 ZEND_EXTERN_MODULE_GLOBALS(http);
24
25 static inline int check_day(const char *day, size_t len);
26 static inline int check_month(const char *month);
27 static inline int check_tzone(const char *tzone);
28 static inline time_t parse_date(const char *month);
29
30 /* {{{ day/month names */
31 static const char *days[] = {
32 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
33 };
34 static const char *wkdays[] = {
35 "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
36 };
37 static const char *weekdays[] = {
38 "Monday", "Tuesday", "Wednesday",
39 "Thursday", "Friday", "Saturday", "Sunday"
40 };
41 static const char *months[] = {
42 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
43 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
44 };
45 enum assume_next {
46 DATE_MDAY,
47 DATE_YEAR,
48 DATE_TIME
49 };
50 #define DS -60
51 static const struct time_zone {
52 const char *name;
53 const int offset;
54 } time_zones[] = {
55 {"GMT", 0}, /* Greenwich Mean */
56 {"UTC", 0}, /* Universal (Coordinated) */
57 {"WET", 0}, /* Western European */
58 {"BST", 0 DS}, /* British Summer */
59 {"WAT", 60}, /* West Africa */
60 {"AST", 240}, /* Atlantic Standard */
61 {"ADT", 240 DS},/* Atlantic Daylight */
62 {"EST", 300}, /* Eastern Standard */
63 {"EDT", 300 DS},/* Eastern Daylight */
64 {"CST", 360}, /* Central Standard */
65 {"CDT", 360 DS},/* Central Daylight */
66 {"MST", 420}, /* Mountain Standard */
67 {"MDT", 420 DS},/* Mountain Daylight */
68 {"PST", 480}, /* Pacific Standard */
69 {"PDT", 480 DS},/* Pacific Daylight */
70 {"YST", 540}, /* Yukon Standard */
71 {"YDT", 540 DS},/* Yukon Daylight */
72 {"HST", 600}, /* Hawaii Standard */
73 {"HDT", 600 DS},/* Hawaii Daylight */
74 {"CAT", 600}, /* Central Alaska */
75 {"AHST", 600}, /* Alaska-Hawaii Standard */
76 {"NT", 660}, /* Nome */
77 {"IDLW", 720}, /* International Date Line West */
78 {"CET", -60}, /* Central European */
79 {"MET", -60}, /* Middle European */
80 {"MEWT", -60}, /* Middle European Winter */
81 {"MEST", -60 DS},/* Middle European Summer */
82 {"CEST", -60 DS},/* Central European Summer */
83 {"MESZ", -60 DS},/* Middle European Summer */
84 {"FWT", -60}, /* French Winter */
85 {"FST", -60 DS},/* French Summer */
86 {"EET", -120}, /* Eastern Europe, USSR Zone 1 */
87 {"WAST", -420}, /* West Australian Standard */
88 {"WADT", -420 DS},/* West Australian Daylight */
89 {"CCT", -480}, /* China Coast, USSR Zone 7 */
90 {"JST", -540}, /* Japan Standard, USSR Zone 8 */
91 {"EAST", -600}, /* Eastern Australian Standard */
92 {"EADT", -600 DS},/* Eastern Australian Daylight */
93 {"GST", -600}, /* Guam Standard, USSR Zone 9 */
94 {"NZT", -720}, /* New Zealand */
95 {"NZST", -720}, /* New Zealand Standard */
96 {"NZDT", -720 DS},/* New Zealand Daylight */
97 {"IDLE", -720}, /* International Date Line East */
98 };
99 /* }}} */
100
101 /* {{{ Day/Month/TZ checks for http_parse_date()
102 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
103 static inline int check_day(const char *day, size_t len)
104 {
105 int i;
106 const char * const *check = (len > 3) ? &weekdays[0] : &wkdays[0];
107 for (i = 0; i < 7; i++) {
108 if (!strcmp(day, check[0])) {
109 return i;
110 }
111 check++;
112 }
113 return -1;
114 }
115
116 static inline int check_month(const char *month)
117 {
118 int i;
119 const char * const *check = &months[0];
120 for (i = 0; i < 12; i++) {
121 if (!strcmp(month, check[0])) {
122 return i;
123 }
124 check++;
125 }
126 return -1;
127 }
128
129 /* return the time zone offset between GMT and the input one, in number
130 of seconds or -1 if the timezone wasn't found/legal */
131
132 static inline int check_tzone(const char *tzone)
133 {
134 unsigned i;
135 const struct time_zone *check = time_zones;
136 for (i = 0; i < sizeof(time_zones) / sizeof(time_zones[0]); i++) {
137 if (!strcmp(tzone, check->name)) {
138 return check->offset * 60;
139 }
140 check++;
141 }
142 return -1;
143 }
144 /* }}} */
145
146 /* {{{ char *http_date(time_t) */
147 PHP_HTTP_API char *_http_date(time_t t TSRMLS_DC)
148 {
149 struct tm *gmtime, tmbuf;
150
151 if ((gmtime = php_gmtime_r(&t, &tmbuf))) {
152 char *date = ecalloc(1, 31);
153 snprintf(date, 30,
154 "%s, %02d %s %04d %02d:%02d:%02d GMT",
155 days[gmtime->tm_wday], gmtime->tm_mday,
156 months[gmtime->tm_mon], gmtime->tm_year + 1900,
157 gmtime->tm_hour, gmtime->tm_min, gmtime->tm_sec
158 );
159 return date;
160 }
161
162 return NULL;
163 }
164 /* }}} */
165
166 /* {{{ time_t http_parse_date(char *) */
167 PHP_HTTP_API time_t _http_parse_date(const char *date TSRMLS_DC)
168 {
169 time_t t = -1;
170
171 #ifdef PHP_WIN32
172 /* fix odd offsets with Win32 */
173 char tzput[64] = "TZ=";
174 const char *tzget = NULL;
175
176 if ((tzget = getenv("TZ"))) {
177 strlcat(tzput, tzget, 63);
178 }
179 putenv("TZ=GMT");
180 #endif
181
182 t = parse_date(date);
183
184 #ifdef PHP_WIN32
185 putenv(tzput);
186 #endif
187
188 if (-1 == t) {
189 http_error_ex(HE_NOTICE, HTTP_E_RUNTIME, "Could not parse date: %s", date);
190 }
191
192 return t;
193 }
194 /* }}} */
195
196 /* time_t parse_date(char *)
197 Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
198 static inline time_t parse_date(const char *date)
199 {
200 time_t t = 0;
201 int tz_offset = -1, year = -1, month = -1, monthday = -1, weekday = -1,
202 hours = -1, minutes = -1, seconds = -1;
203 struct tm tm;
204 enum assume_next dignext = DATE_MDAY;
205 const char *indate = date;
206
207 int part = 0; /* max 6 parts */
208
209 while (*date && (part < 6)) {
210 int found = 0;
211
212 while (*date && !isalnum(*date)) {
213 date++;
214 }
215
216 if (isalpha(*date)) {
217 /* a name coming up */
218 char buf[32] = "";
219 size_t len;
220 sscanf(date, "%31[A-Za-z]", buf);
221 len = strlen(buf);
222
223 if (weekday == -1) {
224 weekday = check_day(buf, len);
225 if (weekday != -1) {
226 found = 1;
227 }
228 }
229
230 if (!found && (month == -1)) {
231 month = check_month(buf);
232 if (month != -1) {
233 found = 1;
234 }
235 }
236
237 if (!found && (tz_offset == -1)) {
238 /* this just must be a time zone string */
239 tz_offset = check_tzone(buf);
240 if (tz_offset != -1) {
241 found = 1;
242 }
243 }
244
245 if (!found) {
246 return -1; /* bad string */
247 }
248 date += len;
249 }
250 else if (isdigit(*date)) {
251 /* a digit */
252 int val;
253 char *end;
254 if ((seconds == -1) &&
255 (3 == sscanf(date, "%02d:%02d:%02d", &hours, &minutes, &seconds))) {
256 /* time stamp! */
257 date += 8;
258 found = 1;
259 }
260 else {
261 val = (int) strtol(date, &end, 10);
262
263 if ((tz_offset == -1) && ((end - date) == 4) && (val < 1300) &&
264 (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) {
265 /* four digits and a value less than 1300 and it is preceeded with
266 a plus or minus. This is a time zone indication. */
267 found = 1;
268 tz_offset = (val / 100 * 60 + val % 100) * 60;
269
270 /* the + and - prefix indicates the local time compared to GMT,
271 this we need ther reversed math to get what we want */
272 tz_offset = date[-1] == '+' ? -tz_offset : tz_offset;
273 }
274
275 if (((end - date) == 8) && (year == -1) && (month == -1) && (monthday == -1)) {
276 /* 8 digits, no year, month or day yet. This is YYYYMMDD */
277 found = 1;
278 year = val / 10000;
279 month = (val % 10000) / 100 - 1; /* month is 0 - 11 */
280 monthday = val % 100;
281 }
282
283 if (!found && (dignext == DATE_MDAY) && (monthday == -1)) {
284 if ((val > 0) && (val < 32)) {
285 monthday = val;
286 found = 1;
287 }
288 dignext = DATE_YEAR;
289 }
290
291 if (!found && (dignext == DATE_YEAR) && (year == -1)) {
292 year = val;
293 found = 1;
294 if (year < 1900) {
295 year += year > 70 ? 1900 : 2000;
296 }
297 if(monthday == -1) {
298 dignext = DATE_MDAY;
299 }
300 }
301
302 if (!found) {
303 return -1;
304 }
305
306 date = end;
307 }
308 }
309
310 part++;
311 }
312
313 if (-1 == seconds) {
314 seconds = minutes = hours = 0; /* no time, make it zero */
315 }
316
317 if ((-1 == monthday) || (-1 == month) || (-1 == year)) {
318 /* lacks vital info, fail */
319 return -1;
320 }
321
322 if (sizeof(time_t) < 5) {
323 /* 32 bit time_t can only hold dates to the beginning of 2038 */
324 if (year > 2037) {
325 return 0x7fffffff;
326 }
327 }
328
329 tm.tm_sec = seconds;
330 tm.tm_min = minutes;
331 tm.tm_hour = hours;
332 tm.tm_mday = monthday;
333 tm.tm_mon = month;
334 tm.tm_year = year - 1900;
335 tm.tm_wday = 0;
336 tm.tm_yday = 0;
337 tm.tm_isdst = 0;
338
339 t = mktime(&tm);
340
341 /* time zone adjust */
342 if (t != -1) {
343 struct tm *gmt, keeptime2;
344 long delta;
345 time_t t2;
346
347 if(!(gmt = php_gmtime_r(&t, &keeptime2))) {
348 return -1; /* illegal date/time */
349 }
350
351 t2 = mktime(gmt);
352
353 /* Add the time zone diff (between the given timezone and GMT) and the
354 diff between the local time zone and GMT. */
355 delta = (tz_offset != -1 ? tz_offset : 0) + (t - t2);
356
357 if((delta > 0) && (t + delta < t)) {
358 return -1; /* time_t overflow */
359 }
360
361 t += delta;
362 }
363
364 return t;
365 }
366 /* }}} */
367
368
369 /*
370 * Local variables:
371 * tab-width: 4
372 * c-basic-offset: 4
373 * End:
374 * vim600: sw=4 ts=4 fdm=marker
375 * vim<600: sw=4 ts=4
376 */
377