Download raw body.
httpd: add custom HTTP header support #2
On Wed, Mar 18, 2026 at 05:48:54PM +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?
I think this is a sensible addition which is needed when you want to play
with content caching headers. My problem is that I have too much other
stuff on my plate to properly review this diff.
> >
> > 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 <v.string> STRING
> > %token <v.number> NUMBER
> > %type <v.port> 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 ||
> >
>
--
:wq Claudio
httpd: add custom HTTP header support #2