Skip to content

Commit

Permalink
Allow array HTTP/2 headers, Bug fixed for #4133 (#4140)
Browse files Browse the repository at this point in the history
* Allow array HTTP/2 headers, Bug fixed for #4133

* Fix tests

* Fix tests

* Fix tests

* optimize

Co-authored-by: matyhtf <mikan.tenny@gmail.com>
  • Loading branch information
doubaokun and matyhtf committed Apr 9, 2021
1 parent a218e35 commit 46ea525
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 50 deletions.
10 changes: 9 additions & 1 deletion examples/http2/server.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php
Co::set([
Swoole\Coroutine::set([
'trace_flags' => SWOOLE_TRACE_HTTP2,
'log_level' => 0,
]);
Expand All @@ -14,6 +14,14 @@
]);

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
$response->header('Test-Value', [
"a\r\n",
'd5678',
"e \n ",
null,
5678,
3.1415926,
]);
$response->end("<h1>Hello Swoole.</h1>");
});

Expand Down
17 changes: 17 additions & 0 deletions ext-src/php_swoole_http.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,23 @@ static sw_inline zval *swoole_http_init_and_read_property(
return *zproperty_store_pp;
}

static inline bool swoole_http_has_crlf(const char *value, size_t length) {
/* new line/NUL character safety check */
for (size_t i = 0; i < length; i++) {
/* RFC 7230 ch. 3.2.4 deprecates folding support */
if (value[i] == '\n' || value[i] == '\r') {
php_swoole_error(E_WARNING, "Header may not contain more than a single header, new line detected");
return true;
}
if (value[i] == '\0') {
php_swoole_error(E_WARNING, "Header may not contain NUL bytes");
return true;
}
}

return false;
}

void swoole_http_parse_cookie(zval *array, const char *at, size_t length);

swoole::http::Context *php_swoole_http_request_get_context(zval *zobject);
Expand Down
74 changes: 45 additions & 29 deletions ext-src/swoole_http2_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ static ssize_t http2_build_header(http_context *ctx, uchar *buffer, size_t body_
sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0);
zval *zcookie =
sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0);
http2::HeaderSet headers(8 + php_swoole_array_length_safe(zheader) + php_swoole_array_length_safe(zcookie));
http2::HeaderSet headers(32 + php_swoole_array_length_safe(zheader) + php_swoole_array_length_safe(zcookie));
char *date_str = nullptr;
char intbuf[2][16];
int ret;
Expand All @@ -253,48 +253,64 @@ static ssize_t http2_build_header(http_context *ctx, uchar *buffer, size_t body_
ret = swoole_itoa(intbuf[0], ctx->response.status);
headers.add(ZEND_STRL(":status"), intbuf[0], ret);

uint32_t header_flags = 0x0;

// headers
if (ZVAL_IS_ARRAY(zheader)) {
uint32_t header_flag = 0x0;
zend_string *key;
const char *key;
uint32_t keylen;
zval *zvalue;
int type;

auto add_header = [](http2::HeaderSet &headers, const char *key, size_t l_key, zval *value, uint32_t &header_flags) {
if (ZVAL_IS_NULL(value)) {
return;
}
zend::String str_value(value);
str_value.rtrim();
if (swoole_http_has_crlf(str_value.val(), str_value.len())) {
return;
}
if (SW_STREQ(key, l_key, "server")) {
header_flags |= HTTP_HEADER_SERVER;
} else if (SW_STREQ(key, l_key, "content-length")) {
return; // ignore
} else if (SW_STREQ(key, l_key, "date")) {
header_flags |= HTTP_HEADER_DATE;
} else if (SW_STREQ(key, l_key, "content-type")) {
header_flags |= HTTP_HEADER_CONTENT_TYPE;
}
headers.add(key, l_key, str_value.val(), str_value.len());
};

ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheader), key, zvalue) {
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zheader), key, keylen, type, zvalue) {
if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) {
continue;
}
zend::String str_value(zvalue);
char *c_key = ZSTR_VAL(key);
size_t c_keylen = ZSTR_LEN(key);
if (SW_STREQ(c_key, c_keylen, "server")) {
header_flag |= HTTP_HEADER_SERVER;
} else if (SW_STREQ(c_key, c_keylen, "content-length")) {
continue; // ignore
} else if (SW_STREQ(c_key, c_keylen, "date")) {
header_flag |= HTTP_HEADER_DATE;
} else if (SW_STREQ(c_key, c_keylen, "content-type")) {
header_flag |= HTTP_HEADER_CONTENT_TYPE;
if (ZVAL_IS_ARRAY(zvalue)) {
zval *zvalue_2;
SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zvalue), zvalue_2) {
add_header(headers, key, keylen, zvalue_2, header_flags);
}
SW_HASHTABLE_FOREACH_END();
} else {
add_header(headers, key, keylen, zvalue, header_flags);
}
headers.add(c_key, c_keylen, str_value.val(), str_value.len());
}
ZEND_HASH_FOREACH_END();
SW_HASHTABLE_FOREACH_END();
(void) type;
}

