From: Kirill A. Korinsky Subject: Re: httpd: add custom HTTP header support #2 To: Rafael Sadowski Cc: tech@openbsd.org, "Anthony J. Bentley" , Stuart Henderson Date: Thu, 19 Mar 2026 01:31:34 +0100 On Wed, 18 Mar 2026 17:48:54 +0100, Rafael Sadowski wrote: > > On Wed Feb 11, 2026 at 12:59:27AM +0100, Rafael Sadowski wrote: > > This is the second attempt to add custom HTTP header support to httpd. > > > > After receiving initial feedback and advice from Stuart, I changed and > > improved the whole idea (This made it very powerful but also somewhat more > > complex to test) and the syntax: > > > > Allow httpd.conf to set/add/remove custom HTTP response headers or > > suppress existing ones. This enables httpd to add security headers, > > custom metadata, or remove unwanted headers without modifying > > app/fastcgi code. > > > > Three new directives are added: > > > > header set name value [always] > > Set a custom HTTP response header with the specified name > > and value. If a header with the same name is already > > present in the response, its value will be replaced. The > > header is added to successful responses (2xx and 3xx > > status codes) by default. > > > > header add name value [always] > > Add a custom HTTP response header with the specified name > > and value. Unlike set, this option appends the header > > even if one with the same name already exists, allowing > > for multiple headers with the same name. The header is > > added to successful responses (2xx and 3xx status codes) > > by default. > > > > header remove name > > Suppress the HTTP response header with the specified > > name. This can be used to remove headers added by > > default, such as "Last-Modified", as well as headers > > inherited from a parent server configuration. > > > > If always is specified, the header will be included in all > > responses, including error pages (4xx and 5xx status codes). > > > > Header names are limited to 127 characters and values to 511 > > characters. Headers defined in a location block are inherited > > from the server context and override defined headers with the > > same name. If you do not wish to inherit these, you can remove > > them again with remove. > > > > Example configuration: > > > > server "example.com" { > > listen on * tls port 443 > > > > header add "X-Frame-Options" "SAMEORIGIN" always > > header add "X-Content-Type-Options" "nosniff" always > > header set "X-Powered-By" "OpenBSD httpd" > > > > location "/api/*" { > > # This location defines its own headers > > header add "Access-Control-Allow-Origin" "*" > > # Override from server context > > header set "Set-Cookie" "id=5; HttpOnly; Secure" > > # Remove from server context > > header remove "X-Frame-Options" > > } > > } > > > > The implementation follows the existing pattern form FastCGI parameters. > > So I didn't have to invent anything new. Much of the code is from > > existing patterns (so the potential bugs too). Just to give one example: > > The entire IMSG part. > > > > The motivation was to have something similar to ngx_http_headers_module > > without relayd coming into play. For this reason I follow the nginx pattern: > > > > "Adds the specified field to a response header provided that the > > response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, > > 307 (1.1.16, 1.0.13), or 308 (1.13.0). Parameter value can contain > > variables." > > -- https://nginx.org/en/docs/http/ngx_http_headers_module.html > > > > I tried to test all combinations (and sure I missed some) but feel > > comfortable with them. My goal was not to modify current behavior. > > > > I would like to ask for more and more tests (big wild setups) and maybe > > OKs. > > Is anyone interested? > Sorry for extreamly long response time. I think this is needed and I mostly OK with it a few things which I had noticed. 1. Order of headers is a bit unexpected. If I not mistaken it is reversed config order. Probably worth to respect the config order. 2. Server generated replies, seems, to be excluded from new custom header logic. Which includes backend / FastCGI error responses. > > > > Rafael > > > > diff --git a/usr.sbin/httpd/config.c b/usr.sbin/httpd/config.c > > index 300a5f2caca..5c10f4218f1 100644 > > --- a/usr.sbin/httpd/config.c > > +++ b/usr.sbin/httpd/config.c > > @@ -196,6 +196,7 @@ clear_config_server_ptrs(struct server_config *cfg) > > > > /* clear TAILQ_HEAD */ > > memset(&cfg->fcgiparams, 0, sizeof(cfg->fcgiparams)); > > + memset(&cfg->headers, 0, sizeof(cfg->headers)); > > > > /* clear TAILQ_ENTRY */ > > memset(&cfg->entry, 0, sizeof(cfg->entry)); > > @@ -270,7 +271,11 @@ config_setserver(struct httpd *env, struct server *srv) > > > > /* Configure TLS if necessary. */ > > config_setserver_tls(env, srv); > > - } else { > > + /* Configure custom headers if necessary. */ > > + config_setserver_headers(env, srv); > > + > > + } else if (id == PROC_SERVER && > > + (srv->srv_conf.flags & SRVFLAG_LOCATION)) { > > if (proc_composev(ps, id, IMSG_CFG_SERVER, > > iov, c) != 0) { > > log_warn("%s: failed to compose " > > @@ -278,7 +283,20 @@ config_setserver(struct httpd *env, struct server *srv) > > __func__, srv->srv_conf.name); > > return (-1); > > } > > + /* Configure FCGI parameters if necessary. */ > > + config_setserver_fcgiparams(env, srv); > > > > + /* Configure custom headers if necessary. */ > > + config_inherit_headers(env, srv); > > + config_setserver_headers(env, srv); > > + } else { > > + if (proc_composev(ps, id, IMSG_CFG_SERVER, > > + iov, c) != 0) { > > + log_warn("%s: failed to compose " > > + "IMSG_CFG_SERVER imsg for `%s'", > > + __func__, srv->srv_conf.name); > > + return (-1); > > + } > > /* Configure FCGI parameters if necessary. */ > > config_setserver_fcgiparams(env, srv); > > } > > @@ -442,6 +460,162 @@ config_setserver_fcgiparams(struct httpd *env, struct server *srv) > > return (0); > > } > > > > +int > > +config_getserver_headers(struct httpd *env, struct imsg *imsg) > > +{ > > + struct server *srv; > > + struct server_config *srv_conf, *iconf; > > + struct custom_header *hdr; > > + uint32_t id; > > + size_t c, nc, len; > > + uint8_t *p = imsg->data; > > + > > + len = sizeof(nc) + sizeof(id); > > + if (IMSG_DATA_SIZE(imsg) < len) { > > + log_debug("%s: invalid message length", __func__); > > + return (-1); > > + } > > + > > + memcpy(&nc, p, sizeof(nc)); /* number of headers */ > > + p += sizeof(nc); > > + > > + memcpy(&id, p, sizeof(id)); /* server conf id */ > > + srv_conf = serverconfig_byid(id); > > + p += sizeof(id); > > + > > + len += nc*sizeof(*hdr); > > + if (IMSG_DATA_SIZE(imsg) < len) { > > + log_debug("%s: invalid message length", __func__); > > + return (-1); > > + } > > + > > + /* Find associated server config */ > > + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { > > + if (srv->srv_conf.id == id) { > > + srv_conf = &srv->srv_conf; > > + break; > > + } > > + TAILQ_FOREACH(iconf, &srv->srv_hosts, entry) { > > + if (iconf->id == id) { > > + srv_conf = iconf; > > + break; > > + } > > + } > > + } > > + > > + /* Fetch custom headers */ > > + for (c = 0; c < nc; c++) { > > + if ((hdr = calloc(1, sizeof(*hdr))) == NULL) > > + fatalx("headers out of memory"); > > + memcpy(hdr, p, sizeof(*hdr)); > > + TAILQ_INSERT_HEAD(&srv_conf->headers, hdr, entry); > > + > > + p += sizeof(*hdr); > > + } > > + > > + return (0); > > +} > > + > > +static int > > +header_exists(struct server_config *srv_conf, const char *name) > > +{ > > + struct custom_header *hdr; > > + > > + TAILQ_FOREACH(hdr, &srv_conf->headers, entry) { > > + if (strcasecmp(hdr->name, name) == 0) > > + return (1); > > + } > > + return (0); > > +} > > + > > +/* > > + * Inherit headers from parent server, skipping those > > + * already defined in the location. > > + */ > > +void > > +config_inherit_headers(struct httpd *env, struct server *srv) > > +{ > > + struct server *parent_srv; > > + struct server_config *srv_conf = &srv->srv_conf; > > + struct custom_header *hdr, *hdr_copy; > > + > > + if (!(srv_conf->flags & SRVFLAG_LOCATION)) > > + return; > > + > > + /* Find parent server by parent_id */ > > + TAILQ_FOREACH(parent_srv, env->sc_servers, srv_entry) { > > + if (parent_srv->srv_conf.id == srv_conf->parent_id) > > + break; > > + } > > + > > + if (parent_srv == NULL) > > + return; > > + > > + TAILQ_FOREACH(hdr, &parent_srv->srv_conf.headers, entry) { > > + if (header_exists(srv_conf, hdr->name)) { > > + DPRINTF("%s: skipping header \"%s\" from parent " > > + "\"%s\", overridden in location \"%s\"", > > + __func__, hdr->name, > > + parent_srv->srv_conf.name, srv_conf->location); > > + continue; > > + } > > + > > + if ((hdr_copy = calloc(1, sizeof(*hdr_copy))) == NULL) > > + fatal("out of memory"); > > + > > + strlcpy(hdr_copy->name, hdr->name, sizeof(hdr_copy->name)); > > + strlcpy(hdr_copy->value, hdr->value, sizeof(hdr_copy->value)); > > + hdr_copy->flags = hdr->flags; > > + > > + TAILQ_INSERT_TAIL(&srv_conf->headers, hdr_copy, entry); > > + DPRINTF("%s: inheriting header \"%s\" from parent \"%s\" " > > + "to location \"%s\"", __func__, hdr->name, > > + parent_srv->srv_conf.name, srv_conf->location); > > + } > > +} > > + > > +int > > +config_setserver_headers(struct httpd *env, struct server *srv) > > +{ > > + struct privsep *ps = env->sc_ps; > > + struct server_config *srv_conf = &srv->srv_conf; > > + struct custom_header *hdr; > > + struct iovec *iov; > > + size_t c = 0, nc = 0; > > + > > + DPRINTF("%s: sending headers for \"%s[%u]\" to %s fd %d", __func__, > > + srv_conf->name, srv_conf->id, ps->ps_title[PROC_SERVER], > > + srv->srv_s); > > + > > + if (TAILQ_EMPTY(&srv_conf->headers)) /* nothing to do */ > > + return (0); > > + > > + TAILQ_FOREACH(hdr, &srv_conf->headers, entry) { > > + nc++; > > + } > > + if ((iov = calloc(nc + 2, sizeof(*iov))) == NULL) > > + return (-1); > > + > > + iov[c].iov_base = &nc; /* number of headers */ > > + iov[c++].iov_len = sizeof(nc); > > + iov[c].iov_base = &srv_conf->id; /* server config id */ > > + iov[c++].iov_len = sizeof(srv_conf->id); > > + > > + TAILQ_FOREACH(hdr, &srv_conf->headers, entry) { /* push headers */ > > + iov[c].iov_base = hdr; > > + iov[c++].iov_len = sizeof(*hdr); > > + } > > + if (proc_composev(ps, PROC_SERVER, IMSG_CFG_HEADERS, iov, c) != 0) { > > + log_warn("%s: failed to compose IMSG_CFG_HEADERS imsg for " > > + "`%s'", __func__, srv_conf->name); > > + free(iov); > > + return (-1); > > + } > > + free(iov); > > + > > + return (0); > > +} > > + > > int > > config_setserver_tls(struct httpd *env, struct server *srv) > > { > > diff --git a/usr.sbin/httpd/httpd.conf.5 b/usr.sbin/httpd/httpd.conf.5 > > index 3053709a359..72d80628ac6 100644 > > --- a/usr.sbin/httpd/httpd.conf.5 > > +++ b/usr.sbin/httpd/httpd.conf.5 > > @@ -464,6 +464,57 @@ Enable static gzip compression to save bandwidth. > > 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 header Ar option > > +Manipulate HTTP response headers. > > +Multiple > > +.Ic header > > +statements may be specified. > > +Valid options are: > > +.Bl -tag -width Ds > > +.It Ic set Ar name Ar value Op Ic always > > +Set a custom HTTP response header with the specified > > +.Ar name > > +and > > +.Ar value . > > +If a header with the same > > +.Ar name > > +is already present in the response, its value will be replaced. > > +The header is added to successful responses > > +(2xx and 3xx status codes) by default. > > +.It Ic add Ar name Ar value Op Ic always > > +Add a custom HTTP response header with the specified > > +.Ar name > > +and > > +.Ar value . > > +Unlike > > +.Ic set , > > +this option appends the header even if one with the same > > +.Ar name > > +already exists, allowing for multiple headers with the same name. > > +The header is added to successful responses > > +(2xx and 3xx status codes) by default. > > +.It Ic remove Ar name > > +Suppress the HTTP response header with the specified > > +.Ar name . > > +This can be used to remove headers added by default, such as > > +.Qq Last-Modified , > > +as well as headers inherited from a parent server configuration. > > +.El > > +.Pp > > +If > > +.Ic always > > +is specified, the header will be included in all responses, > > +including error pages (4xx and 5xx status codes). > > +.Pp > > +Header names are limited to 127 characters and values to 511 characters. > > +Headers defined in a > > +.Ic location > > +block are inherited from the > > +.Ic server > > +context and override defined headers with the same name. > > +If you do not wish to inherit these, you can remove them again with > > +.Ic remove . > > + > > .It Ic hsts Oo Ar option Oc > > Enable HTTP Strict Transport Security. > > Valid options are: > > @@ -895,6 +946,32 @@ server "example.com" { > > } > > } > > .Ed > > +.Pp > > +Custom HTTP headers can be added to responses for security or compatibility: > > +.Bd -literal -offset indent > > +server "example.com" { > > + listen on * tls port 443 > > + > > + header set "X-Powered-By" "OpenBSD httpd" > > + > > + header set "X-Content-Type-Options" "nosniff" always > > + header set "X-Frame-Options" "DENY" always > > + > > + header add "Set-Cookie" "id=23; HttpOnly; Secure" > > + > > + # Remove default Last-Modified header > > + header remove "Last-Modified" > > + > > + location "/api/*" { > > + # This location defines its own headers > > + header add "Access-Control-Allow-Origin" "*" > > + # Override from server context > > + header set "Set-Cookie" "id=5; HttpOnly; Secure" > > + # Remove from server context > > + header remove "X-Frame-Options" > > + } > > +} > > +.Ed > > .Sh SEE ALSO > > .Xr htpasswd 1 , > > .Xr glob 7 , > > diff --git a/usr.sbin/httpd/httpd.h b/usr.sbin/httpd/httpd.h > > index baf11d1b523..096f68f02b0 100644 > > --- a/usr.sbin/httpd/httpd.h > > +++ b/usr.sbin/httpd/httpd.h > > @@ -209,6 +209,7 @@ enum imsg_type { > > IMSG_CFG_MEDIA, > > IMSG_CFG_AUTH, > > IMSG_CFG_FCGI, > > + IMSG_CFG_HEADERS, > > IMSG_CFG_DONE, > > IMSG_LOG_ACCESS, > > IMSG_LOG_ERROR, > > @@ -470,6 +471,19 @@ struct fastcgi_param { > > }; > > TAILQ_HEAD(server_fcgiparams, fastcgi_param); > > > > +struct custom_header { > > + char name[128]; > > + char value[512]; > > + uint8_t flags; > > +#define HEADER_REMOVE 0x01 > > +#define HEADER_ADD 0x02 > > +#define HEADER_SET 0x04 > > +#define HEADER_ALWAYS 0x08 > > + > > + TAILQ_ENTRY(custom_header) entry; > > +}; > > +TAILQ_HEAD(server_headers, custom_header); > > + > > struct server_config { > > uint32_t id; > > uint32_t parent_id; > > @@ -540,6 +554,7 @@ struct server_config { > > > > struct server_fcgiparams fcgiparams; > > int fcgistrip; > > + struct server_headers headers; > > char errdocroot[HTTPD_ERRDOCROOT_MAX]; > > > > TAILQ_ENTRY(server_config) entry; > > @@ -673,6 +688,8 @@ void server_http(void); > > int server_httpdesc_init(struct client *); > > void server_read_http(struct bufferevent *, void *); > > void server_abort_http(struct client *, unsigned int, const char *); > > +void server_custom_headers(struct server_config *, struct kvtree *, > > + unsigned int); > > unsigned int > > server_httpmethod_byname(const char *); > > const char > > @@ -825,9 +842,12 @@ int config_getcfg(struct httpd *, struct imsg *); > > int config_setserver(struct httpd *, struct server *); > > int config_setserver_tls(struct httpd *, struct server *); > > int config_setserver_fcgiparams(struct httpd *, struct server *); > > +int config_setserver_headers(struct httpd *, struct server *); > > +void config_inherit_headers(struct httpd *, struct server *); > > int config_getserver(struct httpd *, struct imsg *); > > int config_getserver_tls(struct httpd *, struct imsg *); > > int config_getserver_fcgiparams(struct httpd *, struct imsg *); > > +int config_getserver_headers(struct httpd *, struct imsg *); > > int config_setmedia(struct httpd *, struct media_type *); > > int config_getmedia(struct httpd *, struct imsg *); > > int config_setauth(struct httpd *, struct auth *); > > diff --git a/usr.sbin/httpd/parse.y b/usr.sbin/httpd/parse.y > > index 326b249662f..d2d6282439f 100644 > > --- a/usr.sbin/httpd/parse.y > > +++ b/usr.sbin/httpd/parse.y > > @@ -142,6 +142,7 @@ typedef struct { > > %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS REWRITE > > %token CA CLIENT CRL OPTIONAL PARAM FORWARDED FOUND NOT > > %token ERRDOCS GZIPSTATIC BANNER > > +%token HEADER ADD ALWAYS REMOVE SET > > %token STRING > > %token NUMBER > > %type port > > @@ -323,6 +324,7 @@ server : SERVER optmatch STRING { > > SPLAY_INIT(&srv->srv_clients); > > TAILQ_INIT(&srv->srv_hosts); > > TAILQ_INIT(&srv_conf->fcgiparams); > > + TAILQ_INIT(&srv_conf->headers); > > > > TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry); > > } '{' optnl serveropts_l '}' { > > @@ -557,6 +559,7 @@ serveroptsl : LISTEN ON STRING opttls port { > > | root > > | directory > > | banner > > + | header > > | logformat > > | fastcgi > > | authenticate > > @@ -643,6 +646,7 @@ serveroptsl : LISTEN ON STRING opttls port { > > srv = s; > > srv_conf = &srv->srv_conf; > > SPLAY_INIT(&srv->srv_clients); > > + TAILQ_INIT(&srv_conf->headers); > > } '{' optnl serveropts_l '}' { > > struct server *s = NULL; > > uint32_t f; > > @@ -710,6 +714,147 @@ banner : BANNER { > > } > > ; > > > > +header : HEADER REMOVE STRING { > > + struct custom_header *hdr; > > + > > + if ((hdr = calloc(1, sizeof(*hdr))) == NULL) > > + fatal("out of memory"); > > + > > + if (strlcpy(hdr->name, $3, sizeof(hdr->name)) >= > > + sizeof(hdr->name)) { > > + yyerror("header name truncated"); > > + free($3); > > + free(hdr); > > + YYERROR; > > + } > > + if (strcmp("Server", hdr->name) == 0) { > > + yyerror("'header remover Server' " > > + "ignored, use 'no banner'"); > > + free($3); > > + free(hdr); > > + YYERROR; > > + } > > + free($3); > > + > > + hdr->flags = HEADER_REMOVE; > > + TAILQ_INSERT_TAIL(&srv->srv_conf.headers, hdr, entry); > > + } > > + | HEADER ADD STRING STRING { > > + struct custom_header *hdr; > > + > > + if ((hdr = calloc(1, sizeof(*hdr))) == NULL) > > + fatal("out of memory"); > > + > > + if (strlcpy(hdr->name, $3, sizeof(hdr->name)) >= > > + sizeof(hdr->name)) { > > + yyerror("header name truncated"); > > + free($3); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($3); > > + > > + if (strlcpy(hdr->value, $4, sizeof(hdr->value)) >= > > + sizeof(hdr->value)) { > > + yyerror("header value truncated"); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($4); > > + > > + hdr->flags = HEADER_ADD; > > + TAILQ_INSERT_TAIL(&srv->srv_conf.headers, hdr, entry); > > + } > > + | HEADER ADD STRING STRING ALWAYS { > > + struct custom_header *hdr; > > + > > + if ((hdr = calloc(1, sizeof(*hdr))) == NULL) > > + fatal("out of memory"); > > + > > + if (strlcpy(hdr->name, $3, sizeof(hdr->name)) >= > > + sizeof(hdr->name)) { > > + yyerror("header name truncated"); > > + free($3); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($3); > > + > > + if (strlcpy(hdr->value, $4, sizeof(hdr->value)) >= > > + sizeof(hdr->value)) { > > + yyerror("header value truncated"); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($4); > > + > > + hdr->flags = HEADER_ADD; > > + hdr->flags |= HEADER_ALWAYS; > > + TAILQ_INSERT_TAIL(&srv->srv_conf.headers, hdr, entry); > > + } > > + | HEADER SET STRING STRING { > > + struct custom_header *hdr; > > + > > + if ((hdr = calloc(1, sizeof(*hdr))) == NULL) > > + fatal("out of memory"); > > + > > + if (strlcpy(hdr->name, $3, sizeof(hdr->name)) >= > > + sizeof(hdr->name)) { > > + yyerror("header name truncated"); > > + free($3); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($3); > > + > > + if (strlcpy(hdr->value, $4, sizeof(hdr->value)) >= > > + sizeof(hdr->value)) { > > + yyerror("header value truncated"); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($4); > > + > > + hdr->flags = HEADER_SET; > > + TAILQ_INSERT_TAIL(&srv->srv_conf.headers, hdr, entry); > > + } > > + | HEADER SET STRING STRING ALWAYS { > > + struct custom_header *hdr; > > + > > + if ((hdr = calloc(1, sizeof(*hdr))) == NULL) > > + fatal("out of memory"); > > + > > + if (strlcpy(hdr->name, $3, sizeof(hdr->name)) >= > > + sizeof(hdr->name)) { > > + yyerror("header name truncated"); > > + free($3); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($3); > > + > > + if (strlcpy(hdr->value, $4, sizeof(hdr->value)) >= > > + sizeof(hdr->value)) { > > + yyerror("header value truncated"); > > + free($4); > > + free(hdr); > > + YYERROR; > > + } > > + free($4); > > + > > + hdr->flags = HEADER_SET; > > + hdr->flags |= HEADER_ALWAYS; > > + TAILQ_INSERT_TAIL(&srv->srv_conf.headers, hdr, entry); > > + } > > + ; > > + > > optfound : /* empty */ { $$ = 0; } > > | FOUND { $$ = 1; } > > | NOT FOUND { $$ = -1; } > > @@ -1447,7 +1592,9 @@ lookup(char *s) > > /* this has to be sorted always */ > > static const struct keywords keywords[] = { > > { "access", ACCESS }, > > + { "add", ADD }, > > { "alias", ALIAS }, > > + { "always", ALWAYS }, > > { "authenticate", AUTHENTICATE}, > > { "auto", AUTO }, > > { "backlog", BACKLOG }, > > @@ -1475,6 +1622,7 @@ lookup(char *s) > > { "forwarded", FORWARDED }, > > { "found", FOUND }, > > { "gzip-static", GZIPSTATIC }, > > + { "header", HEADER }, > > { "hsts", HSTS }, > > { "include", INCLUDE }, > > { "index", INDEX }, > > @@ -1500,6 +1648,7 @@ lookup(char *s) > > { "prefork", PREFORK }, > > { "preload", PRELOAD }, > > { "protocols", PROTOCOLS }, > > + { "remove", REMOVE }, > > { "request", REQUEST }, > > { "requests", REQUESTS }, > > { "return", RETURN }, > > @@ -1507,6 +1656,7 @@ lookup(char *s) > > { "root", ROOT }, > > { "sack", SACK }, > > { "server", SERVER }, > > + { "set", SET }, > > { "socket", SOCKET }, > > { "strip", STRIP }, > > { "style", STYLE }, > > @@ -2298,12 +2448,24 @@ server_inherit(struct server *src, struct server_config *alias, > > struct server_config *addr) > > { > > struct server *dst, *s, *dstl; > > + struct custom_header *hdr, *nhdr; > > > > if ((dst = calloc(1, sizeof(*dst))) == NULL) > > fatal("out of memory"); > > > > /* Copy the source server and assign a new Id */ > > memcpy(&dst->srv_conf, &src->srv_conf, sizeof(dst->srv_conf)); > > + > > + TAILQ_INIT(&dst->srv_conf.headers); > > + TAILQ_FOREACH(hdr, &src->srv_conf.headers, entry) { > > + if ((nhdr = calloc(1, sizeof(*nhdr))) == NULL) > > + fatal("out of memory"); > > + strlcpy(nhdr->name, hdr->name, sizeof(nhdr->name)); > > + strlcpy(nhdr->value, hdr->value, sizeof(nhdr->value)); > > + nhdr->flags = hdr->flags; > > + TAILQ_INSERT_TAIL(&dst->srv_conf.headers, nhdr, entry); > > + } > > + > > if ((dst->srv_conf.tls_cert_file = > > strdup(src->srv_conf.tls_cert_file)) == NULL) > > fatal("out of memory"); > > @@ -2393,6 +2555,18 @@ server_inherit(struct server *src, struct server_config *alias, > > fatal("out of memory"); > > > > memcpy(&dstl->srv_conf, &s->srv_conf, sizeof(dstl->srv_conf)); > > + > > + /* Copy custom headers from source location */ > > + TAILQ_INIT(&dstl->srv_conf.headers); > > + TAILQ_FOREACH(hdr, &s->srv_conf.headers, entry) { > > + if ((nhdr = calloc(1, sizeof(*nhdr))) == NULL) > > + fatal("out of memory"); > > + strlcpy(nhdr->name, hdr->name, sizeof(nhdr->name)); > > + strlcpy(nhdr->value, hdr->value, sizeof(nhdr->value)); > > + nhdr->flags = hdr->flags; > > + TAILQ_INSERT_TAIL(&dstl->srv_conf.headers, nhdr, entry); > > + } > > + > > strlcpy(dstl->srv_conf.name, alias->name, > > sizeof(dstl->srv_conf.name)); > > > > diff --git a/usr.sbin/httpd/server.c b/usr.sbin/httpd/server.c > > index a38cf018d81..ed97f3f7e3c 100644 > > --- a/usr.sbin/httpd/server.c > > +++ b/usr.sbin/httpd/server.c > > @@ -470,6 +470,7 @@ void > > serverconfig_free(struct server_config *srv_conf) > > { > > struct fastcgi_param *param, *tparam; > > + struct custom_header *hdr, *thdr; > > > > free(srv_conf->return_uri); > > free(srv_conf->tls_ca_file); > > @@ -485,6 +486,9 @@ serverconfig_free(struct server_config *srv_conf) > > > > TAILQ_FOREACH_SAFE(param, &srv_conf->fcgiparams, entry, tparam) > > free(param); > > + > > + TAILQ_FOREACH_SAFE(hdr, &srv_conf->headers, entry, thdr) > > + free(hdr); > > } > > > > void > > @@ -503,6 +507,7 @@ serverconfig_reset(struct server_config *srv_conf) > > srv_conf->tls_ocsp_staple = NULL; > > srv_conf->tls_ocsp_staple_file = NULL; > > TAILQ_INIT(&srv_conf->fcgiparams); > > + TAILQ_INIT(&srv_conf->headers); > > } > > > > struct server * > > @@ -1366,6 +1371,9 @@ server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) > > case IMSG_CFG_FCGI: > > config_getserver_fcgiparams(httpd_env, imsg); > > break; > > + case IMSG_CFG_HEADERS: > > + config_getserver_headers(httpd_env, imsg); > > + break; > > case IMSG_CFG_DONE: > > config_getcfg(httpd_env, imsg); > > break; > > diff --git a/usr.sbin/httpd/server_fcgi.c b/usr.sbin/httpd/server_fcgi.c > > index c5f9917204c..02165ba3e32 100644 > > --- a/usr.sbin/httpd/server_fcgi.c > > +++ b/usr.sbin/httpd/server_fcgi.c > > @@ -723,6 +723,8 @@ server_fcgi_header(struct client *clt, unsigned int code) > > return (-1); > > } > > > > + server_custom_headers(srv_conf, &resp->http_headers, code); > > + > > /* Date header is mandatory and should be added as late as possible */ > > key.kv_key = "Date"; > > if (kv_find(&resp->http_headers, &key) == NULL && > > diff --git a/usr.sbin/httpd/server_http.c b/usr.sbin/httpd/server_http.c > > index afdb73f243f..96faa0fb4e5 100644 > > --- a/usr.sbin/httpd/server_http.c > > +++ b/usr.sbin/httpd/server_http.c > > @@ -50,10 +50,12 @@ void server_httpdesc_free(struct http_descriptor *); > > int server_http_authenticate(struct server_config *, > > struct client *); > > static int http_version_num(char *); > > +static int http_is_success(unsigned int code); > > char *server_expand_http(struct client *, const char *, > > char *, size_t); > > char *replace_var(char *, const char *, const char *); > > char *read_errdoc(const char *, const char *); > > +char *get_always_custom_headers(struct server_config *); > > > > static struct http_method http_methods[] = HTTP_METHODS; > > static struct http_error http_errors[] = HTTP_ERRORS; > > @@ -219,6 +221,25 @@ http_version_num(char *version) > > } > > return (0); > > } > > +static int > > +http_is_success(unsigned int code) > > +{ > > + switch (code) { > > + case 200: > > + case 201: > > + case 204: > > + case 206: > > + case 301: > > + case 302: > > + case 303: > > + case 304: > > + case 307: > > + case 308: > > + return (0); > > + default: > > + return (1); > > + } > > +} > > > > void > > server_read_http(struct bufferevent *bev, void *arg) > > @@ -892,6 +913,7 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg) > > char tmbuf[32], hbuf[128], *hstsheader = NULL; > > char *clenheader = NULL; > > char *bannerheader = NULL, *bannertoken = NULL; > > + char *customheaders = NULL; > > char buf[IBUF_READ_SIZE]; > > char *escapedmsg = NULL; > > char cstr[5]; > > @@ -1060,6 +1082,8 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg) > > goto done; > > } > > > > + customheaders = get_always_custom_headers(srv_conf); > > + > > /* Add basic HTTP headers */ > > if (asprintf(&httpmsg, > > "HTTP/1.0 %03d %s\r\n" > > @@ -1070,6 +1094,7 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg) > > "%s" > > "%s" > > "%s" > > + "%s" > > "\r\n" > > "%s", > > code, httperr, tmbuf, > > @@ -1077,6 +1102,7 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg) > > clenheader == NULL ? "" : clenheader, > > extraheader == NULL ? "" : extraheader, > > hstsheader == NULL ? "" : hstsheader, > > + customheaders == NULL ? "" : customheaders, > > desc->http_method == HTTP_METHOD_HEAD || clenheader == NULL ? > > "" : body) == -1) > > goto done; > > @@ -1092,6 +1118,7 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg) > > free(clenheader); > > free(bannerheader); > > free(bannertoken); > > + free(customheaders); > > if (msg == NULL) > > msg = "\"\""; > > if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) { > > @@ -1561,6 +1588,73 @@ server_locationaccesstest(struct server_config *srv_conf, const char *path) > > (ret == 0 && SRVFLAG_LOCATION_NOT_FOUND & srv_conf->flags)); > > } > > > > +void > > +server_custom_headers(struct server_config *srv_conf, struct kvtree *headers, > > + unsigned int code) > > +{ > > + struct custom_header *hdr; > > + struct kv *kv, search; > > + > > + TAILQ_FOREACH(hdr, &srv_conf->headers, entry) { > > + /* Only include headers not marked ALWAYS on success. */ > > + if (!(hdr->flags & HEADER_ALWAYS) && > > + http_is_success(code) != 0) > > + continue; > > + > > + search.kv_key = hdr->name; > > + > > + /* deletes all existing headers of the same key */ > > + if (hdr->flags & HEADER_REMOVE) { > > + while ((kv = kv_find(headers, &search)) != NULL) > > + kv_delete(headers, kv); > > + /* replaces all existing headers of the same name */ > > + } else if (hdr->flags & HEADER_SET) { > > + while ((kv = kv_find(headers, &search)) != NULL) > > + kv_delete(headers, kv); > > + > > + if (kv_add(headers, hdr->name, hdr->value) == NULL) > > + return; > > + /* appends a new header without checking for duplicates */ > > + } else if (hdr->flags & HEADER_ADD) { > > + if (kv_add(headers, hdr->name, hdr->value) == NULL) > > + return; > > + } > > + } > > +} > > + > > +/* > > + * Build a raw custom HTTP header that only includes headers marked as always > > + */ > > +char * > > +get_always_custom_headers(struct server_config *srv_conf) > > +{ > > + struct custom_header *hdr; > > + char *headers = NULL; > > + char *tmp = NULL; > > + > > + TAILQ_FOREACH(hdr, &srv_conf->headers, entry) { > > + if (!(hdr->flags & HEADER_ALWAYS) > > + || hdr->flags & HEADER_REMOVE) > > + continue; > > + > > + if (headers == NULL) { > > + if (asprintf(&headers, "%s: %s\r\n", hdr->name, > > + hdr->value) == -1) { > > + return (NULL); > > + } > > + } else { > > + if (asprintf(&tmp, "%s%s: %s\r\n", headers, hdr->name, > > + hdr->value) == -1) { > > + free(headers); > > + return (NULL); > > + } > > + free(headers); > > + headers = tmp; > > + } > > + } > > + return (headers); > > +} > > + > > int > > server_response_http(struct client *clt, unsigned int code, > > struct media_type *media, off_t size, time_t mtime) > > @@ -1627,6 +1721,7 @@ server_response_http(struct client *clt, unsigned int code, > > "; preload" : "") == -1) > > return (-1); > > } > > + server_custom_headers(srv_conf, &resp->http_headers, code); > > > > /* Date header is mandatory and should be added as late as possible */ > > if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 || > > -- wbr, Kirill