Skip to content
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
35 changes: 32 additions & 3 deletions core-tests/src/server/http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1067,8 +1067,10 @@ TEST(http_server, upload_preprocessed_file) {
std::thread t;
std::string probe_file = std::string(TEST_TMP_FILE) + ".upload_marker." + std::to_string(getpid());
std::string upload_content(80 * 1024, 'A');
auto server = http_server::listen(
":0", [&probe_file, &upload_content](Context &ctx) { check_single_upload(ctx, probe_file, upload_content); });
auto server = http_server::listen(":0", [&probe_file, &upload_content](Context &ctx) {
EXPECT_EQ(ctx.headers["X-Whitespace"], "alpha \t beta");
check_single_upload(ctx, probe_file, upload_content);
});
server->worker_num = 1;
server->get_primary_port()->set_package_max_length(64 * 1024);
server->upload_max_filesize = 1024 * 1024;
Expand All @@ -1083,7 +1085,10 @@ TEST(http_server, upload_preprocessed_file) {

std::string boundary = "------------------------d3f990cdce762596";
std::string body = make_multipart_upload_body(boundary, upload_content, probe_file, false);
std::string request = make_multipart_upload_request(boundary, body);
std::string request = "POST /upload HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n"
"Content-Type: multipart/form-data; boundary=" +
boundary + " \t\r\nX-Whitespace: \talpha \t beta \t \r\nContent-Length: " +
std::to_string(body.length()) + "\r\n\r\n" + body;
SyncClient c(SW_SOCK_TCP);
c.connect(TEST_HOST, server->get_primary_port()->port);
c.send(request.c_str(), request.length());
Expand Down Expand Up @@ -1983,6 +1988,13 @@ TEST(http_server, has_expect_header) {
"Content-Length: 1000000\r\n"
"Expect: 100-continue\r\n\r\n");
ASSERT_TRUE(req.has_expect_header());

req.buffer_->clear();
req.buffer_->append("POST /submit HTTP/1.1\r\n"
"Host: www.example.com\r\n"
"Content-Length: 1000000\r\n"
"Expect:\t100-continue\r\n\r\n");
ASSERT_TRUE(req.has_expect_header());
}

TEST(http_server, duplicate_content_type) {
Expand Down Expand Up @@ -2012,6 +2024,23 @@ TEST(http_server, duplicate_content_type) {
} while (0);
}

TEST(http_server, header_value_whitespace) {
swoole::http_server::Request request;
request.buffer_ = new String("POST / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection:\tkeep-alive\r\n"
"Content-Type:\tmultipart/form-data; boundary=test\r\n"
"\r\n");

ASSERT_EQ(request.get_protocol(), SW_OK);
ASSERT_EQ(request.get_header_length(), SW_OK);
request.parse_header_info();
EXPECT_TRUE(request.keep_alive);
EXPECT_NE(request.form_data_, nullptr);
delete request.buffer_;
request.buffer_ = nullptr;
}

TEST(http_server, invalid_multipart_boundary) {
swoole::http_server::Request request;
Server server(Server::MODE_BASE);
Expand Down
1 change: 1 addition & 0 deletions ext-src/swoole_http_client_coro.cc
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ static int http_parser_on_header_field(llhttp_t *parser, const char *at, size_t

static int http_parser_on_header_value(llhttp_t *parser, const char *at, size_t length) {
auto *http = static_cast<Client *>(parser->data);
length = swoole::rtrim(at, length);
zval *zobject = static_cast<zval *>(http->zobject);

const char *header_name = http->tmp_header_field_name;
Expand Down
1 change: 1 addition & 0 deletions ext-src/swoole_http_request.cc
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ int swoole_http_token_list_contains_value(const char *at, size_t length, const c

static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length) {
auto *ctx = static_cast<HttpContext *>(parser->data);
length = swoole::rtrim(at, length);
zval *zheader = ctx->request.zheader;
const char *header_name = ctx->current_header_name;
size_t header_len = ctx->current_header_name_len;
Expand Down
20 changes: 12 additions & 8 deletions src/protocol/http.cc
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,8 @@ void Request::parse_header_info() {
} else if (SW_STR_ISTARTS_WITH(p, pe - p, "Connection:")) {
// strlen("Connection:")
p += (sizeof("Connection:") - 1);
// skip spaces
while (*p == ' ') {
// skip OWS
while (*p == ' ' || *p == '\t') {
p++;
}
if (SW_STR_ISTARTS_WITH(p, pe - p, "keep-alive")) {
Expand Down Expand Up @@ -920,13 +920,14 @@ void Request::parse_header_info() {
delete form_data_;
form_data_ = nullptr;
p += (sizeof("Content-Type:") - 1);
while (*p == ' ') {
while (*p == ' ' || *p == '\t') {
p++;
}
if (SW_STR_ISTARTS_WITH(p, pe - p, "multipart/form-data")) {
form_data_ = new FormData();
form_data_->multipart_boundary_buf = p + (sizeof("multipart/form-data") - 1);
form_data_->multipart_boundary_len = strchr(p, '\r') - form_data_->multipart_boundary_buf;
form_data_->multipart_boundary_len = swoole::rtrim(
form_data_->multipart_boundary_buf, strchr(p, '\r') - form_data_->multipart_boundary_buf);
}
}
}
Expand Down Expand Up @@ -1035,8 +1036,11 @@ bool Request::has_expect_header() const {
for (char *p = buf; p < pe; p++) {
if (*p == '\r' && (size_t) (pe - p) > sizeof("\r\nExpect")) {
p += 2;
if (SW_STR_ISTARTS_WITH(p, pe - p, "Expect: ")) {
p += sizeof("Expect: ") - 1;
if (SW_STR_ISTARTS_WITH(p, pe - p, "Expect:")) {
p += sizeof("Expect:") - 1;
while (p < pe && (*p == ' ' || *p == '\t')) {
p++;
}
if (SW_STR_ISTARTS_WITH(p, pe - p, "100-continue")) {
return true;
} else {
Expand Down Expand Up @@ -1298,7 +1302,7 @@ std::string Request::get_header(const char *name) const {
char *p = buffer_->str + url_offset_ + url_length_ + 10;
char *pe = buffer_->str + header_length_;

char *buffer = nullptr;
const char *buffer = nullptr;
char *colon = nullptr;

int state = 0;
Expand Down Expand Up @@ -1336,7 +1340,7 @@ std::string Request::get_header(const char *name) const {
break;
case 2:
if (SW_STR_ISTARTS_WITH(p, pe - p, "\r\n")) {
return {buffer, static_cast<size_t>(p - buffer)};
return {buffer, swoole::rtrim(buffer, static_cast<size_t>(p - buffer))};
}
break;
default:
Expand Down
1 change: 1 addition & 0 deletions src/wrapper/http.cc
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t

static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length) {
auto *ctx = static_cast<Context *>(parser->data);
length = swoole::rtrim(at, length);
ContextImpl *impl = ctx->impl;
ctx->headers[impl->current_header_name] = std::string(at, length);

Expand Down
41 changes: 41 additions & 0 deletions tests/swoole_http_client_coro/header_value_whitespace.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
swoole_http_client_coro: trim header value whitespace
--SKIPIF--
<?php require __DIR__ . '/../include/skipif.inc'; ?>
--FILE--
<?php
require __DIR__ . '/../include/bootstrap.php';

Swoole\Coroutine\run(function () {
$listener = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0);
Assert::true($listener->bind('127.0.0.1', 0));
Assert::true($listener->listen());
$port = $listener->getsockname()['port'];

$server = Swoole\Coroutine::create(function () use ($listener) {
$connection = $listener->accept(2);
Assert::notSame($connection, false);
$request = '';
while (!str_contains($request, "\r\n\r\n")) {
$chunk = $connection->recv(4096, 2);
Assert::notSame($chunk, false);
Assert::notSame($chunk, '');
$request .= $chunk;
}
$response =
"HTTP/1.1 200 OK\r\nX-Whitespace: \talpha \t beta \t \r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
Assert::same($connection->sendAll($response), strlen($response));
$connection->close();
});

$client = new Swoole\Coroutine\Http\Client('127.0.0.1', $port);
Assert::true($client->get('/'));
Assert::same($client->headers['x-whitespace'], "alpha \t beta");
$client->close();
$listener->close();
Swoole\Coroutine::join([$server]);
});
echo "DONE\n";
?>
--EXPECT--
DONE
2 changes: 2 additions & 0 deletions tests/swoole_http_server/create_request.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ $data .= "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,
$data .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n";
$data .= "Accept-Encoding: gzip, deflate, br\r\n";
$data .= "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,ja;q=0.6\r\n";
$data .= "X-Whitespace: \talpha \t beta \t \r\n";
$data .= "Cookie: env=pretest; phpsessid=fcccs2af8673a2f343a61a96551c8523d79ea; username=hantianfeng\r\n";

$req3 = Request::create();
Expand All @@ -72,6 +73,7 @@ $data .= $body;

$req4 = Request::create();
$req4->parse($data);
Assert::same($req4->header['x-whitespace'], "alpha \t beta");

for ($offset = 1; $offset < strlen($data); ++$offset) {
$req = Request::create();
Expand Down
5 changes: 5 additions & 0 deletions tests/swoole_http_server/range.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ $pm->parentFunc = function () use ($pm) {
$lastModified = $response['headers']['last-modified'] ?? null;
Assert::notNull($lastModified);
Assert::null($response['headers']['accept-ranges'] ?? null);
if (!$http2) {
$response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['headers' => ['Range' => "bytes=0-15\t "]]);
Assert::same($response['statusCode'], 206);
Assert::same('bytes 0-15/218787', $response['headers']['content-range']);
}
$response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-']]);
Assert::same($response['statusCode'], 206);
Assert::same('bytes 0-218786/218787', $response['headers']['content-range']);
Expand Down
Loading