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
91 changes: 9 additions & 82 deletions src/Http/Adapter/FPM/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function getIP(): string
$remoteAddr = $this->getServer('REMOTE_ADDR') ?? '0.0.0.0';

foreach ($this->trustedIpHeaders as $header) {
$headerValue = $this->getHeader($header);
$headerValue = $this->getHeaderLine($header);

if (empty($headerValue)) {
continue;
Expand Down Expand Up @@ -209,57 +209,19 @@ public function getAccept(string $default = ''): string
}

/**
* Get cookie
* Generate cookies
*
* Method for querying HTTP cookie parameters. If $key is not found $default value will be returned.
*/
public function getCookie(string $key, string $default = ''): string
{
return $_COOKIE[$key] ?? $default;
}

/**
* Get header
*
* Method for querying HTTP header parameters. If $key is not found $default value will be returned.
*/
public function getHeader(string $key, string $default = ''): string
{
$headers = $this->generateHeaders();

if (!isset($headers[$key])) {
return $default;
}

$value = $headers[$key];

return \is_array($value) ? implode(', ', $value) : $value;
}

/**
* Set header
*
* Method for adding HTTP header parameters.
*/
public function addHeader(string $key, string $value): static
{
$this->headers[$key] = $value;

return $this;
}

/**
* Remvoe header
* Parse request cookies into an associative array of cookie name to value.
*
* Method for removing HTTP header parameters.
* @return array<string, string>
*/
public function removeHeader(string $key): static
protected function generateCookies(): array
{
if (isset($this->headers[$key])) {
unset($this->headers[$key]);
if (null === $this->cookies) {
$this->cookies = $_COOKIE;
}

return $this;
return $this->cookies;
}

/**
Expand All @@ -275,7 +237,7 @@ protected function generateInput(): array
$this->queryString = $_GET;
}
if (null === $this->payload) {
$contentType = $this->getHeader('content-type');
$contentType = $this->getHeaderLine('content-type');

// Get content-type without the charset
$length = strpos($contentType, ';');
Expand All @@ -302,39 +264,4 @@ protected function generateInput(): array
default => $this->queryString,
};
}

/**
* Generate headers
*
* Parse request headers as an array for easy querying using the getHeader method
*
* @return array<string, string|array<int, string>>
*/
#[\Override]
protected function generateHeaders(): array
{
if (null === $this->headers) {
/**
* Fallback for older PHP versions
* that do not support generateHeaders
*/
if (!\function_exists('getallheaders')) {
$headers = [];

foreach ($_SERVER as $name => $value) {
if (str_starts_with($name, 'HTTP_')) {
$headers[str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($name, 5))))] = $value;
}
}

$this->headers = $headers;

return $this->headers;
}

$this->headers = array_change_key_case(getallheaders());
}

return $this->headers;
}
}
38 changes: 23 additions & 15 deletions src/Http/Adapter/FPM/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,22 @@ protected function sendStatus(int $statusCode): void
/**
* Send Header
*
* Output Header
* Output Header. Header names are stored lowercased internally; they are
* formatted to the conventional Title-Case form on the wire to match the
* Swoole adapter (e.g. "content-type" => "Content-Type").
*
* @param string|array<string> $value
* @param array<int, string> $value
*/
public function sendHeader(string $key, mixed $value): void
public function sendHeader(string $key, array $value): void
{
if (\is_array($value)) {
foreach ($value as $v) {
header($key . ': ' . $v, false);
}
} else {
header($key . ': ' . $value);
$key = ucwords(strtolower($key), '-');

// First value replaces any header of the same name; the rest are
// appended so multi-value headers (e.g. Set-Cookie) emit one line each.
$replace = true;
foreach ($value as $v) {
header($key . ': ' . $v, $replace);
$replace = false;
}
}

Expand All @@ -69,11 +73,15 @@ public function sendHeader(string $key, mixed $value): void
*/
protected function sendCookie(string $name, string $value, array $options): void
{
// Use proper PHP keyword name
$options['expires'] = $options['expire'];
unset($options['expire']);

// Set the cookie
setcookie($name, $value, $options);
// Coalesce nulls to the types setcookie() expects for each option, and
// map our 'expire' key to PHP's 'expires' keyword.
setcookie($name, $value, [
'expires' => $options['expire'] ?? 0,
'path' => $options['path'] ?? '',
'domain' => $options['domain'] ?? '',
'secure' => $options['secure'] ?? false,
'httponly' => $options['httponly'] ?? false,
'samesite' => $options['samesite'] ?? '',
]);
}
}
79 changes: 32 additions & 47 deletions src/Http/Adapter/Swoole/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function getIP(): string
$remoteAddr = $this->getServer('remote_addr') ?? '0.0.0.0';

