Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
Re: httpd: add cache controls for static files
To:
tech@openbsd.org
Date:
Sun, 10 May 2026 13:39:20 +0200

Download raw body.

Thread
On Tue, 05 May 2026 21:58:01 +0200,
Kirill A. Korinsky <kirill@korins.ky> 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	<v.string>	STRING
 %token  <v.number>	NUMBER
 %type	<v.port>	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