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