Index | Thread | Search

From:
Rob Schmersel <rob.schmersel@bahnhof.se>
Subject:
Re: add custom HTTP header support to httpd
To:
tech@openbsd.org
Date:
Tue, 30 Dec 2025 08:20:07 +0100

Download raw body.

Thread
On Fri, 26 Dec 2025 12:01:22 +0100
Rafael Sadowski <rafael@sizeofvoid.org> wrote:

> Add custom HTTP header support to httpd
> 
> Allow httpd.conf to add custom HTTP response headers or suppress
> existing ones. This enables httpd to add security headers, custom
> metadata, or remove unwanted headers without modifying app code.
> 
> Three new directives are added:
> 
>     header add <name> <value>
>         Add custom header to successful responses (2xx, 3xx)
> 
>     header always add <name> <value>
>         Add custom header to all responses including errors

Not a developer, but would it not be nicer to have:
  header add [option] <name> <value>

with option at the moment only being: always
So the above 2 become:
header add <name> <value>
header add always <name> <value>


>     header hide <name>
>         Suppress a response header
> 
> Headers can be specified in server blocks and are inherited by
> location blocks unless the location defines its own headers.
> 
> Example configuration:
> 
>     server "example.com" {
>         listen on * tls port 443
> 
>         header always add "X-Frame-Options" "SAMEORIGIN"
>         header always add "X-Content-Type-Options" "nosniff"
>         header add "X-Powered-By" "OpenBSD httpd"
> 
>         location "/api/*" {
>             header add "Access-Control-Allow-Origin" "*"
>         }
>     }
> 
> 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
> 
> Tests, feedback... welcome.
> 
> Rafael
> 
> diff --git a/usr.sbin/httpd/config.c b/usr.sbin/httpd/config.c
> index b45081129b7..b115a1a8efa 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));
> @@ -281,6 +282,9 @@ config_setserver(struct httpd *env, struct server
> *srv) 
>  			/* Configure FCGI parameters if necessary. */
>  			config_setserver_fcgiparams(env, srv);
> +
> +			/* Configure custom headers if necessary. */
> +			config_setserver_headers(env, srv);
>  		}
>  	}
>  
> @@ -442,6 +446,104 @@ 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);
> +}
> +
> +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)
>  {
> @@ -640,6 +742,20 @@ config_getserver_config(struct httpd *env,
> struct server *srv, 
>  		srv_conf->flags |= parent->flags & SRVFLAG_NO_BANNER;
>  
> +		/* Inherit custom headers from parent if location
> has none */
> +		if (TAILQ_EMPTY(&srv_conf->headers)) {
> +			struct custom_header *hdr, *hdr_copy;
> +			TAILQ_FOREACH(hdr, &parent->headers, entry) {
> +				if ((hdr_copy = calloc(1,
> sizeof(*hdr_copy))) == NULL)
> +					goto fail;
> +				/* Copy only data fields, not
> TAILQ_ENTRY */
> +				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: %s %d location \"%s\", "
>  		    "parent \"%s[%u]\", flags: %s",
>  		    __func__, ps->ps_title[privsep_process],
> ps->ps_instance, diff --git a/usr.sbin/httpd/httpd.conf.5
> b/usr.sbin/httpd/httpd.conf.5 index b3673284e0b..1b3cbea2504 100644
> --- a/usr.sbin/httpd/httpd.conf.5
> +++ b/usr.sbin/httpd/httpd.conf.5
> @@ -464,6 +464,32 @@ 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 header statements may be specified.
> +Valid options are:
> +.Bl -tag -width Ds
> +.It Ic add Ar name Ar value
> +Add a custom HTTP response header with the specified
> +.Ar name
> +and
> +.Ar value .
> +The header will be added to successful responses (2xx and 3xx status
> codes). +.It Ic always add Ar name Ar value
> +Add a custom HTTP response header that is included in all responses,
> +including error responses (4xx and 5xx status codes).
> +.It Ic hide Ar name
> +Suppress an HTTP response header with the specified
> +.Ar name .
> +This can be used to remove headers that are added by default,
> +such as
> +.Qq Last-Modified
> +or custom headers inherited from a parent server configuration.
> +.El
> +.Pp
> +Header names are limited to 127 characters and values to 511
> characters. +Headers specified in a location block inherit headers
> from the parent +server configuration unless the location defines its
> own headers. .It Ic hsts Oo Ar option Oc
>  Enable HTTP Strict Transport Security.
>  Valid options are:
> @@ -895,6 +921,29 @@ 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
> +
> +	# Add security headers to all responses
> +	header always add "X-Frame-Options" "SAMEORIGIN"
> +	header always add "X-Content-Type-Options" "nosniff"
> +	header always add "Content-Security-Policy" "default-src
> 'self'" +
> +	# Add custom header to successful responses only
> +	header add "X-Powered-By" "OpenBSD httpd"
> +
> +	# Hide default Last-Modified header
> +	header hide "Last-Modified"
> +
> +	location "/api/*" {
> +		# Location inherits parent headers and can add more
> +		header add "Access-Control-Allow-Origin" "*"
> +	}
> +}
> +.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..a6dd2fb8a22 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,17 @@ struct fastcgi_param {
>  };
>  TAILQ_HEAD(server_fcgiparams, fastcgi_param);
>  
> +struct custom_header {
> +	char			name[128];
> +	char			value[512];
> +	uint8_t			flags;
> +#define HEADER_HIDE		0x01
> +#define HEADER_ALWAYS		0x02
> +
> +	TAILQ_ENTRY(custom_header) entry;
> +};
> +TAILQ_HEAD(server_headers, custom_header);
> +
>  struct server_config {
>  	uint32_t		 id;
>  	uint32_t		 parent_id;
> @@ -540,6 +552,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 +686,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_add_custom_headers(struct server_config
> *, struct kvtree *,
> +	    int, unsigned int);
>  unsigned int
>  	 server_httpmethod_byname(const char *);
>  const char
> @@ -825,9 +840,11 @@ 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 *); 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..f5fbd02e524 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 HIDE
>  %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,8 @@ serveroptsl	: LISTEN ON STRING opttls
> port	{ srv = s;
>  			srv_conf = &srv->srv_conf;
>  			SPLAY_INIT(&srv->srv_clients);
> +			TAILQ_INIT(&srv_conf->fcgiparams);
> +			TAILQ_INIT(&srv_conf->headers);
>  		} '{' optnl serveropts_l '}'	{
>  			struct server	*s = NULL;
>  			uint32_t	 f;
> @@ -710,6 +715,82 @@ banner		: BANNER		{
>  		}
>  		;
>  
> +header		: HEADER HIDE 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;
> +			}
> +			free($3);
> +
> +			hdr->flags = HEADER_HIDE;
> +			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 = 0;
> +			TAILQ_INSERT_TAIL(&srv->srv_conf.headers,
> hdr, entry);
> +		}
> +		| HEADER ALWAYS ADD STRING STRING	{
> +			struct custom_header	*hdr;
> +
> +			if ((hdr = calloc(1, sizeof(*hdr))) == NULL)
> +				fatal("out of memory");
> +
> +			if (strlcpy(hdr->name, $4,
> sizeof(hdr->name)) >=
> +			    sizeof(hdr->name)) {
> +				yyerror("header name truncated");
> +				free($4);
> +				free($5);
> +				free(hdr);
> +				YYERROR;
> +			}
> +			free($4);
> +
> +			if (strlcpy(hdr->value, $5,
> sizeof(hdr->value)) >=
> +			    sizeof(hdr->value)) {
> +				yyerror("header value truncated");
> +				free($5);
> +				free(hdr);
> +				YYERROR;
> +			}
> +			free($5);
> +
> +			hdr->flags = HEADER_ALWAYS;
> +			TAILQ_INSERT_TAIL(&srv->srv_conf.headers,
> hdr, entry);
> +		}
> +		;
> +
>  optfound	: /* empty */	{ $$ = 0; }
>  		| FOUND		{ $$ = 1; }
>  		| NOT FOUND	{ $$ = -1; }
> @@ -1447,7 +1528,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 +1558,8 @@ lookup(char *s)
>  		{ "forwarded",		FORWARDED },
>  		{ "found",		FOUND },
>  		{ "gzip-static",	GZIPSTATIC },
> +		{ "header",		HEADER },
> +		{ "hide",		HIDE },
>  		{ "hsts",		HSTS },
>  		{ "include",		INCLUDE },
>  		{ "index",		INDEX },
> diff --git a/usr.sbin/httpd/server.c b/usr.sbin/httpd/server.c
> index 5d5063b6480..e63ae942035 100644
> --- a/usr.sbin/httpd/server.c
> +++ b/usr.sbin/httpd/server.c
> @@ -503,6 +503,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 *
> @@ -1369,6 +1370,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 f3c01a459b0..b0e57194d39 100644
> --- a/usr.sbin/httpd/server_fcgi.c
> +++ b/usr.sbin/httpd/server_fcgi.c
> @@ -730,6 +730,8 @@ server_fcgi_header(struct client *clt, unsigned
> int code) kv_add(&resp->http_headers, "Date", tmbuf) == NULL))
>  		return (-1);
>  
> +	server_add_custom_headers(srv_conf, &resp->http_headers, 0,
> code); +
>  	if (server_writeresponse_http(clt) == -1 ||
>  	    server_bufferevent_print(clt, "\r\n") == -1 ||
>  	    server_headers(clt, resp, server_writeheader_http, NULL)
> == -1 || diff --git a/usr.sbin/httpd/server_http.c
> b/usr.sbin/httpd/server_http.c index 485b67bc5e8..18a9fe2e1b3 100644
> --- a/usr.sbin/httpd/server_http.c
> +++ b/usr.sbin/httpd/server_http.c
> @@ -54,6 +54,7 @@ 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;
> @@ -891,6 +892,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];
> @@ -1059,6 +1061,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"
> @@ -1069,6 +1073,7 @@ server_abort_http(struct client *clt, unsigned
> int code, const char *msg) "%s"
>  	    "%s"
>  	    "%s"
> +	    "%s"
>  	    "\r\n"
>  	    "%s",
>  	    code, httperr, tmbuf,
> @@ -1076,6 +1081,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;
> @@ -1091,6 +1097,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) { @@ -1560,6 +1567,76 @@ server_locationaccesstest(struct
> server_config *srv_conf, const char *path) (ret == 0 &&
> SRVFLAG_LOCATION_NOT_FOUND & srv_conf->flags)); }
>  
> +/*
> + * Add or remove custom headers configured for this server.
> + * Headers marked as always are included in all responses.
> + * Regular headers are only added to 2xx and 3xx responses.
> + */
> +void
> +server_add_custom_headers(struct server_config *srv_conf,
> +    struct kvtree *headers, int is_error, unsigned int code)
> +{
> +	struct custom_header	*hdr;
> +	struct kv		*kv, search;
> +
> +	TAILQ_FOREACH(hdr, &srv_conf->headers, entry) {
> +		if (is_error && !(hdr->flags & HEADER_ALWAYS))
> +			continue;
> +
> +		if (!is_error && !(hdr->flags & HEADER_ALWAYS)) {
> +			if (code != 200 && code != 201 && code !=
> 204 &&
> +			    code != 206 && code != 301 && code !=
> 302 &&
> +			    code != 303 && code != 304 && code !=
> 307 &&
> +			    code != 308)
> +				continue;
> +		}
> +
> +		if (hdr->flags & HEADER_HIDE) {
> +			/* Remove header from response */
> +			search.kv_key = hdr->name;
> +			if ((kv = kv_find(headers, &search)) != NULL)
> +				kv_delete(headers, kv);
> +		} else {
> +			/* Add header to response */
> +			kv_add(headers, hdr->name, hdr->value);
> +		}
> +	}
> +}
> +
> +/*
> + * Build a raw custom HTTP header.
> + * only includes headers marked as always.
> + * Returns the string or NULL on error/no headers.
> + */
> +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_HIDE)
> +			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 +1704,8 @@ server_response_http(struct client *clt,
> unsigned int code, return (-1);
>  	}
>  
> +	server_add_custom_headers(srv_conf, &resp->http_headers, 0,
> code); +
>  	/* Date header is mandatory and should be added as late as
> possible */ if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <=
> 0 || kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
>