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