This patch adds a serve_gzip option. When enabled, If the client requests path, then serve path.gz if it exists and the client accepts Content-Encoding: gzip.
diff -up httpd.orig/config.c httpd/config.c --- httpd.orig/config.c Sat May 1 15:03:11 2021 +++ httpd/config.c Sat May 1 15:45:43 2021 @@ -568,12 +568,12 @@ config_getserver_config(struct httpd *env, struct serv &parent->default_type, sizeof(struct media_type)); } - f = SRVFLAG_PATH_REWRITE|SRVFLAG_NO_PATH_REWRITE; +/* f = SRVFLAG_PATH_REWRITE|SRVFLAG_NO_PATH_REWRITE; if ((srv_conf->flags & f) == 0) { srv_conf->flags |= parent->flags & f; (void)strlcpy(srv_conf->path, parent->path, sizeof(srv_conf->path)); - } + } */ f = SRVFLAG_SERVER_HSTS; srv_conf->flags |= parent->flags & f; diff -up httpd.orig/httpd.conf.5 httpd/httpd.conf.5 --- httpd.orig/httpd.conf.5 Sat May 1 15:03:11 2021 +++ httpd/httpd.conf.5 Sat May 1 16:02:44 2021 @@ -397,6 +397,13 @@ a browser's preload list. Signal to the receiving user agent that this host and all sub domains of the host's domain should be considered HSTS hosts. .El +.It Ic serve_gzip +If the client requests +.Nm path , +then serve +.Nm path.gz +if it exists and the client accepts +.Nm Content-Encoding: gzip . .It Ic listen on Ar address Oo Ic tls Oc Ic port Ar number Set the listen address and port. This statement can be specified multiple times. diff -up httpd.orig/httpd.h httpd/httpd.h --- httpd.orig/httpd.h Sat May 1 15:03:11 2021 +++ httpd/httpd.h Sat May 1 15:41:58 2021 @@ -390,17 +390,17 @@ SPLAY_HEAD(client_tree, client); #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_PATH_REWRITE 0x01000000 +#define SRVFLAG_NO_PATH_REWRITE 0x02000000 */ #define SRVFLAG_LOCATION_FOUND 0x40000000 #define SRVFLAG_LOCATION_NOT_FOUND 0x80000000 - +#define SRVFLAG_SERVER_GZIP 0x01000000 #define SRVFLAG_BITS \ "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \ "\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG" \ "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \ "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \ - "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE\31PATH\32NO_PATH" \ + "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE\31SERVER_GZIP" \ "\37LOCATION_FOUND\40LOCATION_NOT_FOUND" #define TCPFLAG_NODELAY 0x01 @@ -684,7 +684,7 @@ int server_headers(struct client *, void *, int (*)(struct client *, struct kv *, void *), void *); int server_writeresponse_http(struct client *); int server_response_http(struct client *, unsigned int, - struct media_type *, off_t, time_t); + struct media_type *, off_t, time_t, int); void server_reset_http(struct client *); void server_close_http(struct client *); int server_response(struct httpd *, struct client *); diff -up httpd.orig/parse.y httpd/parse.y --- httpd.orig/parse.y Sat May 1 15:03:11 2021 +++ httpd/parse.y Sat May 1 15:48:31 2021 @@ -138,7 +138,7 @@ typedef struct { %token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LIFETIME %token LISTEN LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY OCSP ON PORT PREFORK %token PROTOCOLS REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TICKET -%token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST +%token TIMEOUT TLS TYPE TYPES HSTS SERVE_GZIP 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 <v.string> STRING @@ -644,6 +644,9 @@ serveroptsl : LISTEN ON STRING opttls port { } srv->srv_conf.flags |= SRVFLAG_SERVER_HSTS; } + | SERVE_GZIP { + srv->srv_conf.flags |= SRVFLAG_SERVER_GZIP; + } ; optfound : /* empty */ { $$ = 0; } @@ -925,23 +928,7 @@ requestflags_l : requestflags optcommanl requestflags_ | requestflags optnl ; -requestflags : REWRITE STRING { - if (strlcpy(srv->srv_conf.path, $2, - sizeof(srv->srv_conf.path)) >= - sizeof(srv->srv_conf.path)) { - yyerror("request path too long"); - free($2); - YYERROR; - } - free($2); - srv->srv_conf.flags |= SRVFLAG_PATH_REWRITE; - srv->srv_conf.flags &= ~SRVFLAG_NO_PATH_REWRITE; - } - | NO REWRITE { - srv->srv_conf.flags |= SRVFLAG_NO_PATH_REWRITE; - srv->srv_conf.flags &= ~SRVFLAG_PATH_REWRITE; - } - | STRIP NUMBER { +requestflags : STRIP NUMBER { if ($2 < 0 || $2 > INT_MAX) { yyerror("invalid strip number"); YYERROR; @@ -1431,6 +1418,7 @@ lookup(char *s) { "rewrite", REWRITE }, { "root", ROOT }, { "sack", SACK }, + { "serve_gzip", SERVE_GZIP }, { "server", SERVER }, { "socket", SOCKET }, { "strip", STRIP }, diff -up httpd.orig/server_file.c httpd/server_file.c --- httpd.orig/server_file.c Sat May 1 15:03:11 2021 +++ httpd/server_file.c Sat May 1 15:40:14 2021 @@ -50,7 +50,7 @@ int server_file_modified_since(struct http_descripto int server_file_method(struct client *); int parse_range_spec(char *, size_t, struct range *); int parse_ranges(struct client *, char *, size_t); - +int accepts_gzip(struct http_descriptor *); int server_file_access(struct httpd *env, struct client *clt, char *path, size_t len) @@ -221,30 +221,48 @@ server_file_request(struct httpd *env, struct client * struct server_config *srv_conf = clt->clt_srv_conf; struct media_type *media; const char *errstr = NULL; - int fd = -1, ret, code = 500; size_t bufsiz; - + char gzip_path[PATH_MAX]; + int fd = -1, ret, code = 500, len = 0, gzip = 0; if ((ret = server_file_method(clt)) != 0) { code = ret; goto abort; } if ((ret = server_file_modified_since(clt->clt_descreq, st)) != -1) { - /* send the header without a body */ + /* send the header without a body */ media = media_find_config(env, srv_conf, path); if ((ret = server_response_http(clt, ret, media, -1, - MINIMUM(time(NULL), st->st_mtim.tv_sec))) == -1) + MINIMUM(time(NULL), st->st_mtim.tv_sec), 0)) == -1) goto fail; goto done; - } + } - /* Now open the file, should be readable or we have another problem */ - if ((fd = open(path, O_RDONLY)) == -1) - goto abort; +/* If the client accepts gzip, try to find a .gz file */ + if (srv_conf->flags & SRVFLAG_SERVER_GZIP && + (gzip = accepts_gzip(clt->clt_descreq))) { + len = snprintf(gzip_path, sizeof(gzip_path), "%s.gz", path); + if (len > 0 && len < PATH_MAX) { + fd = open(gzip_path, O_RDONLY); + } + if (fd == -1) + gzip = 0; + else { + if (fstat(fd, st) == -1) + goto abort; + } + } + + /* Otherwise, open the file, should be readable or we have + * another problem */ + if (fd == -1) { + if ((fd = open(path, O_RDONLY)) == -1) + goto abort; + } media = media_find_config(env, srv_conf, path); ret = server_response_http(clt, 200, media, st->st_size, - MINIMUM(time(NULL), st->st_mtim.tv_sec)); + MINIMUM(time(NULL), st->st_mtim.tv_sec), gzip); switch (ret) { case -1: goto fail; @@ -379,7 +397,7 @@ server_partial_file_request(struct httpd *env, struct r->range_toread = TOREAD_HTTP_RANGE; ret = server_response_http(clt, 206, media, content_length, - MINIMUM(time(NULL), st->st_mtim.tv_sec)); + MINIMUM(time(NULL), st->st_mtim.tv_sec), 0); switch (ret) { case -1: goto fail; @@ -555,7 +573,7 @@ server_file_index(struct httpd *env, struct client *cl media = media_find_config(env, srv_conf, "index.html"); ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), - dir_mtime); + dir_mtime, 0); switch (ret) { case -1: goto fail; @@ -765,4 +783,34 @@ parse_range_spec(char *str, size_t size, struct range return (0); return (1); +} + +int +accepts_gzip(struct http_descriptor *desc) { + struct kv key, *encodings; + char *cursor; + char *element; + + key.kv_key = "Accept-Encoding"; + encodings = kv_find(&desc->http_headers, &key); + if (encodings == NULL) + return (0); + + if (encodings->kv_value == NULL) + return (0); + + cursor = encodings->kv_value; + while ((element = strsep(&cursor, ",")) != NULL) { + if (strstr(element, "gzip") == NULL) + continue; + + /* For simplicity, assume that if a qvalue is given, we + cannot provide gzip */ + if (strchr(element, ';') != NULL) + return (0); + + return (1); + } + + return (0); } diff -up httpd.orig/server_http.c httpd/server_http.c --- httpd.orig/server_http.c Sat May 1 15:03:11 2021 +++ httpd/server_http.c Sat May 1 15:44:46 2021 @@ -1325,18 +1325,18 @@ server_response(struct httpd *httpd, struct client *cl return (-1); } - /* Optional rewrite */ + /* Optional rewrite * if (srv_conf->flags & SRVFLAG_PATH_REWRITE) { - /* Expand macros */ + * Expand macros * if (server_expand_http(clt, srv_conf->path, path, sizeof(path)) == NULL) goto fail; - /* + * * Reset and update the query. The updated query must already * be URL encoded - either specified by the user or by using the * original $QUERY_STRING. - */ + * free(desc->http_query_alias); desc->http_query_alias = NULL; if ((query = strchr(path, '?')) != NULL) { @@ -1345,7 +1345,7 @@ server_response(struct httpd *httpd, struct client *cl goto fail; } - /* Canonicalize the updated request path */ + * Canonicalize the updated request path * if (canonicalize_path(path, path, sizeof(path)) == NULL) goto fail; @@ -1358,14 +1358,14 @@ server_response(struct httpd *httpd, struct client *cl if ((desc->http_path_alias = strdup(path)) == NULL) goto fail; - /* Now search for the updated location */ + * Now search for the updated location * if ((srv_conf = server_getlocation(clt, desc->http_path_alias)) == NULL) { server_abort_http(clt, 500, desc->http_path_alias); return (-1); } } - +*/ if (clt->clt_toread > 0 && (size_t)clt->clt_toread > srv_conf->maxrequestbody) { server_abort_http(clt, 413, NULL); @@ -1468,7 +1468,7 @@ server_locationaccesstest(struct server_config *srv_co int server_response_http(struct client *clt, unsigned int code, - struct media_type *media, off_t size, time_t mtime) + struct media_type *media, off_t size, time_t mtime, int compressed) { struct server_config *srv_conf = clt->clt_srv_conf; struct http_descriptor *desc = clt->clt_descreq; @@ -1516,7 +1516,15 @@ server_response_http(struct client *clt, unsigned int if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 || kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL) return (-1); +/* Prevent caches from serving incorrectly encoded content */ + if (kv_add(&resp->http_headers, "Vary", "Accept-Encoding") == NULL) + return (-1); + /* Set encoding type */ + if (compressed) { + if (kv_add(&resp->http_headers, "Content-Encoding", "gzip") == NULL) + return (-1); + } /* HSTS header */ if (srv_conf->flags & SRVFLAG_SERVER_HSTS && srv_conf->flags & SRVFLAG_TLS) {