if (!(header_flag & HTTP_HEADER_SERVER)) {
headers.add(ZEND_STRL("server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE));
}
if (!(header_flag & HTTP_HEADER_DATE)) {
date_str = php_swoole_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), time(nullptr), 0);
headers.add(ZEND_STRL("date"), date_str, strlen(date_str));
}
if (!(header_flag & HTTP_HEADER_CONTENT_TYPE)) {
headers.add(ZEND_STRL("content-type"), ZEND_STRL("text/html"));
}
} else {
if (!(header_flags & HTTP_HEADER_SERVER)) {
headers.add(ZEND_STRL("server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE));
headers.add(ZEND_STRL("content-type"), ZEND_STRL("text/html"));
}
if (!(header_flags & HTTP_HEADER_DATE)) {
date_str = php_swoole_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), time(nullptr), 0);
headers.add(ZEND_STRL("date"), date_str, strlen(date_str));
}
if (!(header_flags & HTTP_HEADER_CONTENT_TYPE)) {
headers.add(ZEND_STRL("content-type"), ZEND_STRL("text/html"));
}
if (date_str) {
efree(date_str);
}
Expand Down
23 changes: 3 additions & 20 deletions ext-src/swoole_http_response.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,6 @@ static inline void http_header_key_format(char *key, int length) {
}
}

static inline bool http_has_crlf(const char *value, size_t length) {
/* new line/NUL character safety check */
for (size_t i = 0; i < length; i++) {
/* RFC 7230 ch. 3.2.4 deprecates folding support */
if (value[i] == '\n' || value[i] == '\r') {
php_swoole_error(E_WARNING, "Header may not contain more than a single header, new line detected");
return true;
}
if (value[i] == '\0') {
php_swoole_error(E_WARNING, "Header may not contain NUL bytes");
return true;
}
}

return false;
}

String *http_context::get_write_buffer() {
if (co_socket) {
String *buffer = ((Socket *) private_data)->get_write_buffer();
Expand Down Expand Up @@ -426,7 +409,7 @@ static void http_build_header(http_context *ctx, swString *response, size_t body
}
zend::String str_value(value);
str_value.rtrim();
if (http_has_crlf(str_value.val(), str_value.len())) {
if (swoole_http_has_crlf(str_value.val(), str_value.len())) {
return;
}
response->append(key, l_key);
Expand Down Expand Up @@ -881,7 +864,7 @@ bool http_context::set_header(const char *k, size_t klen, zval *zvalue, bool for
return false;
}

if (http_has_crlf(k, klen)) {
if (swoole_http_has_crlf(k, klen)) {
Z_TRY_DELREF_P(zvalue);
return false;
}
Expand Down Expand Up @@ -1020,7 +1003,7 @@ static void php_swoole_http_response_cookie(INTERNAL_FUNCTION_PARAMETERS, const
RETURN_FALSE;
}

if (!url_encode && http_has_crlf(value, value_len)) {
if (!url_encode && swoole_http_has_crlf(value, value_len)) {
RETURN_FALSE;
}

Expand Down
50 changes: 50 additions & 0 deletions tests/swoole_http2_server/http2_headers.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
swoole_http2_server: array headers
--SKIPIF--
<?php require __DIR__ . '/../include/skipif.inc'; ?>
--FILE--
<?php
require __DIR__ . '/../include/bootstrap.php';
$pm = new ProcessManager;
$pm->parentFunc = function ($pid) use ($pm) {
$output = `curl --http2-prior-knowledge --silent -I http://127.0.0.1:{$pm->getFreePort()}`;
echo $output;
$pm->kill();
};
$pm->childFunc = function () use ($pm) {
$http = new swoole_http_server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE);
$http->set([
'worker_num' => 1,
'log_file' => '/dev/null',
'open_http2_protocol' => true
]);
$http->on('workerStart', function ($serv, $wid) use ($pm) {
$pm->wakeup();
});
$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
$response->header('test-value', [
"a\r\n",
'd5678',
"e \n ",
null,
5678,
3.1415926,
]);
$response->end("<h1>Hello Swoole.</h1>");
});
$http->start();
};
$pm->childFirst();
$pm->run();
?>
--EXPECTF--
HTTP/2 200
test-value: a
test-value: d5678
test-value: e
test-value: 5678
test-value: 3.1415926
server: swoole-http-server
date: %s
content-type: text/html
content-length: 22

0 comments on commit 46ea525

Please sign in to comment.