diff --git a/frankenphp.c b/frankenphp.c index dde572d0cf..18f5c1072d 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -243,7 +243,7 @@ PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */ } /* }}} */ /* {{{ Fetch all HTTP request headers */ -PHP_FUNCTION(apache_request_headers) { +PHP_FUNCTION(frankenphp_request_headers) { if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); } @@ -265,6 +265,58 @@ PHP_FUNCTION(apache_request_headers) { } /* }}} */ +// add_response_header and apache_response_headers are copied from +// https://github.com/php/php-src/blob/master/sapi/cli/php_cli_server.c +// Copyright (c) The PHP Group +// Licensed under The PHP License +// Original authors: Moriyoshi Koizumi and Xinchen Hui +// +static void add_response_header(sapi_header_struct *h, + zval *return_value) /* {{{ */ +{ + if (h->header_len > 0) { + char *s; + size_t len = 0; + ALLOCA_FLAG(use_heap) + + char *p = strchr(h->header, ':'); + if (NULL != p) { + len = p - h->header; + } + if (len > 0) { + while (len != 0 && + (h->header[len - 1] == ' ' || h->header[len - 1] == '\t')) { + len--; + } + if (len) { + s = do_alloca(len + 1, use_heap); + memcpy(s, h->header, len); + s[len] = 0; + do { + p++; + } while (*p == ' ' || *p == '\t'); + add_assoc_stringl_ex(return_value, s, len, p, + h->header_len - (p - h->header)); + free_alloca(s, use_heap); + } + } + } +} +/* }}} */ + +PHP_FUNCTION(frankenphp_response_headers) /* {{{ */ +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + array_init(return_value); + zend_llist_apply_with_argument( + &SG(sapi_headers).headers, + (llist_apply_with_arg_func_t)add_response_header, return_value); +} +/* }}} */ + PHP_FUNCTION(frankenphp_handle_request) { zend_fcall_info fci; zend_fcall_info_cache fcc; @@ -784,8 +836,12 @@ static char *cli_script; static int cli_argc; static char **cli_argv; -// Adapted from https://github.com/php/php-src/sapi/cli/php_cli.c (The PHP -// Group, The PHP License) +// CLI code is adapted from +// https://github.com/php/php-src/blob/master/sapi/cli/php_cli.c Copyright (c) +// The PHP Group Licensed under The PHP License Original uthors: Edin Kadribasic +// , Marcus Boerger and Johannes Schlueter +// Parts based on CGI SAPI Module by Rasmus Lerdorf, Stig +// Bakken and Zeev Suraski static void cli_register_file_handles(bool no_close) /* {{{ */ { php_stream *s_in, *s_out, *s_err; diff --git a/frankenphp.stub.php b/frankenphp.stub.php index 068314c7ec..d166dc5d36 100644 --- a/frankenphp.stub.php +++ b/frankenphp.stub.php @@ -13,9 +13,22 @@ function frankenphp_finish_request(): bool {} */ function fastcgi_finish_request(): bool {} +function frankenphp_request_headers(): array {} + +/** + * @alias frankenphp_request_headers + */ function apache_request_headers(): array {} /** - * @alias apache_request_headers + * @alias frankenphp_response_headers */ function getallheaders(): array {} + +function frankenphp_response_headers(): array|bool {} + +/** + * @alias frankenphp_response_headers + */ +function apache_response_headers(): array|bool {} + diff --git a/frankenphp_arginfo.h b/frankenphp_arginfo.h index 97cfcecaa4..c343e9f320 100644 --- a/frankenphp_arginfo.h +++ b/frankenphp_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f925a1c280fb955eb32d0823cbd4f360b0cbabed */ + * Stub hash: 467f1406e17d3b8ca67bba5ea367194e60d8dd27 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1, _IS_BOOL, 0) @@ -16,16 +16,25 @@ ZEND_END_ARG_INFO() #define arginfo_fastcgi_finish_request arginfo_frankenphp_finish_request -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_apache_request_headers, 0, 0, - IS_ARRAY, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_request_headers, 0, + 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() -#define arginfo_getallheaders arginfo_apache_request_headers +#define arginfo_apache_request_headers arginfo_frankenphp_request_headers + +#define arginfo_getallheaders arginfo_frankenphp_request_headers + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_frankenphp_response_headers, 0, + 0, MAY_BE_ARRAY | MAY_BE_BOOL) +ZEND_END_ARG_INFO() + +#define arginfo_apache_response_headers arginfo_frankenphp_response_headers ZEND_FUNCTION(frankenphp_handle_request); ZEND_FUNCTION(headers_send); ZEND_FUNCTION(frankenphp_finish_request); -ZEND_FUNCTION(apache_request_headers); +ZEND_FUNCTION(frankenphp_request_headers); +ZEND_FUNCTION(frankenphp_response_headers); static const zend_function_entry ext_functions[] = { ZEND_FE(frankenphp_handle_request, arginfo_frankenphp_handle_request) @@ -33,6 +42,16 @@ static const zend_function_entry ext_functions[] = { frankenphp_finish_request, arginfo_frankenphp_finish_request) ZEND_FALIAS(fastcgi_finish_request, frankenphp_finish_request, arginfo_fastcgi_finish_request) - ZEND_FE(apache_request_headers, arginfo_apache_request_headers) - ZEND_FALIAS(getallheaders, apache_request_headers, - arginfo_getallheaders) ZEND_FE_END}; + ZEND_FE(frankenphp_request_headers, + arginfo_frankenphp_request_headers) + ZEND_FALIAS(apache_request_headers, + frankenphp_request_headers, + arginfo_apache_request_headers) + ZEND_FALIAS(getallheaders, frankenphp_response_headers, + arginfo_getallheaders) + ZEND_FE(frankenphp_response_headers, + arginfo_frankenphp_response_headers) + ZEND_FALIAS(apache_response_headers, + frankenphp_response_headers, + arginfo_apache_response_headers) + ZEND_FE_END}; diff --git a/frankenphp_test.go b/frankenphp_test.go index fa7a56132f..cbc87002ee 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -226,6 +226,27 @@ func testHeaders(t *testing.T, opts *testOptions) { }, opts) } +func TestResponseHeaders_module(t *testing.T) { testResponseHeaders(t, nil) } +func TestResponseHeaders_worker(t *testing.T) { + testResponseHeaders(t, &testOptions{workerScript: "response-headers.php"}) +} +func testResponseHeaders(t *testing.T, opts *testOptions) { + runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { + req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/response-headers.php?i=%d", i), nil) + w := httptest.NewRecorder() + handler(w, req) + + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + + assert.Contains(t, string(body), "'X-Powered-By' => 'PH") + assert.Contains(t, string(body), "'Foo' => 'bar',") + assert.Contains(t, string(body), "'Foo2' => 'bar2',") + assert.Contains(t, string(body), fmt.Sprintf("'I' => '%d',", i)) + assert.NotContains(t, string(body), "Invalid") + }, opts) +} + func TestInput_module(t *testing.T) { testInput(t, nil) } func TestInput_worker(t *testing.T) { testInput(t, &testOptions{workerScript: "input.php"}) } func testInput(t *testing.T, opts *testOptions) { @@ -553,13 +574,13 @@ func testFiberNoCgo(t *testing.T, opts *testOptions) { }, opts) } -func TestApacheRequestHeaders_module(t *testing.T) { testApacheRequestHeaders(t, &testOptions{}) } -func TestApacheRequestHeaders_worker(t *testing.T) { - testApacheRequestHeaders(t, &testOptions{workerScript: "apache-request-headers.php"}) +func TestRequestHeaders_module(t *testing.T) { testRequestHeaders(t, &testOptions{}) } +func TestRequestHeaders_worker(t *testing.T) { + testRequestHeaders(t, &testOptions{workerScript: "request-headers.php"}) } -func testApacheRequestHeaders(t *testing.T, opts *testOptions) { +func testRequestHeaders(t *testing.T, opts *testOptions) { runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { - req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/apache-request-headers.php?i=%d", i), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/request-headers.php?i=%d", i), nil) req.Header.Add("Content-Type", "text/plain") req.Header.Add("Frankenphp-I", strconv.Itoa(i)) diff --git a/testdata/apache-request-headers.php b/testdata/request-headers.php similarity index 100% rename from testdata/apache-request-headers.php rename to testdata/request-headers.php diff --git a/testdata/response-headers.php b/testdata/response-headers.php new file mode 100644 index 0000000000..c994bf0f3a --- /dev/null +++ b/testdata/response-headers.php @@ -0,0 +1,13 @@ +