Merge with git+ssh://pasky.or.cz/srv/git/elinks.git
[elinks.git] / src / protocol / date.c
blobbdd7247724fcdb6a1f040d8963907c0cff0cd505
1 /* Parser of HTTP date */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
10 #ifdef TIME_WITH_SYS_TIME
11 #ifdef HAVE_SYS_TIME_H
12 #include <sys/time.h>
13 #endif
14 #ifdef HAVE_TIME_H
15 #include <time.h>
16 #endif
17 #else
18 #if defined(TM_IN_SYS_TIME) && defined(HAVE_SYS_TIME_H)
19 #include <sys/time.h>
20 #elif defined(HAVE_TIME_H)
21 #include <time.h>
22 #endif
23 #endif
25 #include "elinks.h"
27 #include "protocol/date.h"
28 #include "util/conv.h"
29 #include "util/time.h"
32 * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
33 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
34 * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
37 int
38 parse_year(const unsigned char **date_p, unsigned char *end)
40 const unsigned char *date = *date_p;
41 int year;
43 if ((end && date + 1 >= end)
44 || !isdigit(date[0])
45 || !isdigit(date[1]))
46 return -1;
48 year = (date[0] - '0') * 10 + date[1] - '0';
50 if ((!end || date + 3 < end)
51 && isdigit(date[2])
52 && isdigit(date[3])) {
53 /* Four digits date */
54 year = year * 10 + date[2] - '0';
55 year = year * 10 + date[3] - '0' - 1900;
56 date += 4;
58 } else if (year < 70) {
59 /* Assuming the epoch starting at 1.1.1970 so it's already next
60 * century. --wget */
61 year += 100;
62 date += 2;
65 *date_p = date;
66 return year;
69 int
70 parse_month(const unsigned char **buf, unsigned char *end)
72 const unsigned char *month = *buf;
73 int monthnum;
75 /* Check that the buffer has atleast 3 chars. */
76 if ((end && month + 2 > end)
77 || !month[0] || !month[1] || !month[2])
78 return -1;
80 monthnum = month2num(month);
82 if (monthnum != -1)
83 *buf += 3;
85 return monthnum;
89 /* Return day number. */
90 int
91 parse_day(const unsigned char **date_p, unsigned char *end)
93 const unsigned char *date = *date_p;
94 int day;
96 if ((end && date >= end) || !isdigit(*date))
97 return 32;
98 day = *date++ - '0';
100 if ((!end || date < end) && isdigit(*date)) {
101 day = day * 10 + *date++ - '0';
104 *date_p = date;
105 return day;
109 parse_time(const unsigned char **time, struct tm *tm, unsigned char *end)
111 unsigned char h1, h2, m1, m2;
112 const unsigned char *date = *time;
114 #define check_time(tm) \
115 ((tm)->tm_hour <= 23 && (tm)->tm_min <= 59 && (tm)->tm_sec <= 59)
117 /* Eat HH:MM */
118 if (end && date + 5 > end)
119 return 0;
121 h1 = *date++; if (!isdigit(h1)) return 0;
122 h2 = *date++; if (!isdigit(h2)) return 0;
124 if (*date++ != ':') return 0;
126 m1 = *date++; if (!isdigit(m1)) return 0;
127 m2 = *date++; if (!isdigit(m2)) return 0;
129 tm->tm_hour = (h1 - '0') * 10 + h2 - '0';
130 tm->tm_min = (m1 - '0') * 10 + m2 - '0';
132 /* Eat :SS or [PA]M or nothing */
133 if (end && date + 2 >= end) {
134 *time = date;
135 return check_time(tm);
138 if (*date == ':') {
139 unsigned char s1, s2;
141 date++;
143 if (end && date + 2 >= end)
144 return 0;
146 s1 = *date++; if (!isdigit(s1)) return 0;
147 s2 = *date++; if (!isdigit(s2)) return 0;
149 tm->tm_sec = (s1 - '0') * 10 + s2 - '0';
151 } else if (*date == 'A' || *date == 'P') {
152 /* Adjust hour from AM/PM. The sequence goes 11:00AM, 12:00PM,
153 * 01:00PM ... 11:00PM, 12:00AM, 01:00AM. --wget */
154 if (tm->tm_hour == 12)
155 tm->tm_hour = 0;
157 if (*date++ == 'P')
158 tm->tm_hour += 12;
160 if (*date++ != 'M')
161 return 0;
164 *time = date;
166 return check_time(tm);
170 static time_t
171 my_timegm(struct tm *tm)
173 time_t t = 0;
175 /* Okay, the next part of the code is somehow problematic. Now, we use
176 * own code for calculating the number of seconds from 1.1.1970,
177 * brought here by SC from w3m. I don't like it a lot, but it's 100%
178 * portable, it's faster and it's shorter. --pasky */
179 #if 0
180 #ifdef HAVE_TIMEGM
181 t = timegm(tm);
182 #else
183 /* Since mktime thinks we have localtime, we need a wrapper
184 * to handle GMT. */
185 /* FIXME: It was reported that it doesn't work somewhere :/. */
187 unsigned char *tz = getenv("TZ");
189 if (tz && *tz) {
190 /* Temporary disable timezone in-place. */
191 unsigned char tmp = *tz;
193 *tz = '\0';
194 tzset();
196 t = mktime(tm);
198 *tz = tmp;
199 tzset();
201 } else {
202 /* Already GMT, cool! */
203 t = mktime(tm);
206 #endif
207 #else
208 /* Following code was borrowed from w3m, and its developers probably
209 * borrowed it from somewhere else as well, altough they didn't bother
210 * to mention that. */ /* Actually, same code appears to be in lynx as
211 * well.. oh well. :) */
212 /* See README.timegm for explanation about how this works. */
213 tm->tm_mon -= 2;
214 if (tm->tm_mon < 0) {
215 tm->tm_mon += 12;
216 tm->tm_year--;
218 tm->tm_mon *= 153; tm->tm_mon += 2;
219 tm->tm_year -= 68;
221 /* Bug 924: Assumes all years divisible by 4 are leap years,
222 * even though e.g. 2100 is not. */
223 tm->tm_mday += tm->tm_year * 1461 / 4;
224 tm->tm_mday += ((tm->tm_mon / 5) - 672);
226 t = ((tm->tm_mday * 60 * 60 * 24) +
227 (tm->tm_hour * 60 * 60) +
228 (tm->tm_min * 60) +
229 tm->tm_sec);
230 #endif
232 if (t == (time_t) -1) return 0;
234 return t;
238 time_t
239 parse_date(unsigned char **date_pos, unsigned char *end,
240 int update_pos, int skip_week_day)
242 #define skip_time_sep(date, end) \
243 do { \
244 const unsigned char *start = (date); \
245 while ((!(end) || (date) < (end)) \
246 && (*(date) == ' ' || *(date) == '-')) \
247 (date)++; \
248 if (date == start) return 0; \
249 } while (0)
251 struct tm tm;
252 const unsigned char *date = (const unsigned char *) *date_pos;
254 if (!date) return 0;
256 if (skip_week_day) {
257 /* Skip day-of-week */
258 for (; (!end || date < end) && *date != ' '; date++)
259 if (!*date) return 0;
261 /* As pasky said who cares if we allow '-'s here? */
262 skip_time_sep(date, end);
265 if (isdigit(*date)) {
266 /* RFC 1036 / RFC 1123 */
268 /* Eat day */
270 /* date++; */
271 tm.tm_mday = parse_day(&date, end);
272 if (tm.tm_mday > 31) return 0;
274 skip_time_sep(date, end);
276 /* Eat month */
278 tm.tm_mon = parse_month(&date, end);
279 if (tm.tm_mon < 0) return 0;
281 skip_time_sep(date, end);
283 /* Eat year */
285 tm.tm_year = parse_year(&date, end);
286 if (tm.tm_year < 0) return 0;
288 skip_time_sep(date, end);
290 /* Eat time */
292 if (!parse_time(&date, &tm, end)) return 0;
294 } else {
295 /* ANSI C's asctime() format */
297 /* Eat month */
299 tm.tm_mon = parse_month(&date, end);
300 if (tm.tm_mon < 0) return 0;
302 /* I know, we shouldn't allow '-', but who cares ;). --pasky */
303 skip_time_sep(date, end);
305 /* Eat day */
307 tm.tm_mday = parse_day(&date, end);
308 if (tm.tm_mday > 31) return 0;
310 skip_time_sep(date, end);
312 /* Eat time */
314 if (!parse_time(&date, &tm, end)) return 0;
316 skip_time_sep(date, end);
318 /* Eat year */
320 tm.tm_year = parse_year(&date, end);
321 if (tm.tm_year < 0) return 0;
323 #undef skip_time_sep
325 if (update_pos)
326 *date_pos = (unsigned char *) date;
328 return (time_t) my_timegm(&tm);