Index | Thread | Search

From:
Adam Mullins <alm@alm4x.com>
Subject:
Re: add support to httpd for serving static brotli encoded files
To:
tech@openbsd.org
Date:
Mon, 23 Feb 2026 21:44:42 -0500

Download raw body.

Thread
Whoops. I sent that a little early. I also did not justify it 72
characters. Sorry about that.

I also meant to add that I commented more heavily than the rest of the
httpd files. I don't expect these comments to survive review, but I
wanted to outline my thought process for the reviewer.

The unified diff is included below.

On 2/23/26 9:31 PM, Adam Mullins wrote:
 > Hello. This is my first time contributing to a free software
 > project. Feel free to let me know if I have made any mistakes or
 > missed anything. Sorry for the long email, but I wanted to cover
 > everything.
 >
 > Currently httpd has the ability to preferentially serve gzip encoded
 > static files, but not brotli encoded files. This patch adds the
 > brotli-static option to httpd, allowing it to serve .br files to
 > clients. Brotli offers 10-20% better compression than gzip for common
 > web file types.
 >
 > With this patch, when brotli-static is set in httpd.conf, httpd will
 > serve a *.br file if the client has signaled it will accept it, and
 > the connection is inside of TLS, and the *.br version is no older than
 > the *.gz and unencoded file. Failing that, it will try to serve the
 > gzip version under the same constraints, minus the TLS
 > requirement. Finally, it defaults to serving the unencoded file.
 >
 > Changes:
 > - Update httpd.conf.5 to explain the new option.
 > - Add rules and logic for parsing httpd.conf to parse.y.
 > - Add SRVFLAG_BROTLI_STATIC to httpd.h.
 > - Migrate encoded file selection logic out of
 >    server_file.c:server_file_access() to a helper
 >   static function, and adds logic to select *.br files if they exist.
 >
 > I was uncertain about a couple of things.
 >
 > First, I #define'd SRVFLAG_BROTLI_STATIC to 0x10000000. This particular
 > number was skipped by the other flags (ie, the flags went from
 > 0x08... to 0x40...). I'm not sure if this constant is being avoided for
 > some reason, but everything seems to be working okay. Also, with this
 > addition we are out of possible 32 bit combinations unless I am
 > missing something.
 >
 > Second, I tried a few approaches to the selection logic flow, but I
 > couldn't get it any more compact. It was taking up a lot of space in the
 > original function, so I migrated it to a helper. I only noticed a few
 > others like this in the project, so I'm not sure if this idiomatic. Also,
 > while my additions check the return of libc calls like fstat() and
 > snprintf() for errors, it _does not_ check that all passed in pointers
 > are non-NULL. This seems reasonable to me given that it is a file-local
 > helper, called from only one function, and that caller likewise does not
 > check its arguments (ie server_file_access does not verify
 > `struct client *clt` is non-NULL before dereferencing it).

cvs server: Diffing .
Index: httpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v
diff -u -p -u -r1.129 httpd.conf.5
--- httpd.conf.5        18 Jan 2026 16:38:02 -0000      1.129
+++ httpd.conf.5        24 Feb 2026 02:41:19 -0000
@@ -464,6 +464,15 @@ Enable static gzip compression to save b
  If gzip encoding is accepted and if the requested file exists with
  an additional .gz suffix, use the compressed file instead and deliver
  it with content encoding gzip.
+.It Ic brotli-static
+Enable static brotli compression to save bandwidth.
+.Pp
+If brotli encoding is accepted, the requested file exists with a .br 
suffix,
+and the client is connected with TLS, use the compressed file instead and
+deliver it with content encoding br.
+.Pp
+If both brotli-static and gzip-static are enabled, brotli is preferred
+if the above conditions are satisfied.
  .It Ic hsts Oo Ar option Oc
  Enable HTTP Strict Transport Security.
  Valid options are:
Index: httpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v
diff -u -p -u -r1.167 httpd.h
--- httpd.h     28 Nov 2025 16:10:00 -0000      1.167
+++ httpd.h     24 Feb 2026 02:41:19 -0000
@@ -390,6 +390,7 @@ SPLAY_HEAD(client_tree, client);
  #define SRVFLAG_NO_PATH_REWRITE        0x02000000
  #define SRVFLAG_GZIP_STATIC    0x04000000
  #define SRVFLAG_NO_BANNER      0x08000000
