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-2007, Michael Wallner <mike@php.net> |
10 : +--------------------------------------------------------------------+
11 : */
12 :
13 : /* $Id: http_date_api.c,v 1.17 2007/02/07 11:50:26 mike Exp $ */
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 5 : {
100 : int i;
101 5 : const char * const *check = (len > 3) ? &weekdays[0] : &wkdays[0];
102 30 : for (i = 0; i < 7; i++) {
103 29 : if (!strcmp(day, check[0])) {
104 4 : return i;
105 : }
106 25 : check++;
107 : }
108 1 : return -1;
109 : }
110 :
111 : static inline int check_month(const char *month)
112 5 : {
113 : int i;
114 5 : const char * const *check = &months[0];
115 17 : for (i = 0; i < 12; i++) {
116 16 : if (!strcmp(month, check[0])) {
117 4 : return i;
118 : }
119 12 : check++;
120 : }
121 1 : 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 5 : {
129 : unsigned i;
130 5 : const struct time_zone *check = time_zones;
131 48 : for (i = 0; i < sizeof(time_zones) / sizeof(time_zones[0]); i++) {
132 47 : if (!strcmp(tzone, check->name)) {
133 4 : return check->offset * 60;
134 : }
135 43 : check++;
136 : }
137 1 : return -1;
138 : }
139 : /* }}} */
140 :
141 : /* {{{ char *http_date(time_t) */
142 : PHP_HTTP_API char *_http_date(time_t t TSRMLS_DC)
143 9 : {
144 9 : char *date = NULL;
145 : struct tm *gmtime, tmbuf;
146 :
147 9 : if ((gmtime = php_gmtime_r(&t, &tmbuf))) {
148 9 : spprintf(&date, 0,
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 : }
155 :
156 9 : return date;
157 : }
158 : /* }}} */
159 :
160 : /* {{{ time_t http_parse_date(char *) */
161 : PHP_HTTP_API time_t _http_parse_date_ex(const char *date, zend_bool silent TSRMLS_DC)
162 5 : {
163 5 : time_t t = parse_date(date);
164 :
165 5 : if (-1 == t && !silent) {
166 0 : http_error_ex(HE_NOTICE, HTTP_E_RUNTIME, "Could not parse date: %s", date);
167 : }
168 :
169 5 : return t;
170 : }
171 : /* }}} */
172 :
173 : /* time_t parse_date(char *)
174 : Originally by libcurl, Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. */
175 : static inline time_t parse_date(const char *date)
176 5 : {
177 5 : time_t t = 0;
178 5 : int tz_offset = -1, year = -1, month = -1, monthday = -1, weekday = -1,
179 5 : hours = -1, minutes = -1, seconds = -1;
180 : struct tm tm;
181 5 : enum assume_next dignext = DATE_MDAY;
182 5 : const char *indate = date;
183 :
184 5 : int part = 0; /* max 6 parts */
185 :
186 34 : while (*date && (part < 6)) {
187 25 : int found = 0;
188 :
189 75 : while (*date && !HTTP_IS_CTYPE(alnum, *date)) {
190 25 : date++;
191 : }
192 :
193 25 : if (HTTP_IS_CTYPE(alpha, *date)) {
194 : /* a name coming up */
195 13 : char buf[32] = "";
196 : size_t len;
197 13 : sscanf(date, "%31[A-Za-z]", buf);
198 13 : len = strlen(buf);
199 :
200 13 : if (weekday == -1) {
201 5 : weekday = check_day(buf, len);
202 5 : if (weekday != -1) {
203 4 : found = 1;
204 : }
205 : }
206 :
207 13 : if (!found && (month == -1)) {
208 5 : month = check_month(buf);
209 5 : if (month != -1) {
210 4 : found = 1;
211 : }
212 : }
213 :
214 13 : if (!found && (tz_offset == -1)) {
215 : /* this just must be a time zone string */
216 5 : tz_offset = check_tzone(buf);
217 5 : if (tz_offset != -1) {
218 4 : found = 1;
219 : }
220 : }
221 :
222 13 : if (!found) {
223 1 : return -1; /* bad string */
224 : }
225 12 : date += len;
226 : }
227 12 : else if (HTTP_IS_CTYPE(digit, *date)) {
228 : /* a digit */
229 : int val;
230 : char *end;
231 16 : if ((seconds == -1) &&
232 : (3 == sscanf(date, "%02d:%02d:%02d", &hours, &minutes, &seconds))) {
233 : /* time stamp! */
234 4 : date += 8;
235 4 : found = 1;
236 : }
237 : else {
238 8 : val = (int) strtol(date, &end, 10);
239 :
240 8 : if ((tz_offset == -1) && ((end - date) == 4) && (val < 1300) &&
241 : (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) {
242 : /* four digits and a value less than 1300 and it is preceeded with
243 : a plus or minus. This is a time zone indication. */
244 0 : found = 1;
245 0 : tz_offset = (val / 100 * 60 + val % 100) * 60;
246 :
247 : /* the + and - prefix indicates the local time compared to GMT,
248 : this we need ther reversed math to get what we want */
249 0 : tz_offset = date[-1] == '+' ? -tz_offset : tz_offset;
250 : }
251 :
252 8 : if (((end - date) == 8) && (year == -1) && (month == -1) && (monthday == -1)) {
253 : /* 8 digits, no year, month or day yet. This is YYYYMMDD */
254 0 : found = 1;
255 0 : year = val / 10000;
256 0 : month = (val % 10000) / 100 - 1; /* month is 0 - 11 */
257 0 : monthday = val % 100;
258 : }
259 :
260 8 : if (!found && (dignext == DATE_MDAY) && (monthday == -1)) {
261 4 : if ((val > 0) && (val < 32)) {
262 4 : monthday = val;
263 4 : found = 1;
264 : }
265 4 : dignext = DATE_YEAR;
266 : }
267 :
268 8 : if (!found && (dignext == DATE_YEAR) && (year == -1)) {
269 4 : year = val;
270 4 : found = 1;
271 4 : if (year < 1900) {
272 0 : year += year > 70 ? 1900 : 2000;
273 : }
274 4 : if(monthday == -1) {
275 0 : dignext = DATE_MDAY;
276 : }
277 : }
278 :
279 8 : if (!found) {
280 0 : return -1;
281 : }
282 :
283 8 : date = end;
284 : }
285 : }
286 :
287 24 : part++;
288 : }
289 :
290 4 : if (-1 == seconds) {
291 0 : seconds = minutes = hours = 0; /* no time, make it zero */
292 : }
293 :
294 4 : if ((-1 == monthday) || (-1 == month) || (-1 == year)) {
295 : /* lacks vital info, fail */
296 0 : return -1;
297 : }
298 :
299 : if (sizeof(time_t) < 5) {
300 : /* 32 bit time_t can only hold dates to the beginning of 2038 */
301 4 : if (year > 2037) {
302 2 : return 0x7fffffff;
303 : }
304 : }
305 :
306 2 : tm.tm_sec = seconds;
307 2 : tm.tm_min = minutes;
308 2 : tm.tm_hour = hours;
309 2 : tm.tm_mday = monthday;
310 2 : tm.tm_mon = month;
311 2 : tm.tm_year = year - 1900;
312 2 : tm.tm_wday = 0;
313 2 : tm.tm_yday = 0;
314 2 : tm.tm_isdst = 0;
315 :
316 2 : t = mktime(&tm);
317 :
318 : /* time zone adjust */
319 2 : if (t != -1) {
320 : struct tm *gmt, keeptime2;
321 : long delta;
322 : time_t t2;
323 :
324 2 : if((gmt = php_gmtime_r(&t, &keeptime2))) {
325 2 : tm = *gmt; /* MSVC quirks */
326 : } else {
327 0 : return -1; /* illegal date/time */
328 : }
329 :
330 2 : t2 = mktime(&tm);
331 :
332 : /* Add the time zone diff (between the given timezone and GMT) and the
333 : diff between the local time zone and GMT. */
334 4 : delta = (tz_offset != -1 ? tz_offset : 0) + (t - t2);
335 :
336 2 : if((delta > 0) && (t + delta < t)) {
337 0 : return -1; /* time_t overflow */
338 : }
339 :
340 2 : t += delta;
341 : }
342 :
343 2 : return t;
344 : }
345 : /* }}} */
346 :
347 :
348 : /*
349 : * Local variables:
350 : * tab-width: 4
351 : * c-basic-offset: 4
352 : * End:
353 : * vim600: sw=4 ts=4 fdm=marker
354 : * vim<600: sw=4 ts=4
355 : */
356 :
|