foreach ($this->trustedIpHeaders as $header) {
$headerValue = $this->getHeader($header);
$headerValue = $this->getHeaderLine($header);

if (empty($headerValue)) {
continue;
Expand Down Expand Up @@ -92,7 +92,7 @@ public function getIP(): string
*/
public function getProtocol(): string
{
$protocol = $this->getHeader('x-forwarded-proto', $this->getServer('server_protocol') ?? 'https');
$protocol = $this->getHeaderLine('x-forwarded-proto', $this->getServer('server_protocol') ?? 'https');

if ($protocol === 'HTTP/1.1') {
return 'http';
Expand All @@ -111,7 +111,7 @@ public function getProtocol(): string
*/
public function getPort(): string
{
return $this->getHeader('x-forwarded-port', (string) parse_url($this->getProtocol() . '://' . $this->getHeader('x-forwarded-host', $this->getHeader('host')), PHP_URL_PORT));
return $this->getHeaderLine('x-forwarded-port', (string) parse_url($this->getProtocol() . '://' . $this->getHeaderLine('x-forwarded-host', $this->getHeaderLine('host')), PHP_URL_PORT));
}

/**
Expand All @@ -121,7 +121,7 @@ public function getPort(): string
*/
public function getHostname(): string
{
$hostname = parse_url($this->getProtocol() . '://' . $this->getHeader('x-forwarded-host', $this->getHeader('host')), PHP_URL_HOST);
$hostname = parse_url($this->getProtocol() . '://' . $this->getHeaderLine('x-forwarded-host', $this->getHeaderLine('host')), PHP_URL_HOST);
return strtolower(\strval($hostname));
}

Expand Down Expand Up @@ -177,7 +177,7 @@ public function setURI(string $uri): static
*/
public function getReferer(string $default = ''): string
{
return $this->getHeader('referer', '');
return $this->getHeaderLine('referer', $default);
}

/**
Expand All @@ -187,7 +187,7 @@ public function getReferer(string $default = ''): string
*/
public function getOrigin(string $default = ''): string
{
return $this->getHeader('origin', $default);
return $this->getHeaderLine('origin', $default);
}

/**
Expand All @@ -197,7 +197,7 @@ public function getOrigin(string $default = ''): string
*/
public function getUserAgent(string $default = ''): string
{
return $this->getHeader('user-agent', $default);
return $this->getHeaderLine('user-agent', $default);
}

/**
Expand All @@ -207,7 +207,7 @@ public function getUserAgent(string $default = ''): string
*/
public function getAccept(string $default = ''): string
{
return $this->getHeader('accept', $default);
return $this->getHeaderLine('accept', $default);
}

/**
Expand All @@ -226,47 +226,19 @@ public function getFiles($key): array
}

/**
* Get cookie
* Generate cookies
*
* Method for querying HTTP cookie parameters. If $key is not found $default value will be returned.
*/
public function getCookie(string $key, string $default = ''): string
{
$key = strtolower($key);

return $this->swoole->cookie[$key] ?? $default;
}

/**
* Get header
* Parse request cookies into an associative array of cookie name to value.
*
* Method for querying HTTP header parameters. If $key is not found $default value will be returned.
*/
public function getHeader(string $key, string $default = ''): string
{
return $this->swoole->header[$key] ?? $default;
}

/**
* Method for adding HTTP header parameters.
*/
public function addHeader(string $key, string $value): static
{
$this->swoole->header[$key] = $value;

return $this;
}

/**
* Method for removing HTTP header parameters.
* @return array<string, string>
*/
public function removeHeader(string $key): static
protected function generateCookies(): array
{
if (isset($this->swoole->header[$key])) {
unset($this->swoole->header[$key]);
if (null === $this->cookies) {
$this->cookies = $this->swoole->cookie ?? [];
}

return $this;
return $this->cookies;
}
Comment on lines +235 to 242
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Swoole cookie lookup silently broken for mixed-case keys

The old Swoole getCookie() applied strtolower($key) before the lookup because Swoole normalises all incoming cookie names to lowercase internally. The new base class getCookie() does not lowercase the key, so any call like getCookie('MySession') or getCookie('CSRF_TOKEN') on a Swoole request will silently return the $default even when the cookie was sent by the browser — because Swoole stored it as 'mysession' / 'csrf_token'. This is a silent regression for every Swoole consumer that uses non-lowercase lookup keys, and it is not mentioned in the migration guide.


public function getSwooleRequest(): SwooleRequest
Expand All @@ -287,7 +259,7 @@ protected function generateInput(): array
$this->queryString = $this->swoole->get ?? [];
}
if (null === $this->payload) {
$contentType = $this->getHeader('content-type');
$contentType = $this->getHeaderLine('content-type');

// Get content-type without the charset
$length = strpos($contentType, ';');
Expand Down Expand Up @@ -316,13 +288,26 @@ protected function generateInput(): array
/**
* Generate headers
*
* Parse request headers as an array for easy querying using the getHeader method
* Parse Swoole request headers into a PSR-7 style map of lowercased header name
* to a list of string values for easy querying using the getHeader method.
*
* @return array<string, mixed>
* @return array<string, array<int, string>>
*/
#[\Override]
protected function generateHeaders(): array
{
return $this->swoole->header;
if (null === $this->headers) {
$headers = [];

foreach ($this->swoole->header ?? [] as $name => $value) {
$headers[strtolower($name)] = \is_array($value)
? array_values(array_map(strval(...), $value))
: [(string) $value];
}

$this->headers = $headers;
}

return $this->headers;
}
}
8 changes: 5 additions & 3 deletions src/Http/Adapter/Swoole/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ protected function sendStatus(int $statusCode): void
/**
* Send Header
*
* @param string|array<string> $value
* @param array<int, string> $value
*/
public function sendHeader(string $key, mixed $value): void
public function sendHeader(string $key, array $value): void
{
$this->swoole->header($key, $value);
}
Expand All @@ -73,6 +73,8 @@ public function sendHeader(string $key, mixed $value): void
*/
protected function sendCookie(string $name, string $value, array $options): void
{
// Coalesce nulls to the types Swoole's cookie() expects: the SameSite
// argument is parsed as a string (Z_PARAM_STR), so it must not be a bool.
$this->swoole->cookie(
$name,
$value,
Expand All @@ -81,7 +83,7 @@ protected function sendCookie(string $name, string $value, array $options): void
$options['domain'] ?? '',
$options['secure'] ?? false,
$options['httponly'] ?? false,
$options['samesite'] ?? false,
$options['samesite'] ?? '',
);
}
}
2 changes: 1 addition & 1 deletion src/Http/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ public function run(Request $request, Response $response): static
private function runInternal(Request $request, Response $response): static
{
if ($this->compression) {
$response->setAcceptEncoding($request->getHeader('accept-encoding', ''));
$response->setAcceptEncoding($request->getHeaderLine('accept-encoding', ''));
$response->setCompressionMinSize($this->compressionMinSize);
$response->setCompressionSupported($this->compressionSupported);
}
Expand Down
Loading