Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add headers_send_early_and_clear() function for HTTP Early Hints #7025

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ static int phar_file_action(phar_archive_data *phar, phar_entry_info *info, char
sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
efree((void *) ctr.line);

if (FAILURE == sapi_send_headers()) {
if (FAILURE == sapi_send_headers(/* last_headers */ true)) {
zend_bailout();
}

Expand Down Expand Up @@ -300,7 +300,7 @@ static void phar_do_403(char *entry, size_t entry_len) /* {{{ */
ctr.line_len = sizeof("HTTP/1.0 403 Access Denied")-1;
ctr.line = "HTTP/1.0 403 Access Denied";
sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
sapi_send_headers();
sapi_send_headers(/* last_headers */ true);
PHPWRITE("<html>\n <head>\n <title>Access Denied</title>\n </head>\n <body>\n <h1>403 - File ", sizeof("<html>\n <head>\n <title>Access Denied</title>\n </head>\n <body>\n <h1>403 - File ") - 1);
PHPWRITE("Access Denied</h1>\n </body>\n</html>", sizeof("Access Denied</h1>\n </body>\n</html>") - 1);
}
Expand All @@ -324,7 +324,7 @@ static void phar_do_404(phar_archive_data *phar, char *fname, size_t fname_len,
ctr.line_len = sizeof("HTTP/1.0 404 Not Found")-1;
ctr.line = "HTTP/1.0 404 Not Found";
sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
sapi_send_headers();
sapi_send_headers(/* last_headers */ true);
PHPWRITE("<html>\n <head>\n <title>File Not Found</title>\n </head>\n <body>\n <h1>404 - File ", sizeof("<html>\n <head>\n <title>File Not Found</title>\n </head>\n <body>\n <h1>404 - File ") - 1);
PHPWRITE("Not Found</h1>\n </body>\n</html>", sizeof("Not Found</h1>\n </body>\n</html>") - 1);
}
Expand Down Expand Up @@ -783,7 +783,7 @@ PHP_METHOD(Phar, webPhar)
}

sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
sapi_send_headers();
sapi_send_headers(/* last_headers */ true);
efree((void *) ctr.line);
zend_bailout();
}
Expand Down
2 changes: 2 additions & 0 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ function headers_sent(&$filename = null, &$line = null): bool {}

function headers_list(): array {}

function headers_send_early_and_clear(): bool {}

/* {{{ html.c */

function htmlspecialchars(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE, ?string $encoding = null, bool $double_encode = true): string {}
Expand Down
6 changes: 5 additions & 1 deletion ext/standard/basic_functions_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 810b8bfbdf037702fcaec2ff81998c2bc2cefae8 */
* Stub hash: f09f8df1b2704720615642414ea106471b139e33 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
Expand Down Expand Up @@ -770,6 +770,8 @@ ZEND_END_ARG_INFO()

#define arginfo_headers_list arginfo_ob_list_handlers

#define arginfo_headers_send_early_and_clear arginfo_ob_flush

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_htmlspecialchars, 0, 1, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "ENT_QUOTES | ENT_SUBSTITUTE")
Expand Down Expand Up @@ -2445,6 +2447,7 @@ ZEND_FUNCTION(setcookie);
ZEND_FUNCTION(http_response_code);
ZEND_FUNCTION(headers_sent);
ZEND_FUNCTION(headers_list);
ZEND_FUNCTION(headers_send_early_and_clear);
ZEND_FUNCTION(htmlspecialchars);
ZEND_FUNCTION(htmlspecialchars_decode);
ZEND_FUNCTION(html_entity_decode);
Expand Down Expand Up @@ -3080,6 +3083,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(http_response_code, arginfo_http_response_code)
ZEND_FE(headers_sent, arginfo_headers_sent)
ZEND_FE(headers_list, arginfo_headers_list)
ZEND_FE(headers_send_early_and_clear, arginfo_headers_send_early_and_clear)
ZEND_FE(htmlspecialchars, arginfo_htmlspecialchars)
ZEND_FE(htmlspecialchars_decode, arginfo_htmlspecialchars_decode)
ZEND_FE(html_entity_decode, arginfo_html_entity_decode)
Expand Down
20 changes: 19 additions & 1 deletion ext/standard/head.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ PHP_FUNCTION(header_remove)

