From: Adam Mullins 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 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       STRING  %token       NUMBER  %type          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);  }