xref: /lighttpd1.4/src/http_date.c (revision 309c1693)
1f8cc9fb9SGlenn Strauss /*
2f8cc9fb9SGlenn Strauss  * http_date - HTTP date manipulation
3f8cc9fb9SGlenn Strauss  *
4f8cc9fb9SGlenn Strauss  * Copyright(c) 2015 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
5f8cc9fb9SGlenn Strauss  * License: BSD 3-clause (same as lighttpd)
6f8cc9fb9SGlenn Strauss  */
7f8cc9fb9SGlenn Strauss #include "http_date.h"
8f8cc9fb9SGlenn Strauss 
9f8cc9fb9SGlenn Strauss #include "sys-time.h"
1048a8e893SGlenn Strauss #include <string.h>     /* strlen() */
1148a8e893SGlenn Strauss 
1248a8e893SGlenn Strauss #include "buffer.h"     /* light_isdigit() */
1348a8e893SGlenn Strauss #include "log.h"        /* log_epoch_secs */
14f8cc9fb9SGlenn Strauss 
15f8cc9fb9SGlenn Strauss /**
16f8cc9fb9SGlenn Strauss  * https://tools.ietf.org/html/rfc7231
17f8cc9fb9SGlenn Strauss  * [RFC7231] 7.1.1.1 Date/Time Formats
18f8cc9fb9SGlenn Strauss  *   Prior to 1995, there were three different formats commonly used by
19f8cc9fb9SGlenn Strauss  *   servers to communicate timestamps.  For compatibility with old
20f8cc9fb9SGlenn Strauss  *   implementations, all three are defined here.  The preferred format is
21f8cc9fb9SGlenn Strauss  *   a fixed-length and single-zone subset of the date and time
22f8cc9fb9SGlenn Strauss  *   specification used by the Internet Message Format [RFC5322].
23f8cc9fb9SGlenn Strauss  *     HTTP-date    = IMF-fixdate / obs-date
24f8cc9fb9SGlenn Strauss  *   An example of the preferred format is
25f8cc9fb9SGlenn Strauss  *     Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate
26f8cc9fb9SGlenn Strauss  *
27f8cc9fb9SGlenn Strauss  *
28f8cc9fb9SGlenn Strauss  * (intended for use with strftime() and strptime())
29f8cc9fb9SGlenn Strauss  *   "%a, %d %b %Y %T GMT"
30f8cc9fb9SGlenn Strauss  */
31f8cc9fb9SGlenn Strauss 
32f8cc9fb9SGlenn Strauss 
3348a8e893SGlenn Strauss static const char datestrs[] =
3448a8e893SGlenn Strauss   /*0  10  20  30  40  50  60  70  80  90*/
3548a8e893SGlenn Strauss   "\0\x0A\x14\x1E\x28\x32\x3c\x46\x50\x5A"
3648a8e893SGlenn Strauss   "SunMonTueWedThuFriSat"
3748a8e893SGlenn Strauss   "JanFebMarAprMayJunJulAugSepOctNovDec";
38f8cc9fb9SGlenn Strauss 
39f8cc9fb9SGlenn Strauss 
4048a8e893SGlenn Strauss __attribute_cold__
4148a8e893SGlenn Strauss static const char *
http_date_parse_RFC_850(const char * s,struct tm * const tm)4248a8e893SGlenn Strauss http_date_parse_RFC_850 (const char *s, struct tm * const tm)
4348a8e893SGlenn Strauss {
4448a8e893SGlenn Strauss     /* RFC 7231 7.1.1.1.
4548a8e893SGlenn Strauss      *   Recipients of a timestamp value in rfc850-date format, which uses a
4648a8e893SGlenn Strauss      *   two-digit year, MUST interpret a timestamp that appears to be more
4748a8e893SGlenn Strauss      *   than 50 years in the future as representing the most recent year in
4848a8e893SGlenn Strauss      *   the past that had the same last two digits.
4948a8e893SGlenn Strauss      */
50*309c1693SGlenn Strauss     static unix_time64_t tm_year_last_check;
5148a8e893SGlenn Strauss     static int tm_year_cur;
5248a8e893SGlenn Strauss     static int tm_year_base;
5348a8e893SGlenn Strauss     /* (log_epoch_secs is a global variable, maintained elsewhere) */
5448a8e893SGlenn Strauss     /* (optimization: check for year change no more than once per min) */
5548a8e893SGlenn Strauss     if (log_epoch_secs >= tm_year_last_check + 60) {
5648a8e893SGlenn Strauss         struct tm tm_cur;
57*309c1693SGlenn Strauss         if (NULL != gmtime64_r(&log_epoch_secs, &tm_cur)) {
5848a8e893SGlenn Strauss             tm_year_last_check = log_epoch_secs;
5948a8e893SGlenn Strauss             if (tm_cur.tm_year != tm_year_cur) {
6048a8e893SGlenn Strauss                 tm_year_cur = tm_cur.tm_year;
6148a8e893SGlenn Strauss                 tm_year_base = tm_year_cur - (tm_year_cur % 100);
6248a8e893SGlenn Strauss             }
6348a8e893SGlenn Strauss         }
6448a8e893SGlenn Strauss     }
6548a8e893SGlenn Strauss 
6648a8e893SGlenn Strauss     /* Note: does not validate numerical ranges of
6748a8e893SGlenn Strauss      *       tm_mday, tm_hour, tm_min, tm_sec */
6848a8e893SGlenn Strauss     /* Note: does not validate tm_wday beyond first three chars */
6948a8e893SGlenn Strauss 
7048a8e893SGlenn Strauss     tm->tm_isdst = 0;
7148a8e893SGlenn Strauss     tm->tm_yday = 0;
7248a8e893SGlenn Strauss     tm->tm_wday = 0;
7348a8e893SGlenn Strauss     tm->tm_mon = 0;
7448a8e893SGlenn Strauss 
7548a8e893SGlenn Strauss     const char * const tens = datestrs;
7648a8e893SGlenn Strauss 
7748a8e893SGlenn Strauss     const char *p = tens + 10;
7848a8e893SGlenn Strauss     do {
7948a8e893SGlenn Strauss         if (s[0] == p[0] && s[1] == p[1] && s[2] == p[2]) break;
8048a8e893SGlenn Strauss         p += 3;
8148a8e893SGlenn Strauss     } while (++tm->tm_wday < 7);
8248a8e893SGlenn Strauss     if (7 == tm->tm_wday) return NULL;
8348a8e893SGlenn Strauss 
8448a8e893SGlenn Strauss     s += 3;
8548a8e893SGlenn Strauss     while (*s != ',' && *s != '\0') ++s;
8648a8e893SGlenn Strauss 
8748a8e893SGlenn Strauss     if (s[0] != ',' || s[1] != ' '
8848a8e893SGlenn Strauss         || !light_isdigit(s[2]) || !light_isdigit(s[3]))
8948a8e893SGlenn Strauss         return NULL;
9048a8e893SGlenn Strauss     tm->tm_mday = tens[(s[2]-'0')] + (s[3]-'0');
9148a8e893SGlenn Strauss 
9248a8e893SGlenn Strauss     if ( s[4] != '-') return NULL;
9348a8e893SGlenn Strauss     p = tens + 10 + sizeof("SunMonTueWedThuFriSat")-1;
9448a8e893SGlenn Strauss     do {
9548a8e893SGlenn Strauss         if (s[5] == p[0] && s[6] == p[1] && s[7] == p[2]) break;
9648a8e893SGlenn Strauss         p += 3;
9748a8e893SGlenn Strauss     } while (++tm->tm_mon < 12);
9848a8e893SGlenn Strauss     if (12 == tm->tm_mon) return NULL;
9948a8e893SGlenn Strauss 
10048a8e893SGlenn Strauss     if (s[8] != '-' || !light_isdigit(s[9]) || !light_isdigit(s[10]))
10148a8e893SGlenn Strauss         return NULL;
10248a8e893SGlenn Strauss     tm->tm_year = tens[(s[9]-'0')] + (s[10]-'0') + tm_year_base;
10348a8e893SGlenn Strauss     if (tm->tm_year > tm_year_cur + 50) tm->tm_year -= 100;
10448a8e893SGlenn Strauss 
10548a8e893SGlenn Strauss     if (s[11] != ' ' || !light_isdigit(s[12]) || !light_isdigit(s[13]))
10648a8e893SGlenn Strauss         return NULL;
10748a8e893SGlenn Strauss     tm->tm_hour = tens[(s[12]-'0')] + (s[13]-'0');
10848a8e893SGlenn Strauss 
10948a8e893SGlenn Strauss     if (s[14] != ':' || !light_isdigit(s[15]) || !light_isdigit(s[16]))
11048a8e893SGlenn Strauss         return NULL;
11148a8e893SGlenn Strauss     tm->tm_min  = tens[(s[15]-'0')] + (s[16]-'0');
11248a8e893SGlenn Strauss 
11348a8e893SGlenn Strauss     if (s[17] != ':' || !light_isdigit(s[18]) || !light_isdigit(s[19]))
11448a8e893SGlenn Strauss         return NULL;
11548a8e893SGlenn Strauss     tm->tm_sec  = tens[(s[18]-'0')] + (s[19]-'0');
11648a8e893SGlenn Strauss 
11748a8e893SGlenn Strauss     if (s[20] != ' ' || s[21] != 'G' || s[22] != 'M' || s[23] != 'T')
11848a8e893SGlenn Strauss         return NULL;
11948a8e893SGlenn Strauss 
12048a8e893SGlenn Strauss     return s+24; /*(24 chars from ',' following the variable len wday)*/
12148a8e893SGlenn Strauss }
12248a8e893SGlenn Strauss 
12348a8e893SGlenn Strauss 
12448a8e893SGlenn Strauss __attribute_cold__
12548a8e893SGlenn Strauss static const char *
http_date_parse_asctime(const char * const s,struct tm * const tm)12648a8e893SGlenn Strauss http_date_parse_asctime (const char * const s, struct tm * const tm)
12748a8e893SGlenn Strauss {
12848a8e893SGlenn Strauss     /* Note: does not validate numerical ranges of
12948a8e893SGlenn Strauss      *       tm_mday, tm_hour, tm_min, tm_sec */
13048a8e893SGlenn Strauss 
13148a8e893SGlenn Strauss     tm->tm_isdst = 0;
13248a8e893SGlenn Strauss     tm->tm_yday = 0;
13348a8e893SGlenn Strauss     tm->tm_wday = 0;
13448a8e893SGlenn Strauss     tm->tm_mon = 0;
13548a8e893SGlenn Strauss 
13648a8e893SGlenn Strauss     const char * const tens = datestrs;
13748a8e893SGlenn Strauss 
13848a8e893SGlenn Strauss     const char *p = tens + 10;
13948a8e893SGlenn Strauss     do {
14048a8e893SGlenn Strauss         if (s[0] == p[0] && s[1] == p[1] && s[2] == p[2]) break;
14148a8e893SGlenn Strauss         p += 3;
14248a8e893SGlenn Strauss     } while (++tm->tm_wday < 7);
14348a8e893SGlenn Strauss     if (7 == tm->tm_wday) return NULL;
14448a8e893SGlenn Strauss 
14548a8e893SGlenn Strauss     if (s[3] != ' ') return NULL;
14648a8e893SGlenn Strauss     p = tens + 10 + sizeof("SunMonTueWedThuFriSat")-1;
14748a8e893SGlenn Strauss     do {
14848a8e893SGlenn Strauss         if (s[4] == p[0] && s[5] == p[1] && s[6] == p[2]) break;
14948a8e893SGlenn Strauss         p += 3;
15048a8e893SGlenn Strauss     } while (++tm->tm_mon < 12);
15148a8e893SGlenn Strauss     if (12 == tm->tm_mon) return NULL;
15248a8e893SGlenn Strauss 
15348a8e893SGlenn Strauss     if (s[7] != ' ' || (s[8] != ' ' && !light_isdigit(s[8]))
15448a8e893SGlenn Strauss         || !light_isdigit(s[9]))
15548a8e893SGlenn Strauss         return NULL;
15648a8e893SGlenn Strauss     tm->tm_mday = (s[8] == ' ' ? 0 : tens[(s[8]-'0')]) + (s[9]-'0');
15748a8e893SGlenn Strauss 
15848a8e893SGlenn Strauss     if (s[10] != ' ' || !light_isdigit(s[11]) || !light_isdigit(s[12]))
15948a8e893SGlenn Strauss         return NULL;
16048a8e893SGlenn Strauss     tm->tm_hour = tens[(s[11]-'0')] + (s[12]-'0');
16148a8e893SGlenn Strauss 
16248a8e893SGlenn Strauss     if (s[13] != ':' || !light_isdigit(s[14]) || !light_isdigit(s[15]))
16348a8e893SGlenn Strauss         return NULL;
16448a8e893SGlenn Strauss     tm->tm_min  = tens[(s[14]-'0')] + (s[15]-'0');
16548a8e893SGlenn Strauss 
16648a8e893SGlenn Strauss     if (s[16] != ':' || !light_isdigit(s[17]) || !light_isdigit(s[18]))
16748a8e893SGlenn Strauss         return NULL;
16848a8e893SGlenn Strauss     tm->tm_sec  = tens[(s[17]-'0')] + (s[18]-'0');
16948a8e893SGlenn Strauss 
17048a8e893SGlenn Strauss     if (s[19] != ' ' || !light_isdigit(s[20]) || !light_isdigit(s[21])
17148a8e893SGlenn Strauss         || !light_isdigit(s[22]) || !light_isdigit(s[23])) return NULL;
17248a8e893SGlenn Strauss     tm->tm_year =(tens[(s[20]-'0')] + (s[21]-'0'))*100
17348a8e893SGlenn Strauss                 + tens[(s[22]-'0')] + (s[23]-'0') - 1900;
17448a8e893SGlenn Strauss 
17548a8e893SGlenn Strauss     return s+24;
17648a8e893SGlenn Strauss }
17748a8e893SGlenn Strauss 
17848a8e893SGlenn Strauss 
17948a8e893SGlenn Strauss static const char *
http_date_parse_IMF_fixdate(const char * const s,struct tm * const tm)18048a8e893SGlenn Strauss http_date_parse_IMF_fixdate (const char * const s, struct tm * const tm)
18148a8e893SGlenn Strauss {
18248a8e893SGlenn Strauss     /* Note: does not validate numerical ranges of
18348a8e893SGlenn Strauss      *       tm_mday, tm_hour, tm_min, tm_sec */
18448a8e893SGlenn Strauss 
18548a8e893SGlenn Strauss     tm->tm_isdst = 0;
18648a8e893SGlenn Strauss     tm->tm_yday = 0;
18748a8e893SGlenn Strauss     tm->tm_wday = 0;
18848a8e893SGlenn Strauss     tm->tm_mon = 0;
18948a8e893SGlenn Strauss 
19048a8e893SGlenn Strauss     const char * const tens = datestrs;
19148a8e893SGlenn Strauss 
19248a8e893SGlenn Strauss     const char *p = tens + 10;
19348a8e893SGlenn Strauss     do {
19448a8e893SGlenn Strauss         if (s[0] == p[0] && s[1] == p[1] && s[2] == p[2]) break;
19548a8e893SGlenn Strauss         p += 3;
19648a8e893SGlenn Strauss     } while (++tm->tm_wday < 7);
19748a8e893SGlenn Strauss     if (7 == tm->tm_wday) return NULL;
19848a8e893SGlenn Strauss 
19948a8e893SGlenn Strauss     if (s[3] != ',' || s[4] != ' '
20048a8e893SGlenn Strauss         || !light_isdigit(s[5]) || !light_isdigit(s[6]))
20148a8e893SGlenn Strauss         return NULL;
20248a8e893SGlenn Strauss     tm->tm_mday = tens[(s[5]-'0')] + (s[6]-'0');
20348a8e893SGlenn Strauss 
20448a8e893SGlenn Strauss     if ( s[7] != ' ') return NULL;
20548a8e893SGlenn Strauss     p = tens + 10 + sizeof("SunMonTueWedThuFriSat")-1;
20648a8e893SGlenn Strauss     do {
20748a8e893SGlenn Strauss         if (s[8] == p[0] && s[9] == p[1] && s[10] == p[2]) break;
20848a8e893SGlenn Strauss         p += 3;
20948a8e893SGlenn Strauss     } while (++tm->tm_mon < 12);
21048a8e893SGlenn Strauss     if (12 == tm->tm_mon) return NULL;
21148a8e893SGlenn Strauss 
21248a8e893SGlenn Strauss     if (s[11] != ' ' || !light_isdigit(s[12]) || !light_isdigit(s[13])
21348a8e893SGlenn Strauss         || !light_isdigit(s[14]) || !light_isdigit(s[15])) return NULL;
21448a8e893SGlenn Strauss     tm->tm_year =(tens[(s[12]-'0')] + (s[13]-'0'))*100
21548a8e893SGlenn Strauss                 + tens[(s[14]-'0')] + (s[15]-'0') - 1900;
21648a8e893SGlenn Strauss 
21748a8e893SGlenn Strauss     if (s[16] != ' ' || !light_isdigit(s[17]) || !light_isdigit(s[18]))
21848a8e893SGlenn Strauss         return NULL;
21948a8e893SGlenn Strauss     tm->tm_hour = tens[(s[17]-'0')] + (s[18]-'0');
22048a8e893SGlenn Strauss 
22148a8e893SGlenn Strauss     if (s[19] != ':' || !light_isdigit(s[20]) || !light_isdigit(s[21]))
22248a8e893SGlenn Strauss         return NULL;
22348a8e893SGlenn Strauss     tm->tm_min  = tens[(s[20]-'0')] + (s[21]-'0');
22448a8e893SGlenn Strauss 
22548a8e893SGlenn Strauss     if (s[22] != ':' || !light_isdigit(s[23]) || !light_isdigit(s[24]))
22648a8e893SGlenn Strauss         return NULL;
22748a8e893SGlenn Strauss     tm->tm_sec  = tens[(s[23]-'0')] + (s[24]-'0');
22848a8e893SGlenn Strauss 
22948a8e893SGlenn Strauss     if (s[25] != ' ' || s[26] != 'G' || s[27] != 'M' || s[28] != 'T')
23048a8e893SGlenn Strauss         return NULL;
23148a8e893SGlenn Strauss 
23248a8e893SGlenn Strauss     return s+29;
23348a8e893SGlenn Strauss }
23448a8e893SGlenn Strauss 
23548a8e893SGlenn Strauss 
23648a8e893SGlenn Strauss static const char *
http_date_str_to_tm(const char * const s,const uint32_t len,struct tm * const tm)237122094e3SGlenn Strauss http_date_str_to_tm (const char * const s, const uint32_t len,
238122094e3SGlenn Strauss                      struct tm * const tm)
239f8cc9fb9SGlenn Strauss {
240122094e3SGlenn Strauss 
241f8cc9fb9SGlenn Strauss     /* attempt strptime() using multiple date formats
24248a8e893SGlenn Strauss      * support RFC 822,1123,7231; RFC 850; and ANSI C asctime() date strings,
243f8cc9fb9SGlenn Strauss      * as required by [RFC7231] https://tools.ietf.org/html/rfc7231#section-7.1
244f8cc9fb9SGlenn Strauss      * [RFC7231] 7.1.1.1 Date/Time Formats
245f8cc9fb9SGlenn Strauss      *   HTTP-date = IMF-fixdate / obs-date
246f8cc9fb9SGlenn Strauss      *   [...]
247f8cc9fb9SGlenn Strauss      *   A recipient that parses a timestamp value in an HTTP header field
248f8cc9fb9SGlenn Strauss      *   MUST accept all three HTTP-date formats.
249f8cc9fb9SGlenn Strauss      */
250f8cc9fb9SGlenn Strauss 
25148a8e893SGlenn Strauss     /* employ specialized strptime()
25248a8e893SGlenn Strauss      * - HTTP expected date formats are known, so not needed as input param
25348a8e893SGlenn Strauss      * - HTTP expected date string content is in C locale and is case-sensitive
25448a8e893SGlenn Strauss      * - returns (const char *) instead of strptime() (char *) return type
255122094e3SGlenn Strauss      * - returns NULL if error (if date string could not be parsed)
256122094e3SGlenn Strauss      * Note: internal implementation requires '\0'-terminated string, or at
257122094e3SGlenn Strauss      * least one valid char after partial match of RFC 850 or asctime formats */
25848a8e893SGlenn Strauss     if (len == 29)
25948a8e893SGlenn Strauss         return http_date_parse_IMF_fixdate(s, tm);
26048a8e893SGlenn Strauss     else if (len > 29)
26148a8e893SGlenn Strauss         return http_date_parse_RFC_850(s, tm);
26248a8e893SGlenn Strauss     else /* len < 29 */
26348a8e893SGlenn Strauss         return http_date_parse_asctime(s, tm);
264f8cc9fb9SGlenn Strauss }
265f8cc9fb9SGlenn Strauss 
266f8cc9fb9SGlenn Strauss 
267122094e3SGlenn Strauss uint32_t
http_date_time_to_str(char * const s,const size_t sz,const unix_time64_t t)268*309c1693SGlenn Strauss http_date_time_to_str (char * const s, const size_t sz, const unix_time64_t t)
269f8cc9fb9SGlenn Strauss {
270f8cc9fb9SGlenn Strauss     /*('max' is expected to be >= 30 (IMF-fixdate is 29 chars + '\0'))*/
271f8cc9fb9SGlenn Strauss     struct tm tm;
272122094e3SGlenn Strauss     const char fmt[] = "%a, %d %b %Y %T GMT";       /*IMF-fixdate fmt*/
273*309c1693SGlenn Strauss     return (__builtin_expect( (NULL != gmtime64_r(&t, &tm)), 1))
274122094e3SGlenn Strauss       ? (uint32_t)strftime(s, sz, fmt, &tm)
275f8cc9fb9SGlenn Strauss       : 0;
276f8cc9fb9SGlenn Strauss }
277f8cc9fb9SGlenn Strauss 
278f8cc9fb9SGlenn Strauss 
279f8cc9fb9SGlenn Strauss int
http_date_if_modified_since(const char * const ifmod,const uint32_t ifmodlen,const unix_time64_t lmtime)280122094e3SGlenn Strauss http_date_if_modified_since (const char * const ifmod, const uint32_t ifmodlen,
281*309c1693SGlenn Strauss                              const unix_time64_t lmtime)
282f8cc9fb9SGlenn Strauss {
283f8cc9fb9SGlenn Strauss     struct tm ifmodtm;
284122094e3SGlenn Strauss     if (NULL == http_date_str_to_tm(ifmod, ifmodlen, &ifmodtm))
285f8cc9fb9SGlenn Strauss         return 1; /* date parse error */
2866b6252a3SGlenn Strauss     const time_t ifmtime = timegm(&ifmodtm);
287*309c1693SGlenn Strauss   #if HAS_TIME_BITS64
288f8cc9fb9SGlenn Strauss     return (lmtime > ifmtime);
289*309c1693SGlenn Strauss   #else
290*309c1693SGlenn Strauss     return (TIME64_CAST(lmtime) > TIME64_CAST(ifmtime) || ifmtime==(time_t)-1);
291*309c1693SGlenn Strauss   #endif
292f8cc9fb9SGlenn Strauss     /* returns 0 if not modified since,
293f8cc9fb9SGlenn Strauss      * returns 1 if modified since or date parse error */
294f8cc9fb9SGlenn Strauss }
295