From cd32c597a71f93a238959959d0d141ddc8768fe9 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 28 Jan 2026 16:50:06 -0400 Subject: [PATCH] Fix regression with header removal removing whole prefixes (#21020) * Fix regression with header removing removing whole prefixes The header removal code looked for the colon for key-value at the wrong place, so it would overzealously remove headers. Tweak that condition, and make the alternative condition only active if it's set (with the remove prefix op case). Fixes GH-21018. * avoid reading past the actual length * Rename variable to be more clear --- .../tests/general_functions/gh21018.phpt | 21 +++++++++++++++++++ main/SAPI.c | 11 +++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 ext/standard/tests/general_functions/gh21018.phpt diff --git a/ext/standard/tests/general_functions/gh21018.phpt b/ext/standard/tests/general_functions/gh21018.phpt new file mode 100644 index 0000000000000..249cfb515aeb6 --- /dev/null +++ b/ext/standard/tests/general_functions/gh21018.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-21018 (header() removes headers with the same prefix) +--INI-- +expose_php=On +--CGI-- +--FILE-- + +--EXPECTF-- +array(3) { + [0]=> + string(%d) "X-Powered-By: PHP/%s" + [1]=> + string(9) "a-test: 1" + [2]=> + string(4) "a: 1" +} diff --git a/main/SAPI.c b/main/SAPI.c index 6709d467e34fe..2fd7e18adcea5 100644 --- a/main/SAPI.c +++ b/main/SAPI.c @@ -601,7 +601,7 @@ static void sapi_update_response_code(int ncode) * since zend_llist_del_element only removes one matched item once, * we should remove them manually */ -static void sapi_remove_header(zend_llist *l, char *name, size_t len, size_t header_len) +static void sapi_remove_header(zend_llist *l, char *name, size_t len, size_t prefix_len) { sapi_header_struct *header; zend_llist_element *next; @@ -610,8 +610,13 @@ static void sapi_remove_header(zend_llist *l, char *name, size_t len, size_t hea while (current) { header = (sapi_header_struct *)(current->data); next = current->next; - if (header->header_len > header_len - && (header->header[header_len] == ':' || len > header_len) + /* + * prefix_len is set for DELETE_PREFIX (used for deleting i.e. + * "Set-Cookie: PHPSESSID=", where we need more than just key) + * look for the : otherwise + */ + if (header->header_len > len + && (header->header[len] == ':' || (prefix_len && len > prefix_len)) && !strncasecmp(header->header, name, len)) { if (current->prev) { current->prev->next = next;