PHPAPI int php_header(void)
{
if (sapi_send_headers()==FAILURE || SG(request_info).headers_only) {
if (sapi_send_headers(/* last_headers */ true) == FAILURE || SG(request_info).headers_only) {
return 0; /* don't allow output */
} else {
return 1; /* allow output */
Expand Down Expand Up @@ -383,3 +383,21 @@ PHP_FUNCTION(http_response_code)
RETURN_LONG(SG(sapi_headers).http_response_code);
}
/* }}} */

PHP_FUNCTION(headers_send_early_and_clear)
{
ZEND_PARSE_PARAMETERS_NONE();

if (SG(headers_sent)) {
php_error_docref(NULL, E_WARNING, "Headers already sent");
RETURN_FALSE;
}

if (sapi_send_headers(/* last_header */ false) == FAILURE) {
RETURN_FALSE;
}

zend_llist_clean(&SG(sapi_headers).headers);
SG(sapi_headers).http_response_code = 200;
RETURN_TRUE;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Using headers_send_early_and_clear() for HTTP early hints (no extra headers)
--CGI--
--FILE--
<?php

header('HTTP/1.1 103 Early Hints');
header('Link: </style.css>; rel=preload; as=style');
headers_send_early_and_clear();
// Headers should be cleared.
var_dump(headers_list());
?>
--EXPECTHEADERS--
Status: 103 Early Hints
Link: </style.css>; rel=preload; as=style
--EXPECT--
Content-type: text/html; charset=UTF-8

array(0) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Using headers_send_early_and_clear() for HTTP early hints (extra headers)
--CGI--
--FILE--
<?php

header('HTTP/1.1 103 Early Hints');
header('Link: </style.css>; rel=preload; as=style');
headers_send_early_and_clear();
header('Location: http://example.com/');
echo "Foo\n";
?>
--EXPECTHEADERS--
Status: 103 Early Hints
Link: </style.css>; rel=preload; as=style
--EXPECT--
Status: 302 Found
Location: http://example.com/
Content-type: text/html; charset=UTF-8

Foo
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Using headers_send_early_and_clear() for HTTP early hints (after output)
--CGI--
--FILE--
<?php

echo "Foo\n";
var_dump(headers_send_early_and_clear());
?>
--EXPECTF--
Foo

Warning: headers_send_early_and_clear(): Headers already sent in %s on line %d
bool(false)
6 changes: 3 additions & 3 deletions main/SAPI.c
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ SAPI_API int sapi_header_op(sapi_header_op_enum op, void *arg)
}


SAPI_API int sapi_send_headers(void)
SAPI_API int sapi_send_headers(bool last_headers)
{
int retval;
int ret = FAILURE;
Expand All @@ -833,7 +833,7 @@ SAPI_API int sapi_send_headers(void)
/* Success-oriented. We set headers_sent to 1 here to avoid an infinite loop
* in case of an error situation.
*/
if (SG(sapi_headers).send_default_content_type && sapi_module.send_headers) {
if (SG(sapi_headers).send_default_content_type && sapi_module.send_headers && last_headers) {
uint32_t len = 0;
char *default_mimetype = get_default_content_type(0, &len);

Expand Down Expand Up @@ -863,7 +863,7 @@ SAPI_API int sapi_send_headers(void)
zval_ptr_dtor(&cb);
}

SG(headers_sent) = 1;
SG(headers_sent) = last_headers;

if (sapi_module.send_headers) {
retval = sapi_module.send_headers(&SG(sapi_headers));
Expand Down
2 changes: 1 addition & 1 deletion main/SAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ SAPI_API int sapi_add_header_ex(const char *header_line, size_t header_line_len,
#define sapi_add_header(a, b, c) sapi_add_header_ex((a),(b),(c),1)


SAPI_API int sapi_send_headers(void);
SAPI_API int sapi_send_headers(bool last_headers);
SAPI_API void sapi_free_header(sapi_header_struct *sapi_header);
SAPI_API void sapi_handle_post(void *arg);
SAPI_API size_t sapi_read_post_block(char *buffer, size_t buflen);
Expand Down
2 changes: 1 addition & 1 deletion run-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -2513,7 +2513,7 @@ function run_test(string $php, $file, array $env): string
$headers = [];

if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
$output = trim($match[2]);
$output = str_replace("\r\n", "\n", trim($match[2]));
$rh = preg_split("/[\n\r]+/", $match[1]);

foreach ($rh as $line) {
Expand Down
2 changes: 1 addition & 1 deletion sapi/apache2handler/sapi_apache2.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ php_apache_sapi_flush(void *server_context)

r = ctx->r;

sapi_send_headers();
sapi_send_headers(/* last_headers */ true);

r->status = SG(sapi_headers).http_response_code;
SG(headers_sent) = 1;
Expand Down
2 changes: 1 addition & 1 deletion sapi/cli/php_cli_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ static void sapi_cli_server_flush(void *server_context) /* {{{ */
}

if (!SG(headers_sent)) {
sapi_send_headers();
sapi_send_headers(/* last_headers */ true);
SG(headers_sent) = 1;
}
} /* }}} */
Expand Down
47 changes: 47 additions & 0 deletions sapi/cli/tests/php_cli_server_early_hints.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
PHP CLI server HTTP early hints
--SKIPIF--
<?php
include "skipif.inc";
?>
--FILE--
<?php
include "php_cli_server.inc";
php_cli_server_start(<<<'PHP'
header('HTTP/1.1 103 Early Hints');
header('Link: </style.css>; rel=preload; as=style');
headers_send_early_and_clear();
header('Location: http://example.com/');
echo "Foo\n";
PHP);

$host = PHP_CLI_SERVER_HOSTNAME;
$fp = php_cli_server_connect();

if (fwrite($fp, <<<HEADER
GET / HTTP/1.1
Host: {$host}


HEADER
)) {
fpassthru($fp);
}

?>
--EXPECTF--
HTTP/1.1 103 Early Hints
Host: %s
Date: %s
Connection: close
X-Powered-By: PHP/%s
Link: </style.css>; rel=preload; as=style

HTTP/1.1 302 Found
Host: localhost
Date: %s
Connection: close
Location: http://example.com/
Content-type: text/html; charset=UTF-8

Foo