1 /*
2  * http_date - HTTP date manipulation
3  *
4  * Copyright(c) 2015 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
5  * License: BSD 3-clause (same as lighttpd)
6  */
7 #include "http_date.h"
8 
9 #include "sys-time.h"
10 #include <string.h>     /* strlen() */
11 
12 #include "buffer.h"     /* light_isdigit() */
13 #include "log.h"        /* log_epoch_secs */
14 
15 /**
16  * https://tools.ietf.org/html/rfc7231
17  * [RFC7231] 7.1.1.1 Date/Time Formats
18  *   Prior to 1995, there were three different formats commonly used by
19  *   servers to communicate timestamps.  For compatibility with old
20  *   implementations, all three are defined here.  The preferred format is
21  *   a fixed-length and single-zone subset of the date and time
22  *   specification used by the Internet Message Format [RFC5322].
23  *     HTTP-date    = IMF-fixdate / obs-date
24  *   An example of the preferred format is
25  *     Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate
26  *
27  *
28  * (intended for use with strftime() and strptime())
29  *   "%a, %d %b %Y %T GMT"
30  */
31 
32 
33 static const char datestrs[] =
34   /*0  10  20  30  40  50  60  70  80  90*/
35   "\0\x0A\x14\x1E\x28\x32\x3c\x46\x50\x5A"
36   "SunMonTueWedThuFriSat"
37   "JanFebMarAprMayJunJulAugSepOctNovDec";
38 
39 
40 __attribute_cold__
41 static const char *
http_date_parse_RFC_850(const char * s,struct tm * const tm)42 http_date_parse_RFC_850 (const char *s, struct tm * const tm)
43 {
44     /* RFC 7231 7.1.1.1.
45      *   Recipients of a timestamp value in rfc850-date format, which uses a
46      *   two-digit year, MUST interpret a timestamp that appears to be more
47      *   than 50 years in the future as representing the most recent year in
48      *   the past that had the same last two digits.
49      */
50     static unix_time64_t tm_year_last_check;
51     static int tm_year_cur;
52     static int tm_year_base;
53     /* (log_epoch_secs is a global variable, maintained elsewhere) */
54     /* (optimization: check for year change no more than once per min) */
55     if (log_epoch_secs >= tm_year_last_check + 60) {
56         struct tm tm_cur;
57         if (NULL != gmtime64_r(&log_epoch_secs, &tm_cur)) {
58             tm_year_last_check = log_epoch_secs;
59             if (tm_cur.tm_year != tm_year_cur) {
60                 tm_year_cur = tm_cur.tm_year;
61                 tm_year_base = tm_year_cur - (tm_year_cur % 100);
62             }
63         }
64     }
65 
66     /* Note: does not validate numerical ranges of
67      *       tm_mday, tm_hour, tm_min, tm_sec */
68     /* Note: does not validate tm_wday beyond first three chars */
69 
70     tm->tm_isdst = 0;
71     tm->tm_yday = 0;
72     tm->tm_wday = 0;
73     tm->tm_mon = 0;
74 
75     const char * const tens = datestrs;
76 
77     const char *p = tens + 10;
78     do {
79         if (s[0] == p[0] && s[1] == p[1] && s[2] == p[2]) break;
80         p += 3;
81     } while (++tm->tm_wday < 7);
82     if (7 == tm->tm_wday) return NULL;
83 
84     s += 3;
85     while (*s != ',' && *s != '\0') ++s;
86 
87     if (s[0] != ',' || s[1] != ' '
88         || !light_isdigit(s[2]) || !light_isdigit(s[3]))
89         return NULL;
90     tm->tm_mday = tens[(s[2]-'0')] + (s[3]-'0');
91 
92     if ( s[4] != '-') return NULL;
93     p = tens + 10 + sizeof("SunMonTueWedThuFriSat")-1;
94     do {
95         if (s[5] == p[0] && s[6] == p[1] && s[7] == p[2]) break;
96         p += 3;
97     } while (++tm->tm_mon < 12);
98     if (12 == tm->tm_mon) return NULL;
99 
100     if (s[8] != '-' || !light_isdigit(s[9]) || !light_isdigit(s[10]))
101         return NULL;
102     tm->tm_year = tens[(s[9]-'0')] + (s[10]-'0') + tm_year_base;
103     if (tm->tm_year > tm_year_cur + 50) tm->tm_year -= 100;
104 
105     if (s[11] != ' ' || !light_isdigit(s[12]) || !light_isdigit(s[13]))
106         return NULL;
107     tm->tm_hour = tens[(s[12]-'0')] + (s[13]-'0');
108 
109     if (s[14] != ':' || !light_isdigit(s[15]) || !light_isdigit(s[16]))
110         return NULL;
111     tm->tm_min  = tens[(s[15]-'0')] + (s[16]-'0');
112 
113     if (s[17] != ':' || !light_isdigit(s[18]) || !light_isdigit(s[19]))
114         return NULL;
115     tm->tm_sec  = tens[(s[18]-'0')] + (s[19]-'0');
116 
117     if (s[20] != ' ' || s[21] != 'G' || s[22] != 'M' || s[23] != 'T')
118         return NULL;
119 
120     return s+24; /*(24 chars from ',' following the variable len wday)*/
121 }
122 
123 
124 __attribute_cold__
125 static const char *
http_date_parse_asctime(const char * const s,struct tm * const tm)126 http_date_parse_asctime (const char * const s, struct tm * const tm)
127 {
128     /* Note: does not validate numerical ranges of
129      *       tm_mday, tm_hour, tm_min, tm_sec */
130 
131     tm->tm_isdst = 0;
132     tm->tm_yday = 0;
133     tm->tm_wday = 0;
134     tm->tm_mon = 0;
135 
136     const char * const tens = datestrs;
137 
138     const char *p = tens + 10;
139     do {
140         if (s[0] == p[0] && s[1] == p[1] && s[2] == p[2]) break;
141         p += 3;
142     } while (++tm->tm_wday < 7);
143     if (7 == tm->tm_wday) return NULL;
144 
145     if (s[3] != ' ') return NULL;
146     p = tens + 10 + sizeof("SunMonTueWedThuFriSat")-1;
147     do {
148         if (s[4] == p[0] && s[5] == p[1] && s[6] == p[2]) break;
149         p += 3;
150     } while (++tm->tm_mon < 12);
151     if (12 == tm->tm_mon) return NULL;
152 
153     if (s[7] != ' ' || (s[8] != ' ' && !light_isdigit(s[8]))
154         || !light_isdigit(s[9]))
155         return NULL;
156     tm->tm_mday = (s[8] == ' ' ? 0 : tens[(s[8]-'0')]) + (s[9]-'0');
157 
158     if (s[10] != ' ' || !light_isdigit(s[11]) || !light_isdigit(s[12]))
159         return NULL;
160     tm->tm_hour = tens[(s[11]-'0')] + (s[12]-'0');
161 
162     if (s[13] != ':' || !light_isdigit(s[14]) || !light_isdigit(s[15]))
163         return NULL;
164     tm->tm_min  = tens[(s[14]-'0')] + (s[15]-'0');
165 
166     if (s[16] != ':' || !light_isdigit(s[17]) || !light_isdigit(s[18]))
167         return NULL;
168     tm->tm_sec  = tens[(s[17]-'0')] + (s[18]-'0');
169 
170     if (s[19] != ' ' || !light_isdigit(s[20]) || !light_isdigit(s[21])
171         || !light_isdigit(s[22]) || !light_isdigit(s[23])) return NULL;
172     tm->tm_year =(tens[(s[20]-'0')] + (s[21]-'0'))*100
173                 + tens[(s[22]-'0')] + (s[23]-'0') - 1900;
174 
175     return s+24;
176 }
177 
178 
179 static const char *
http_date_parse_IMF_fixdate(const char * const s,struct tm * const tm)180 http_date_parse_IMF_fixdate (const char * const s, struct tm * const tm)
181 {
182     /* Note: does not validate numerical ranges of
183      *       tm_mday, tm_hour, tm_min, tm_sec */
184 
185     tm->tm_isdst = 0;
186     tm->tm_yday = 0;
187     tm->tm_wday = 0;
188     tm->tm_mon = 0;
189 
190     const char * const tens = datestrs;
191 
192     const char *p = tens + 10;
193     do {
194         if (s[0] == p[0] && s[1] == p[1] && s[2] == p[2]) break;
195         p += 3;
196     } while (++tm->tm_wday < 7);
197     if (7 == tm->tm_wday) return NULL;
198 
199     if (s[3] != ',' || s[4] != ' '
200         || !light_isdigit(s[5]) || !light_isdigit(s[6]))
201         return NULL;
202     tm->tm_mday = tens[(s[5]-'0')] + (s[6]-'0');
203 
204     if ( s[7] != ' ') return NULL;
205     p = tens + 10 + sizeof("SunMonTueWedThuFriSat")-1;
206     do {
207         if (s[8] == p[0] && s[9] == p[1] && s[10] == p[2]) break;
208         p += 3;
209     } while (++tm->tm_mon < 12);
210     if (12 == tm->tm_mon) return NULL;
211 
212     if (s[11] != ' ' || !light_isdigit(s[12]) || !light_isdigit(s[13])
213         || !light_isdigit(s[14]) || !light_isdigit(s[15])) return NULL;
214     tm->tm_year =(tens[(s[12]-'0')] + (s[13]-'0'))*100
215                 + tens[(s[14]-'0')] + (s[15]-'0') - 1900;
216 
217     if (s[16] != ' ' || !light_isdigit(s[17]) || !light_isdigit(s[18]))
218         return NULL;
219     tm->tm_hour = tens[(s[17]-'0')] + (s[18]-'0');
220 
221     if (s[19] != ':' || !light_isdigit(s[20]) || !light_isdigit(s[21]))
222         return NULL;
223     tm->tm_min  = tens[(s[20]-'0')] + (s[21]-'0');
224 
225     if (s[22] != ':' || !light_isdigit(s[23]) || !light_isdigit(s[24]))
226         return NULL;
227     tm->tm_sec  = tens[(s[23]-'0')] + (s[24]-'0');
228 
229     if (s[25] != ' ' || s[26] != 'G' || s[27] != 'M' || s[28] != 'T')
230         return NULL;
231 
232     return s+29;
233 }
234 
235 
236 static const char *
http_date_str_to_tm(const char * const s,const uint32_t len,struct tm * const tm)237 http_date_str_to_tm (const char * const s, const uint32_t len,
238                      struct tm * const tm)
239 {
240 
241     /* attempt strptime() using multiple date formats
242      * support RFC 822,1123,7231; RFC 850; and ANSI C asctime() date strings,
243      * as required by [RFC7231] https://tools.ietf.org/html/rfc7231#section-7.1
244      * [RFC7231] 7.1.1.1 Date/Time Formats
245      *   HTTP-date = IMF-fixdate / obs-date
246      *   [...]
247      *   A recipient that parses a timestamp value in an HTTP header field
248      *   MUST accept all three HTTP-date formats.
249      */
250 
251     /* employ specialized strptime()
252      * - HTTP expected date formats are known, so not needed as input param
253      * - HTTP expected date string content is in C locale and is case-sensitive
254      * - returns (const char *) instead of strptime() (char *) return type
255      * - returns NULL if error (if date string could not be parsed)
256      * Note: internal implementation requires '\0'-terminated string, or at
257      * least one valid char after partial match of RFC 850 or asctime formats */
258     if (len == 29)
259         return http_date_parse_IMF_fixdate(s, tm);
260     else if (len > 29)
261         return http_date_parse_RFC_850(s, tm);
262     else /* len < 29 */
263         return http_date_parse_asctime(s, tm);
264 }
265 
266 
267 uint32_t
http_date_time_to_str(char * const s,const size_t sz,const unix_time64_t t)268 http_date_time_to_str (char * const s, const size_t sz, const unix_time64_t t)
269 {
270     /*('max' is expected to be >= 30 (IMF-fixdate is 29 chars + '\0'))*/
271     struct tm tm;
272     const char fmt[] = "%a, %d %b %Y %T GMT";       /*IMF-fixdate fmt*/
273     return (__builtin_expect( (NULL != gmtime64_r(&t, &tm)), 1))
274       ? (uint32_t)strftime(s, sz, fmt, &tm)
275       : 0;
276 }
277 
278 
279 int
http_date_if_modified_since(const char * const ifmod,const uint32_t ifmodlen,const unix_time64_t lmtime)280 http_date_if_modified_since (const char * const ifmod, const uint32_t ifmodlen,
281                              const unix_time64_t lmtime)
282 {
283     struct tm ifmodtm;
284     if (NULL == http_date_str_to_tm(ifmod, ifmodlen, &ifmodtm))
285         return 1; /* date parse error */
286     const time_t ifmtime = timegm(&ifmodtm);
287   #if HAS_TIME_BITS64
288     return (lmtime > ifmtime);
289   #else
290     return (TIME64_CAST(lmtime) > TIME64_CAST(ifmtime) || ifmtime==(time_t)-1);
291   #endif
292     /* returns 0 if not modified since,
293      * returns 1 if modified since or date parse error */
294 }
295