From: Kirill A. Korinsky Subject: Re: httpd: add cache controls for static files To: tech@openbsd.org Date: Sun, 10 May 2026 13:39:20 +0200 On Tue, 05 May 2026 21:58:01 +0200, Kirill A. Korinsky wrote: > > tech@, > > I'd like to teach httpd to advertise static file revalidation by default > with Cache-Control: no-cache, preserving the existing Last-Modified and > If-Modified-Since flow; add a [no] cache directive for opting out, and > advertise Vary: Accept-Encoding whenever gzip-static is enabled. > This is new version which is rebased upon inherit gzip-static in locations fix. It widen server flags to 64-bit integers because the 32-bit flag space has no room for the cache/no-cache pair. Ok? Index: config.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/config.c,v diff -u -p -r1.70 config.c --- config.c 10 May 2026 10:02:04 -0000 1.70 +++ config.c 10 May 2026 11:37:11 -0000 @@ -501,7 +501,7 @@ config_getserver_config(struct httpd *en #endif struct server_config *srv_conf, *parent; uint8_t *p = imsg->data; - unsigned int f; + uint64_t f; size_t s; if ((srv_conf = calloc(1, sizeof(*srv_conf))) == NULL) @@ -632,6 +632,10 @@ config_getserver_config(struct httpd *en (void)strlcpy(srv_conf->path, parent->path, sizeof(srv_conf->path)); } + + f = SRVFLAG_CACHE|SRVFLAG_NO_CACHE; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= parent->flags & f; f = SRVFLAG_SERVER_HSTS; srv_conf->flags |= parent->flags & f; Index: httpd.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.c,v diff -u -p -r1.77 httpd.c --- httpd.c 2 Mar 2026 19:24:58 -0000 1.77 +++ httpd.c 10 May 2026 11:37:11 -0000 @@ -1218,7 +1218,7 @@ print_host(struct sockaddr_storage *ss, } const char * -printb_flags(const uint32_t v, const char *bits) +printb_flags(const uint64_t v, const char *bits) { static char buf[2][BUFSIZ]; static int idx = 0; @@ -1231,13 +1231,15 @@ printb_flags(const uint32_t v, const cha if (bits) { bits++; while ((i = *bits++)) { - if (v & (1 << (i - 1))) { + if (v & (1ULL << (i - 1))) { if (any) { *p++ = ','; *p++ = ' '; } any = 1; - for (; (c = *bits) > 32; bits++) { + for (; isalnum((unsigned char)*bits) || + *bits == '_'; bits++) { + c = *bits; if (c == '_') *p++ = ' '; else @@ -1245,7 +1247,8 @@ printb_flags(const uint32_t v, const cha tolower((unsigned char)c); } } else - for (; *bits > 32; bits++) + for (; isalnum((unsigned char)*bits) || + *bits == '_'; bits++) ; } } Index: httpd.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v diff -u -p -r1.129 httpd.conf.5 --- httpd.conf.5 18 Jan 2026 16:38:02 -0000 1.129 +++ httpd.conf.5 10 May 2026 11:37:11 -0000 @@ -254,6 +254,18 @@ If is omitted, enable the banner for the current .Ic server if it was disabled globally. +.It Oo Ic no Oc Ic cache +Send +.Dq Cache-Control: no-cache +with static file responses. +When +.Ic gzip-static +is enabled, also send +.Dq Vary: Accept-Encoding +with static file responses. +This is enabled by default; use +.Ic no cache +to disable these headers. .It Ic block drop Drop the connection without sending an error page. .It Ic block Op Ic return Ar code Op Ar uri Index: httpd.h =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v diff -u -p -r1.170 httpd.h --- httpd.h 10 May 2026 10:02:04 -0000 1.170 +++ httpd.h 10 May 2026 11:37:12 -0000 @@ -119,7 +119,7 @@ enum httpchunk { struct ctl_flags { uint8_t cf_opts; - uint32_t cf_flags; + uint64_t cf_flags; uint8_t cf_tls_sid[TLS_MAX_SESSION_ID_LENGTH]; }; @@ -362,37 +362,39 @@ struct client { }; SPLAY_HEAD(client_tree, client); -#define SRVFLAG_INDEX 0x00000001 -#define SRVFLAG_NO_INDEX 0x00000002 -#define SRVFLAG_AUTO_INDEX 0x00000004 -#define SRVFLAG_NO_AUTO_INDEX 0x00000008 -#define SRVFLAG_ROOT 0x00000010 -#define SRVFLAG_LOCATION 0x00000020 -#define SRVFLAG_FCGI 0x00000040 -#define SRVFLAG_NO_FCGI 0x00000080 -#define SRVFLAG_LOG 0x00000100 -#define SRVFLAG_NO_LOG 0x00000200 -#define SRVFLAG_ERRDOCS 0x00000400 -#define SRVFLAG_SYSLOG 0x00000800 -#define SRVFLAG_NO_SYSLOG 0x00001000 -#define SRVFLAG_TLS 0x00002000 -#define SRVFLAG_ACCESS_LOG 0x00004000 -#define SRVFLAG_ERROR_LOG 0x00008000 -#define SRVFLAG_AUTH 0x00010000 -#define SRVFLAG_NO_AUTH 0x00020000 -#define SRVFLAG_BLOCK 0x00040000 -#define SRVFLAG_NO_BLOCK 0x00080000 -#define SRVFLAG_LOCATION_MATCH 0x00100000 -#define SRVFLAG_SERVER_MATCH 0x00200000 -#define SRVFLAG_SERVER_HSTS 0x00400000 -#define SRVFLAG_DEFAULT_TYPE 0x00800000 -#define SRVFLAG_PATH_REWRITE 0x01000000 -#define SRVFLAG_NO_PATH_REWRITE 0x02000000 -#define SRVFLAG_GZIP_STATIC 0x04000000 -#define SRVFLAG_NO_BANNER 0x08000000 -#define SRVFLAG_NO_GZIP_STATIC 0x10000000 -#define SRVFLAG_LOCATION_FOUND 0x40000000 -#define SRVFLAG_LOCATION_NOT_FOUND 0x80000000 +#define SRVFLAG_INDEX 0x0000000000000001ULL +#define SRVFLAG_NO_INDEX 0x0000000000000002ULL +#define SRVFLAG_AUTO_INDEX 0x0000000000000004ULL +#define SRVFLAG_NO_AUTO_INDEX 0x0000000000000008ULL +#define SRVFLAG_ROOT 0x0000000000000010ULL +#define SRVFLAG_LOCATION 0x0000000000000020ULL +#define SRVFLAG_FCGI 0x0000000000000040ULL +#define SRVFLAG_NO_FCGI 0x0000000000000080ULL +#define SRVFLAG_LOG 0x0000000000000100ULL +#define SRVFLAG_NO_LOG 0x0000000000000200ULL +#define SRVFLAG_ERRDOCS 0x0000000000000400ULL +#define SRVFLAG_SYSLOG 0x0000000000000800ULL +#define SRVFLAG_NO_SYSLOG 0x0000000000001000ULL +#define SRVFLAG_TLS 0x0000000000002000ULL +#define SRVFLAG_ACCESS_LOG 0x0000000000004000ULL +#define SRVFLAG_ERROR_LOG 0x0000000000008000ULL +#define SRVFLAG_AUTH 0x0000000000010000ULL +#define SRVFLAG_NO_AUTH 0x0000000000020000ULL +#define SRVFLAG_BLOCK 0x0000000000040000ULL +#define SRVFLAG_NO_BLOCK 0x0000000000080000ULL +#define SRVFLAG_LOCATION_MATCH 0x0000000000100000ULL +#define SRVFLAG_SERVER_MATCH 0x0000000000200000ULL +#define SRVFLAG_SERVER_HSTS 0x0000000000400000ULL +#define SRVFLAG_DEFAULT_TYPE 0x0000000000800000ULL +#define SRVFLAG_PATH_REWRITE 0x0000000001000000ULL +#define SRVFLAG_NO_PATH_REWRITE 0x0000000002000000ULL +#define SRVFLAG_GZIP_STATIC 0x0000000004000000ULL +#define SRVFLAG_NO_BANNER 0x0000000008000000ULL +#define SRVFLAG_NO_GZIP_STATIC 0x0000000010000000ULL +#define SRVFLAG_LOCATION_FOUND 0x0000000040000000ULL +#define SRVFLAG_LOCATION_NOT_FOUND 0x0000000080000000ULL +#define SRVFLAG_CACHE 0x0000000100000000ULL +#define SRVFLAG_NO_CACHE 0x0000000200000000ULL #define SRVFLAG_BITS \ "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \ @@ -401,7 +403,8 @@ SPLAY_HEAD(client_tree, client); "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \ "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE\31PATH_REWRITE" \ "\32NO_PATH_REWRITE\34NO_BANNER\33GZIP_STATIC" \ - "\35NO_GZIP_STATIC\37LOCATION_FOUND\40LOCATION_NOT_FOUND" + "\35NO_GZIP_STATIC\37LOCATION_FOUND\40LOCATION_NOT_FOUND" \ + "\41CACHE\42NO_CACHE" #define TCPFLAG_NODELAY 0x01 #define TCPFLAG_NNODELAY 0x02 @@ -516,7 +519,7 @@ struct server_config { struct server_tls_ticket tls_ticket_key; int tls_ticket_lifetime; - uint32_t flags; + uint64_t flags; int strip; uint8_t tcpflags; int tcpbufsiz; @@ -582,7 +585,7 @@ TAILQ_HEAD(serverlist, server); struct httpd { uint8_t sc_opts; - uint32_t sc_flags; + uint64_t sc_flags; const char *sc_conffile; struct event sc_ev; uint16_t sc_prefork_server; @@ -756,7 +759,7 @@ struct auth *auth_add(struct serverauth struct auth *auth_byid(struct serverauth *, uint32_t); void auth_free(struct serverauth *, struct auth *); const char *print_host(struct sockaddr_storage *, char *, size_t); -const char *printb_flags(const uint32_t, const char *); +const char *printb_flags(const uint64_t, const char *); void getmonotime(struct timeval *); extern struct httpd *httpd_env; Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/httpd/parse.y,v diff -u -p -r1.132 parse.y --- parse.y 10 May 2026 10:02:04 -0000 1.132 +++ parse.y 10 May 2026 11:37:13 -0000 @@ -142,7 +142,7 @@ typedef struct { %token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS REWRITE %token CA CLIENT CRL OPTIONAL PARAM FORWARDED FOUND NOT -%token ERRDOCS GZIPSTATIC BANNER +%token ERRDOCS GZIPSTATIC BANNER CACHE %token STRING %token NUMBER %type port @@ -275,7 +275,7 @@ server : SERVER optmatch STRING { SERVER_REQUESTTIMEOUT; s->srv_conf.maxrequests = SERVER_MAXREQUESTS; s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY; - s->srv_conf.flags = SRVFLAG_LOG; + s->srv_conf.flags = SRVFLAG_LOG | SRVFLAG_CACHE; if ($2) s->srv_conf.flags |= SRVFLAG_SERVER_MATCH; s->srv_conf.logformat = LOG_FORMAT_COMMON; @@ -558,6 +558,7 @@ serveroptsl : LISTEN ON STRING opttls po | root | directory | banner + | cache | logformat | fastcgi | authenticate @@ -646,7 +647,7 @@ serveroptsl : LISTEN ON STRING opttls po SPLAY_INIT(&srv->srv_clients); } '{' optnl serveropts_l '}' { struct server *s = NULL; - uint32_t f; + uint64_t f; f = SRVFLAG_LOCATION_FOUND | SRVFLAG_LOCATION_NOT_FOUND; @@ -1252,6 +1253,16 @@ gzip_static : NO GZIPSTATIC { } ; +cache : NO CACHE { + srv_conf->flags &= ~SRVFLAG_CACHE; + srv_conf->flags |= SRVFLAG_NO_CACHE; + } + | CACHE { + srv_conf->flags &= ~SRVFLAG_NO_CACHE; + srv_conf->flags |= SRVFLAG_CACHE; + } + ; + tcpip : TCP '{' optnl tcpflags_l '}' | TCP tcpflags ; @@ -1459,6 +1470,7 @@ lookup(char *s) { "body", BODY }, { "buffer", BUFFER }, { "ca", CA }, + { "cache", CACHE }, { "certificate", CERTIFICATE }, { "chroot", CHROOT }, { "ciphers", CIPHERS }, Index: server_http.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v diff -u -p -r1.161 server_http.c --- server_http.c 2 Mar 2026 19:24:58 -0000 1.161 +++ server_http.c 10 May 2026 11:37:13 -0000 @@ -1608,6 +1608,16 @@ server_response_http(struct client *clt, kv_set(cl, "%lld", (long long)size) == -1)) return (-1); + if (srv_conf->flags & SRVFLAG_CACHE) { + if (kv_add(&resp->http_headers, "Cache-Control", + "no-cache") == NULL) + return (-1); + if (srv_conf->flags & SRVFLAG_GZIP_STATIC && + kv_add(&resp->http_headers, "Vary", + "Accept-Encoding") == NULL) + return (-1); + } + /* Set last modification time */ if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 || kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL) -- wbr, Kirill