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
26 changes: 3 additions & 23 deletions src/Http/Adapter/Swoole/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,23 +271,9 @@ public function getFiles($key): array
*/
public function getCookie(string $key, string $default = ''): string
{
$key = \strtolower($key);

$cookies = \explode(';', $this->getHeader('cookie', ''));
foreach ($cookies as $cookie) {
$cookie = \trim($cookie);
if ($cookie === '') {
continue;
}
$parts = \explode('=', $cookie, 2);
$cookieKey = \trim($parts[0]);
$cookieValue = isset($parts[1]) ? \trim($parts[1]) : '';
if ($cookieKey === $key) {
return $cookieValue;
}
}
$key = strtolower($key);

return $default;
return $this->swoole->cookie[$key] ?? $default;
}
Comment on lines 272 to 277
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 getCookie() key normalisation differs from FPM adapter

Swoole's native cookie parser stores cookie names in lowercase (all HTTP header keys are lowercased by Swoole), so the strtolower($key) here correctly normalises lookup. However, FPM\Request::getCookie() performs no such normalisation — it looks up $_COOKIE[$key] with the key exactly as passed.

This means getCookie('MyToken') returns the cookie on Swoole (lowercased lookup matches mytoken) but will miss it on FPM when the cookie was sent as mytoken. The reverse is also true: FPM is case-sensitive while Swoole is case-insensitive. Any application that calls getCookie() with a mixed-case cookie name will behave differently depending on the adapter in use.

This is a pre-existing 0.33.x characteristic being restored, but it may be worth documenting the difference or adding a note to getCookie()'s docblock so callers know to use lowercase keys for portability across adapters.


/**
Expand Down Expand Up @@ -391,12 +377,6 @@ protected function generateInput(): array
*/
protected function generateHeaders(): array
{
$headers = $this->swoole->header;

foreach ($headers as $key => $value) {
$headers[strtolower($key)] = $value;
}

return $headers;
return $this->swoole->header;
}
}
4 changes: 1 addition & 3 deletions src/Http/Adapter/Swoole/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ class Server extends Adapter
public function __construct(string $host, ?string $port = null, array $settings = [], int $mode = SWOOLE_PROCESS, ?Container $container = null)
{
$this->server = new SwooleServer($host, (int) $port, $mode);
$this->server->set(\array_merge($settings, [
'http_parse_cookie' => false,
]));
$this->server->set($settings);
$this->container = $container ?? new Container();
}

Expand Down
4 changes: 1 addition & 3 deletions src/Http/Adapter/SwooleCoroutine/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ public function __construct(
?Container $container = null
) {
$this->server = new SwooleServer($host, $port, false, true);
$this->server->set(\array_merge($settings, [
'http_parse_cookie' => false,
]));
$this->server->set($settings);
$this->container = $container ?? new Container();
}

Expand Down
33 changes: 0 additions & 33 deletions tests/e2e/BaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,6 @@ public function testFile()
$this->assertEquals(204, $response['headers']['status-code']);
}

public function testCookie()
{
// One cookie
$cookie = 'cookie1=value1';
$response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

// Two cookiees
$cookie = 'cookie1=value1; cookie2=value2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

// Two cookies without optional space
$cookie = 'cookie1=value1;cookie2=value2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

// Cookie with "=" in value
$cookie = 'cookie1=value1=value2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

// Case sensitivity for cookie names
$cookie = 'cookie1=v1; Cookie1=v2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', [ 'Cookie: ' . $cookie ]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);
}

public function testSetCookie()
{
$response = $this->client->call(Client::METHOD_GET, '/set-cookie');
Expand Down
28 changes: 28 additions & 0 deletions tests/e2e/ResponseFPMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,32 @@ public function setUp(): void
{
$this->client = new Client();
}

public function testCookie(): void
{
$cookie = 'cookie1=value1';
$response = $this->client->call(Client::METHOD_GET, '/cookies', ['Cookie: ' . $cookie]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

$cookie = 'cookie1=value1; cookie2=value2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', ['Cookie: ' . $cookie]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

$cookie = 'cookie1=value1;cookie2=value2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', ['Cookie: ' . $cookie]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

$cookie = 'cookie1=value1=value2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', ['Cookie: ' . $cookie]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);

$cookie = 'cookie1=v1; Cookie1=v2';
$response = $this->client->call(Client::METHOD_GET, '/cookies', ['Cookie: ' . $cookie]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);
}
}
13 changes: 13 additions & 0 deletions tests/e2e/ResponseSwooleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ public function setUp(): void
$this->client = new Client('http://swoole');
}

public function testCookie(): void
{
$headers = ['Cookie: cookie1=value1; cookie2=value2'];

$response = $this->client->call(Client::METHOD_GET, '/cookie/cookie1', $headers);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('value1', $response['body']);

$response = $this->client->call(Client::METHOD_GET, '/cookie/cookie2', $headers);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('value2', $response['body']);
}
Comment on lines +19 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Swoole cookie test missing edge cases that FPM covers

ResponseFPMTest::testCookie() tests several edge cases: cookies with = in the value (cookie1=value1=value2), cookies with no space after the semicolon separator (cookie1=value1;cookie2=value2), and two cookies that differ only in casing (cookie1=v1; Cookie1=v2). None of these are exercised in the Swoole equivalent.

Since the whole point of this PR is to restore Swoole native cookie parsing, it would be valuable to confirm Swoole handles at least the value=with=equals case correctly (i.e. getCookie('cookie1') returns value1=value2), because different cookie parsers handle trailing = splits differently.

Consider adding a test case such as:

$response = $this->client->call(
    Client::METHOD_GET,
    '/cookie/cookie1',
    ['Cookie: cookie1=value1=value2']
);
$this->assertEquals('value1=value2', $response['body']);


public function testSwooleResources(): void
{
$response = $this->client->call(Client::METHOD_DELETE, '/swoole-test');
Expand Down
8 changes: 8 additions & 0 deletions tests/e2e/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
$response->send($request->getHeaders()['cookie'] ?? '');
});

Http::get('/cookie/:key')
->param('key', '', new Text(64))
->inject('request')
->inject('response')
->action(function (string $key, Request $request, Response $response) {
$response->send($request->getCookie($key, ''));
});

Http::get('/set-cookie')
->inject('request')
->inject('response')
Expand Down
Loading