Download raw body.
add custom HTTP header support to httpd
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
>
> 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.
I hadn't yet tested it, but I will, anyway it reads good and it triggers the
first feedback: shall we backout "no banner" commit?
It seems that header hide should be enough to make it in general way.
>
> 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)
>
--
wbr, Kirill
add custom HTTP header support to httpd