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