Index | Thread | Search

From:
"Omar Polo" <op@omarpolo.com>
Subject:
Re: httpd: add custom HTTP header support #2
To:
Rafael Sadowski <rafael@sizeofvoid.org>
Cc:
tech@openbsd.org, kirill@openbsd.org
Date:
Mon, 20 Jul 2026 12:21:21 +0200

Download raw body.

Thread
Rafael Sadowski <rafael@sizeofvoid.org> wrote:
> [...]
> I've already sent this diff to kirill@, and here is the rebase following
> the latest commits. This diff fix small typos/minor issues and impl. the
> error path:

haven't had the time yet to properly test it, and sorry if i'm joining
the thread so late, but fwiw I wanted this for quite a long time, thanks
for working on it :)

it's overall okay op@, i'm just leaving some nits below, but nothing
major.

> [...]
> diff --git a/parse.y b/parse.y
> index 65664e1..4b967ca 100644
> --- a/parse.y
> +++ b/parse.y
> [...]
> +optalways	:
> +		/* empty */ { $$ = 0; }
> +		| ALWAYS    { $$ = 1; }
> +		;
> +
> +header		: HEADER REMOVE STRING optalways	{
> +			struct custom_header	*hdr;
> +
> +			if (strlen($3) > HTTPD_HEADER_NAME_MAX - 1) {
> +				yyerror("header name too long (max %d)",
> +				    HTTPD_HEADER_NAME_MAX - 1);
> +				free($3);
> +				YYERROR;
> +			}
> +
> +			if (header_name_forbidden($3)) {
> +				free($3);
> +				YYERROR;
> +			}
> +
> +			if ((hdr = calloc(1, sizeof(*hdr))) == NULL)
> +				fatal("out of memory");
> +
> +			if ((hdr->name = strdup($3)) == NULL ||
> +			    (hdr->value = strdup("")) == NULL)	/* never NULL */
> +				fatal("out of memory");
> +			free($3);

why not just

+			hdr->name = $3;

instead of of dup'ing plus freeing it?  same for the hunks below, where
we could also have `hdr->value = $4'.

> +			hdr->flags = HEADER_REMOVE;
> +			if ($4)
> +				hdr->flags |= HEADER_ALWAYS;
> +			TAILQ_INSERT_TAIL(&srv->srv_conf.headers, hdr, entry);
> +		}
> +		| HEADER ADD STRING STRING optalways	{
> +			struct custom_header	*hdr;
> +
> +			if (strlen($3) > HTTPD_HEADER_NAME_MAX - 1) {
> +				yyerror("header name too long (max %d)",
> +				    HTTPD_HEADER_NAME_MAX - 1);
> +				free($3);
> +				free($4);
> +				YYERROR;
> +			}
> +			if (header_name_forbidden($3)) {
> +				free($3);
> +				free($4);
> +				YYERROR;
> +			}
> +			if (strlen($4) > HTTPD_HEADER_VAL_MAX - 1) {
> +				yyerror("header value too long (max %d)",
> +				    HTTPD_HEADER_VAL_MAX - 1);
> +				free($3);
> +				free($4);
> +				YYERROR;
> +			}
> +
> +			if ((hdr = calloc(1, sizeof(*hdr))) == NULL)
> +				fatal("out of memory");
> +
> +			if ((hdr->name = strdup($3)) == NULL ||
> +			    (hdr->value = strdup($4)) == NULL)
> +				fatal("out of memory");
> +
> +			free($3);
> +			free($4);

i meant here (and in the next block as well) where it could have been just

			hdr->name = $3;
			hdr->value = $4;

nad move on.

> +			hdr->flags = HEADER_ADD;
> +			if ($5)
> +				hdr->flags |= HEADER_ALWAYS;
> +			TAILQ_INSERT_TAIL(&srv->srv_conf.headers, hdr, entry);
> [...]
> --- a/server_http.c
> +++ b/server_http.c
> [...]
> @@ -975,8 +985,8 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg)
>  
>  	if (srv_conf->flags & SRVFLAG_SERVER_HSTS &&
>  	    srv_conf->flags & SRVFLAG_TLS) {
> -		if (asprintf(&hstsheader, "Strict-Transport-Security: "
> -		    "max-age=%d%s%s\r\n", srv_conf->hsts_max_age,
> +		if (asprintf(&hstsheader, "max-age=%d%s%s",
> +		    srv_conf->hsts_max_age,
>  		    srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
>  		    "; includeSubDomains" : "",
>  		    srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
> @@ -984,45 +994,52 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg)
>  			hstsheader = NULL;
>  			goto done;

since we only use hstsheader in this if block, why not moving the
variable locally in this block?  doing this free() + hstsheader = NULL
dance when in `goto done' we don't touch it anymore is a bit weird.

>  		}
> +
> +		ret = kv_add(&http_headers, "Strict-Transport-Security",
> +		    hstsheader) == NULL;
> +		free(hstsheader);
> +		hstsheader = NULL;
> +		if (ret)
> +			goto done;
>  	}
> +	has_body = !((code >= 100 && code < 200) || code == 204);
> +	if (has_body) {
> +		ret = snprintf(hbodylen, sizeof(hbodylen), "%zd", bodylen);
> +		if (ret < 0 || (size_t)ret >= sizeof(hbodylen))
> +			goto done;
>  
> [...]
> +int
> +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. */

i've found this comment a bit hard to parse.  maybe "only include
headers marked as ALWAYS on failure"?

> +		if (!(hdr->flags & HEADER_ALWAYS) && !http_is_success(code)) {
> +			print_custom_header("skip", hdr);
> +			continue;
> +		}
> +
> +		search.kv_key = hdr->name;
> +
> +		/* deletes all existing headers of the same key */
> +		if (hdr->flags & HEADER_REMOVE) {
> +			print_custom_header("remove", hdr);
> +			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) {
> +			print_custom_header("set", hdr);
> +			while ((kv = kv_find(headers, &search)) != NULL)
> +				kv_delete(headers, kv);
> +
> +			if (kv_add(headers, hdr->name, hdr->value) == NULL)
> +				return (-1);
> +		/* appends a new header without checking for duplicates */
> +		} else if (hdr->flags & HEADER_ADD) {
> +			print_custom_header("add", hdr);
> +			if (kv_add(headers, hdr->name, hdr->value) == NULL)
> +				return (-1);
> +		}
> +	}
> +	return (0);
> +}