From f8cc9fb915288879fa0cb4fa7a11dbd0e2c41069 Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Sat, 21 Nov 2020 09:51:40 -0500 Subject: [PATCH] [core] http_date.[ch] encapsulate HTTP-date parse http_date.[ch] encapsulate HTTP-date parse/compare (import from one of my development branches from 2015) --- SConstruct | 1 + configure.ac | 1 + src/CMakeLists.txt | 2 + src/Makefile.am | 3 +- src/SConscript | 1 + src/http_date.c | 120 +++++++++++++++++++++++++++++++++++++++++++++ src/http_date.h | 29 +++++++++++ src/meson.build | 2 + src/server.c | 1 + 9 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/http_date.c create mode 100644 src/http_date.h diff --git a/SConstruct b/SConstruct index 8d5bfaab..f4e2f101 100644 --- a/SConstruct +++ b/SConstruct @@ -448,6 +448,7 @@ if 1: 'strftime', 'strstr', 'strtol', + 'timegm', 'writev', ]) autoconf.haveFunc('getentropy', 'sys/random.h') diff --git a/configure.ac b/configure.ac index 6ebfe385..da9c8b10 100644 --- a/configure.ac +++ b/configure.ac @@ -1479,6 +1479,7 @@ AC_CHECK_FUNCS([\ sigaction \ signal \ srandom \ + timegm \ writev \ ]) AC_CHECK_HEADERS([sys/random.h], [AC_CHECK_FUNCS([getentropy])]) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f2baab9c..1bdaa7fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -188,6 +188,7 @@ check_function_exists(sigtimedwait HAVE_SIGTIMEDWAIT) check_function_exists(srandom HAVE_SRANDOM) check_function_exists(strptime HAVE_STRPTIME) check_function_exists(syslog HAVE_SYSLOG) +check_function_exists(timegm HAVE_TIMEGM) check_function_exists(writev HAVE_WRITEV) check_function_exists(inet_aton HAVE_INET_ATON) check_function_exists(issetugid HAVE_ISSETUGID) @@ -745,6 +746,7 @@ set(COMMON_SRC configfile-glue.c http-header-glue.c http_auth.c + http_date.c http_vhostdb.c request.c sock_addr.c diff --git a/src/Makefile.am b/src/Makefile.am index 7770c1a2..e4280828 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -84,6 +84,7 @@ common_src=base64.c buffer.c burl.c log.c \ configfile-glue.c \ http-header-glue.c \ http_auth.c \ + http_date.c \ http_vhostdb.c \ rand.c \ request.c \ @@ -457,7 +458,7 @@ hdr = base64.h buffer.h burl.h network.h log.h http_kv.h keyvalue.h \ response.h request.h reqpool.h chunk.h h2.h \ first.h http_chunk.h \ algo_md.h algo_md5.h algo_sha1.h algo_splaytree.h algo_xxhash.h \ - http_auth.h http_header.h http_vhostdb.h stream.h \ + http_auth.h http_date.h http_header.h http_vhostdb.h stream.h \ fdevent.h gw_backend.h connections.h base.h base_decls.h stat_cache.h \ plugin.h plugin_config.h \ etag.h array.h vector.h \ diff --git a/src/SConscript b/src/SConscript index 6907bb26..29f12b6b 100644 --- a/src/SConscript +++ b/src/SConscript @@ -70,6 +70,7 @@ common_src = Split("base64.c buffer.c burl.c log.c \ configfile-glue.c \ http-header-glue.c \ http_auth.c \ + http_date.c \ http_vhostdb.c \ request.c \ sock_addr.c \ diff --git a/src/http_date.c b/src/http_date.c new file mode 100644 index 00000000..63dd913e --- /dev/null +++ b/src/http_date.c @@ -0,0 +1,120 @@ +/* + * http_date - HTTP date manipulation + * + * Copyright(c) 2015 Glenn Strauss gstrauss()gluelogic.com All rights reserved + * License: BSD 3-clause (same as lighttpd) + */ +#include "http_date.h" + +#include "sys-time.h" + +/** + * https://tools.ietf.org/html/rfc7231 + * [RFC7231] 7.1.1.1 Date/Time Formats + * Prior to 1995, there were three different formats commonly used by + * servers to communicate timestamps. For compatibility with old + * implementations, all three are defined here. The preferred format is + * a fixed-length and single-zone subset of the date and time + * specification used by the Internet Message Format [RFC5322]. + * HTTP-date = IMF-fixdate / obs-date + * An example of the preferred format is + * Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate + * + * + * (intended for use with strftime() and strptime()) + * "%a, %d %b %Y %T GMT" + */ + + +#if defined(__CYGWIN__) && defined(__STRICT_ANSI__) +/* (prototype for strptime() from cygwin /usr/include/time.h) */ +char *_EXFUN(strptime, (const char *__restrict, + const char *__restrict, + struct tm *__restrict)); +#endif + + +static char * +http_date_str_to_tm (const char * const s, struct tm * const tm) +{ + /* attempt strptime() using multiple date formats + * support RFC 822,1123; RFC 850; and ANSI C asctime() date strings, + * as required by [RFC7231] https://tools.ietf.org/html/rfc7231#section-7.1 + * [RFC7231] 7.1.1.1 Date/Time Formats + * HTTP-date = IMF-fixdate / obs-date + * [...] + * A recipient that parses a timestamp value in an HTTP header field + * MUST accept all three HTTP-date formats. + */ + + static const char *datefmts[] = { + "%a, %d %b %Y %T GMT", /* RFC 822, RFC 1123; RFC 7231 IMF-fixdate */ + "%A, %d-%b-%y %T GMT", /* RFC 850 */ + "%a %b %d %T %Y" /* ANSI C asctime() (obsolete in POSIX.1-2008) */ + }; + + char *p; + int i = 0; + do { + p = strptime(s, datefmts[i], tm); + } while (__builtin_expect( (NULL == p), 0) + && ++i < (int)(sizeof(datefmts)/sizeof(char *))); + return p; /* NULL if error; date string could not be parsed */ +} + + +size_t +http_date_time_to_str (char * const s, const size_t max, const time_t t) +{ + /*('max' is expected to be >= 30 (IMF-fixdate is 29 chars + '\0'))*/ + struct tm tm; + return (__builtin_expect( (NULL != gmtime_r(&t, &tm)), 1)) + ? strftime(s, max, "%a, %d %b %Y %T GMT", &tm) /* IMF-fixdate format */ + : 0; +} + + +#ifdef HAVE_TIMEGM +#define http_date_timegm(tm) timegm(tm) +#else +#ifdef _WIN32 +#define http_date_timegm(tm) _mkgmtime(tm) +#else +/* If OS missing timegm(), then for best portability it is recommended to set + * $ export LC_TIME=C TZ=UTC0 + * + * tm->tm_isdst = 0 for mktime() to indicate daylight saving time not in effect + * which is fine since two strings should be GMT dates, and both are converted + * with mktime() and then the results compared */ +#define http_date_timegm(tm) ((tm)->tm_isdst = 0, mktime(tm)) +#endif +#endif + + +int +http_date_if_modified_since (const char * const ifmod, + const char * const lmod, time_t lmtime) +{ + /* if caller provides non-zero lmtime, it must match Last-Modified lmod, + * and will typically be same arg given to http_response_set_last_modified() + * (small opt to elide one strptime(),timegm() call) + * (In absense of timegm(), substitute mktime(), which works reasonably well + * since mktime() strings are used for comparison of If-Modified-Since) + * (use mktime() to convert both strings for compare) */ + struct tm ifmodtm; + if (NULL == http_date_str_to_tm(ifmod, &ifmodtm)) + return 1; /* date parse error */ + #if defined(HAVE_TIMEGM) || defined(_WIN32) + if (0 == lmtime) + #endif + { + struct tm lmodtm; + if (NULL == http_date_str_to_tm(lmod, &lmodtm)) + return 1; /* date parse error */ + lmtime = http_date_timegm(&lmodtm); + } + const time_t ifmtime = http_date_timegm(&ifmodtm); + return (lmtime > ifmtime); + /* returns 0 if not modified since, + * returns 1 if modified since or date parse error */ +} diff --git a/src/http_date.h b/src/http_date.h new file mode 100644 index 00000000..3e2aae70 --- /dev/null +++ b/src/http_date.h @@ -0,0 +1,29 @@ +/* + * http_date - HTTP date manipulation + * + * Copyright(c) 2015 Glenn Strauss gstrauss()gluelogic.com All rights reserved + * License: BSD 3-clause (same as lighttpd) + */ +#ifndef INCLUDED_HTTP_DATE_H +#define INCLUDED_HTTP_DATE_H +#include "first.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define HTTP_DATE_SZ 30 /* (IMF-fixdate is 29 chars + '\0') */ + +size_t http_date_time_to_str (char *s, size_t max, time_t t); + +int http_date_if_modified_since (const char *ifmod, const char *lmod, time_t lmtime); + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/meson.build b/src/meson.build index 4ef1fee2..6a6f609f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -143,6 +143,7 @@ conf_data.set('HAVE_SIGTIMEDWAIT', compiler.has_function('sigtimedwait', args: d conf_data.set('HAVE_SRANDOM', compiler.has_function('srandom', args: defs)) conf_data.set('HAVE_STRPTIME', compiler.has_function('strptime', args: defs)) conf_data.set('HAVE_SYSLOG', compiler.has_function('syslog', args: defs)) +conf_data.set('HAVE_TIMEGM', compiler.has_function('timegm', args: defs)) conf_data.set('HAVE_WRITEV', compiler.has_function('writev', args: defs)) conf_data.set('HAVE_INET_ATON', compiler.has_function('inet_aton', args: defs)) conf_data.set('HAVE_ISSETUGID', compiler.has_function('issetugid', args: defs)) @@ -713,6 +714,7 @@ common_src = [ 'gw_backend.c', 'http_auth.c', 'http_chunk.c', + 'http_date.c', 'http_header.c', 'http_kv.c', 'http_vhostdb.c', diff --git a/src/server.c b/src/server.c index cc6fc38e..3b301260 100644 --- a/src/server.c +++ b/src/server.c @@ -1944,6 +1944,7 @@ int main (int argc, char **argv) { /* for nice %b handling in strftime() */ setlocale(LC_TIME, "C"); + tzset(); do { server * const srv = server_init();