Skip to content

Headers stored as arrays in $_environment cause getEnv() to return "Array" #18823

@tunnela

Description

@tunnela

Description

In ServerRequest, the method withAddedHeader() always casts the header value to an array, even if you pass a single string:

public function withAddedHeader(string $name, $value): static
{
    $new = clone $this;
    $name = $this->normalizeHeaderName($name);
    $existing = [];
    if (isset($new->_environment[$name])) {
        $existing = (array)$new->_environment[$name];
    }
    $existing = array_merge($existing, (array)$value);
    $new->_environment[$name] = $existing;

    return $new;
}

This means even adding a single header value like '1.1.1.1' results in storing ['1.1.1.1'] in the internal $_environment.

Meanwhile, getEnv() unconditionally casts stored values to string:

return $this->_environment[$key] !== null
    ? (string)$this->_environment[$key]
    : $default;

Casting an array to string triggers PHP warning and returns the string "Array".


Example: effect on ServerRequest::clientIp()

The clientIp() method expects getEnv('HTTP_X_FORWARDED_FOR') to return a string it can explode and parse:

$addresses = array_map('trim', explode(',', (string)$this->getEnv('HTTP_X_FORWARDED_FOR')));

But if you add the header with withAddedHeader() even for a single value:

$request = $request->withAddedHeader('X-Forwarded-For', '1.1.1.1');
echo $request->clientIp();

getEnv('HTTP_X_FORWARDED_FOR') returns (string)['1.1.1.1'] which becomes "Array", not the expected IP address string.

This breaks the logic that parses client IPs from headers.


Expected behavior:

  • withAddedHeader() should not always cast header values to arrays internally, or
  • getEnv() should handle array values safely and not blindly cast them to strings.

Actual behavior:

  • Even single header values are stored as arrays in $_environment.
  • getEnv() casts arrays to strings, causing PHP warning and breaking dependent methods like clientIp().

Example test demonstrating expected behavior (which currently fails):

public function testClientIpWithSingleXForwardedForHeader()
{
    $request = new ServerRequest();

    $request->trustProxy = true;

    $request = $request->withAddedHeader('X-Forwarded-For', '1.1.1.1');

    $this->assertSame('1.1.1.1', $request->getEnv('HTTP_X_FORWARDED_FOR'));
    $this->assertSame('1.1.1.1', $request->clientIp());
}

CakePHP Version

5.x

PHP Version

8.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions