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) {

Reply via email to