Skip to content
Merged
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
62 changes: 59 additions & 3 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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 <moriyoshi@php.net> and Xinchen Hui
// <laruence@php.net>
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;
Expand Down Expand Up @@ -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
// <edink@php.net>, Marcus Boerger <helly@php.net> and Johannes Schlueter
// <johannes@php.net> 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;
Expand Down
15 changes: 14 additions & 1 deletion frankenphp.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

35 changes: 27 additions & 8 deletions frankenphp_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: f925a1c280fb955eb32d0823cbd4f360b0cbabed */
* Stub hash: 467f1406e17d3b8ca67bba5ea367194e60d8dd27 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1,
_IS_BOOL, 0)
Expand All @@ -16,23 +16,42 @@ 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)
ZEND_FE(headers_send, arginfo_headers_send) ZEND_FE(
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};
31 changes: 26 additions & 5 deletions frankenphp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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))

Expand Down
File renamed without changes.
13 changes: 13 additions & 0 deletions testdata/response-headers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

require_once __DIR__.'/_executor.php';

return function () {
header('Foo: bar');
header('Foo2: bar2');
header('Invalid');
header('I: ' . ($_GET['i'] ?? 'i not set'));
http_response_code(201);

var_export(apache_response_headers());
};