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