+#define SRVFLAG_BROTLI_STATIC  0x10000000
  #define SRVFLAG_LOCATION_FOUND 0x40000000
  #define SRVFLAG_LOCATION_NOT_FOUND 0x80000000

Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/parse.y,v
diff -u -p -u -r1.130 parse.y
--- parse.y     28 Nov 2025 16:10:00 -0000      1.130
+++ parse.y     24 Feb 2026 02:41:19 -0000
@@ -141,7 +141,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 BROTLISTATIC
  %token <v.string>      STRING
  %token  <v.number>     NUMBER
  %type  <v.port>        port
@@ -561,6 +561,7 @@ serveroptsl : LISTEN ON STRING opttls po
                 | fastcgi
                 | authenticate
                 | gzip_static
+               | brotli_static
                 | filter
                 | LOCATION optfound optmatch STRING     {
                         struct server           *s;
@@ -1249,6 +1250,14 @@ gzip_static      : NO GZIPSTATIC         {
                 }
                 ;

+brotli_static  : NO BROTLISTATIC       {
+                       srv->srv_conf.flags &= ~SRVFLAG_BROTLI_STATIC;
+               }
+               | BROTLISTATIC          {
+                       srv->srv_conf.flags |= SRVFLAG_BROTLI_STATIC;
+               }
+               ;
+
  tcpip          : TCP '{' optnl tcpflags_l '}'
                 | TCP tcpflags
                 ;
@@ -1455,6 +1464,7 @@ lookup(char *s)
                 { "block",              BLOCK },
                 { "body",               BODY },
                 { "buffer",             BUFFER },
+               { "brotli-static",      BROTLISTATIC },
                 { "ca",                 CA },
                 { "certificate",        CERTIFICATE },
                 { "chroot",             CHROOT },
Index: server_file.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_file.c,v
diff -u -p -u -r1.80 server_file.c
--- server_file.c       29 Apr 2024 16:17:46 -0000      1.80
+++ server_file.c       24 Feb 2026 02:41:19 -0000
@@ -55,6 +55,8 @@ int            server_file_method(struct client *
  int             parse_range_spec(char *, size_t, struct range *);
  int             parse_ranges(struct client *, char *, size_t);
  static int      select_visible(const struct dirent *);
+static int      server_file_encoded_path(const struct client *, const 
char *,
+                    int *, struct stat *);

  int
  server_file_access(struct httpd *env, struct client *clt,
@@ -168,44 +170,10 @@ server_file_access(struct httpd *env, st
                             fd, &st, r->kv_value));
         }

-       /* change path to path.gz if necessary. */
-       if (srv_conf->flags & SRVFLAG_GZIP_STATIC) {
-               struct http_descriptor  *req = clt->clt_descreq;
-               struct http_descriptor  *resp = clt->clt_descresp;
-               struct stat              gzst;
-               int                      gzfd;
-               char                     gzpath[PATH_MAX];
-
-               /* check Accept-Encoding header */
-               key.kv_key = "Accept-Encoding";
-               r = kv_find(&req->http_headers, &key);
-
-               if (r != NULL && strstr(r->kv_value, "gzip") != NULL) {
-                       /* append ".gz" to path and check existence */
-                       ret = snprintf(gzpath, sizeof(gzpath), "%s.gz", 
path);
-                       if (ret < 0 || (size_t)ret >= sizeof(gzpath)) {
-                               close(fd);
-                               return (500);
-                       }
-
-                       if ((gzfd = open(gzpath, O_RDONLY)) != -1) {
-                               /* .gz must be a file, and not older */
-                               if (fstat(gzfd, &gzst) != -1 &&
-                                   S_ISREG(gzst.st_mode) &&
-                                   timespeccmp(&gzst.st_mtim, &st.st_mtim,
-                                   >=)) {
-  kv_add(&resp->http_headers,
-                                           "Content-Encoding", "gzip");
-                                       /* Use original file timestamp */
-                                       gzst.st_mtim = st.st_mtim;
-                                       st = gzst;
-                                       close(fd);
-                                       fd = gzfd;
-                               } else {
-                                       close(gzfd);
-                               }
-                       }
-               }
+       /* Send encoded file if client and server agree. */
+       if((ret = server_file_encoded_path(clt, path, &fd, &st)) != 0) {
+               close(fd);
+               return (ret);
         }

         return (server_file_request(env, clt, media, fd, &st));
@@ -823,4 +791,124 @@ parse_range_spec(char *str, size_t size,
                 return (0);

         return (1);
+}
+
+/*
+ * If brotli-static is enabled,
+ *   and the connection is inside of TLS,
+ *   and the file isn't older than the html
+ *   and (if gzip-static is enabled, and
+ *       the brotli isn't older than the gzip),
+ *   then send the brotli version.
+ * Else if gzip-static is enabled,
+ *   and the file exists
+ *   and it's newer than the html version,
+ *   then send the gzip version.
+ * Otherwise send the regular file.
+ */
+static int
+server_file_encoded_path(const struct client *clt, const char *path,
+    int *fd, struct stat *st)
+{
+       struct kv               *r, key;
+       struct server_config    *srv_conf = clt->clt_srv_conf;
+       int                      ret;
+       struct http_descriptor  *req = clt->clt_descreq;
+       struct http_descriptor  *resp = clt->clt_descresp;
+       struct stat              brst;
+       int                      brfd = -1;
+       char                     brpath[PATH_MAX];
+       struct stat              gzst;
+       int                      gzfd = -1;
+       char                     gzpath[PATH_MAX];
+
+       /* did client send Accept-Encoding header? */
+       key.kv_key = "Accept-Encoding";
+       r = kv_find(&req->http_headers, &key);
+
+       /* if brotli-static is set, client accepts brotli, and
+        * connection is inside tls, set brst & brfd for later use. */
+       if ((srv_conf->flags & SRVFLAG_BROTLI_STATIC) &&
+           r != NULL &&
+           strstr(r->kv_value, "br") != NULL &&
+           clt->clt_tls_ctx != NULL) {
+               /* append .br... */
+               ret = snprintf(brpath, sizeof(brpath), "%s.br", path);
+               if (ret < 0 || (size_t)ret >= sizeof(brpath)) {
+                       return (500);
+               }
+               /* ...and check existence. */
+               if ((brfd = open(brpath, O_RDONLY)) != -1) {
+                       if(fstat(brfd, &brst) == -1) {
+                               /* couldn't stat file, bail */
+                               close(brfd);
+                               return (500);
+                       }
+               }
+       }
+
+       /* if client accepts gzip */
+       if ((srv_conf->flags & SRVFLAG_GZIP_STATIC) &&
+           r != NULL && strstr(r->kv_value, "gzip") != NULL) {
+               /* append .gz... */
+               ret = snprintf(gzpath, sizeof(gzpath), "%s.gz", path);
+               if (ret < 0 || (size_t)ret >= sizeof(gzpath)) {
+                       /* brfd might be open here */
+                       if (brfd != -1) close(brfd);
+                       return (500);
+               }
+               /* ...and check existence. */
+               if ((gzfd = open(gzpath, O_RDONLY)) != -1) {
+                       if(fstat(gzfd, &gzst) == -1) {
+                               /* brfd might be open here */
+                               if (brfd != -1) close(brfd);
+                               close(gzfd);
+                               return (500);
+                       }
+               }
+       }
+
+       if (brfd != -1 &&
+           S_ISREG(brst.st_mode) &&
+           timespeccmp(&brst.st_mtim, &st->st_mtim, >=) &&
+           (gzfd == -1 || timespeccmp(&brst.st_mtim, &gzst.st_mtim, >=)))
+       {
+               /* if .br is a valid option, is a regular file, not 
older than
+                  the base file, and not older than the gzip version (if it
+                  exists), then serve the .br file. */
+               kv_add(&resp->http_headers,
+                      "Content-Encoding", "br");
+               /* Use original file timestamp */
+               brst.st_mtim = st->st_mtim;
+               /* Point fd at the br version. */
+               *st = brst;
+               close(*fd);
+               *fd = brfd;
+               brfd = -1;
+       } else if (gzfd != -1 &&
+                  S_ISREG(gzst.st_mode) &&
+                  timespeccmp(&gzst.st_mtim, &st->st_mtim, >=))
+       {
+               /* otherwise, if .gz is a valid option, is a regular
+                  file, and not older than the base file, serve it.*/
+               kv_add(&resp->http_headers,
+                      "Content-Encoding", "gzip");
+               /* Use original file timestamp */
+               gzst.st_mtim = st->st_mtim;
+               /* Point fd at the gz version. */
+               *st = gzst;
+               close(*fd);
+               *fd = gzfd;
+               gzfd = -1;
+       }
+
+       /*
+        *  It's possible to reach this point with fd, brfd, and gzfd
+        *  all open and valid; eg, if *.br and *.gz both exist, but
+        *  are older than the uncompressed version.
+        */
+       if (brfd != -1) close(brfd);
+       if (gzfd != -1) close(gzfd);
+
+       return (0);
  }