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 * 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 time_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 != gmtime_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 * 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 * 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 * 237 http_date_str_to_tm (const char * const s, struct tm * const tm) 238 { 239 /* attempt strptime() using multiple date formats 240 * support RFC 822,1123,7231; RFC 850; and ANSI C asctime() date strings, 241 * as required by [RFC7231] https://tools.ietf.org/html/rfc7231#section-7.1 242 * [RFC7231] 7.1.1.1 Date/Time Formats 243 * HTTP-date = IMF-fixdate / obs-date 244 * [...] 245 * A recipient that parses a timestamp value in an HTTP header field 246 * MUST accept all three HTTP-date formats. 247 */ 248 249 /* employ specialized strptime() 250 * - HTTP expected date formats are known, so not needed as input param 251 * - HTTP expected date string content is in C locale and is case-sensitive 252 * - returns (const char *) instead of strptime() (char *) return type 253 * - returns NULL if error (if date string could not be parsed) */ 254 const size_t len = strlen(s); /*(require '\0'-terminated string)*/ 255 if (len == 29) 256 return http_date_parse_IMF_fixdate(s, tm); 257 else if (len > 29) 258 return http_date_parse_RFC_850(s, tm); 259 else /* len < 29 */ 260 return http_date_parse_asctime(s, tm); 261 } 262 263 264 size_t 265 http_date_time_to_str (char * const s, const size_t max, const time_t t) 266 { 267 /*('max' is expected to be >= 30 (IMF-fixdate is 29 chars + '\0'))*/ 268 struct tm tm; 269 return (__builtin_expect( (NULL != gmtime_r(&t, &tm)), 1)) 270 ? strftime(s, max, "%a, %d %b %Y %T GMT", &tm) /* IMF-fixdate format */ 271 : 0; 272 } 273 274 275 #ifdef HAVE_TIMEGM 276 #define http_date_timegm(tm) timegm(tm) 277 #else 278 #ifdef _WIN32 279 #define http_date_timegm(tm) _mkgmtime(tm) 280 #else 281 /* If OS missing timegm(), then for best portability it is recommended to set 282 * $ export LC_TIME=C TZ=UTC0 283 * 284 * tm->tm_isdst = 0 for mktime() to indicate daylight saving time not in effect 285 * which is fine since two strings should be GMT dates, and both are converted 286 * with mktime() and then the results compared */ 287 #define http_date_timegm(tm) ((tm)->tm_isdst = 0, mktime(tm)) 288 #endif 289 #endif 290 291 292 int 293 http_date_if_modified_since (const char * const ifmod, 294 const char * const lmod, time_t lmtime) 295 { 296 /* if caller provides non-zero lmtime, it must match Last-Modified lmod, 297 * and will typically be same arg given to http_response_set_last_modified() 298 * (small opt to elide one strptime(),timegm() call) 299 * (In absense of timegm(), substitute mktime(), which works reasonably well 300 * since mktime() strings are used for comparison of If-Modified-Since) 301 * (use mktime() to convert both strings for compare) */ 302 struct tm ifmodtm; 303 if (NULL == http_date_str_to_tm(ifmod, &ifmodtm)) 304 return 1; /* date parse error */ 305 #if defined(HAVE_TIMEGM) || defined(_WIN32) 306 if (0 == lmtime) 307 #endif 308 { 309 struct tm lmodtm; 310 if (NULL == http_date_str_to_tm(lmod, &lmodtm)) 311 return 1; /* date parse error */ 312 lmtime = http_date_timegm(&lmodtm); 313 } 314 const time_t ifmtime = http_date_timegm(&ifmodtm); 315 return (lmtime > ifmtime); 316 /* returns 0 if not modified since, 317 * returns 1 if modified since or date parse error */ 318 } 319