Skip to content

fix: env() TypeError for non-string $_SERVER values + esc() fixes#10305

Open
gr8man wants to merge 1 commit into
codeigniter4:developfrom
gr8man:fix/common-env-typeerror-esc-leak
Open

fix: env() TypeError for non-string $_SERVER values + esc() fixes#10305
gr8man wants to merge 1 commit into
codeigniter4:developfrom
gr8man:fix/common-env-typeerror-esc-leak

Conversation

@gr8man

@gr8man gr8man commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Fix: env() TypeError in CLI + esc() improvements

What

Fixes two bugs in system/Common.php.

env() — In CLI, $_SERVER contains non-string values (argc is int, argv is array). Passing them to strtolower() threw a TypeError under declare(strict_types=1). Added an is_string() guard before the match expression; non-string values are returned as-is.

esc() — Three fixes applied together:

  • foreach ($data as &$value) left a dangling reference after the loop; added unset($value) to prevent silent mutation of the last array element in the calling scope
  • $encoding was not propagated in recursive array calls
  • Replaced single static $escaper with static $escapers[] keyed by encoding for correct per-encoding caching

Tests

Added a data-provider test for env() covering non-string $_SERVER/$_ENV values (int, array, float), and three tests verifying the esc() reference leak is gone.

Files changed

  • system/Common.php
  • tests/system/CommonFunctionsTest.php

- env(): guard non-string values (int argc, array argv in CLI) before
  strtolower() to prevent TypeError under declare(strict_types=1)

- esc(): propagate $encoding in recursive array calls (was ignored before),
  add early return after array processing, replace single static $escaper
  with static $escapers[] cache keyed by encoding

- tests: data-provider test for env() non-string types,
  three tests for esc() foreach reference leak
@gr8man gr8man force-pushed the fix/common-env-typeerror-esc-leak branch from 53a365d to 449dbae Compare June 11, 2026 21:43

@michalsn michalsn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please send one change per PR.

This needs a changelog entry.

Comment thread system/Common.php
$value = esc($value, $context);
$value = esc($value, $context, $encoding);
}
unset($value); // Prevent reference leak: &$value would remain bound to last element

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
unset($value); // Prevent reference leak: &$value would remain bound to last element

What reference leak? We return right after this.

Comment thread system/Common.php
$escaper = new Escaper($encoding);
}
static $escapers = [];
$cacheKey = $encoding ?? 'default';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will cause the incorrect encoding default to be accepted after the first normal instantiation.

Please change it to the equivalent of Escaper handling:

Suggested change
$cacheKey = $encoding ?? 'default';
$cacheKey = strtolower($encoding ?? 'utf-8');

@michalsn michalsn added the bug Verified issues on the current code behavior or pull requests that will fix them label Jun 12, 2026
Comment on lines +315 to +344
public function testEscapeArrayDoesNotLeakForeachReference(): void
{
$data = ['first' => '<b>bold</b>', 'last' => '<i>italic</i>'];

$escaped = esc($data);

$this->assertSame('&lt;b&gt;bold&lt;/b&gt;', $escaped['first']);
$this->assertSame('&lt;i&gt;italic&lt;/i&gt;', $escaped['last']);
}

public function testEscapeArrayLastElementNotMutatedAfterCall(): void
{
$data = ['x' => '<script>', 'y' => '<style>'];

$escaped = esc($data);

$this->assertSame('&lt;script&gt;', $escaped['x']);
$this->assertSame('&lt;style&gt;', $escaped['y']);
$this->assertCount(2, $escaped);
}

public function testEscapeArrayReferenceIsCleanedUpOnSingleElement(): void
{
$data = ['only' => '<div>'];

$escaped = esc($data);

$this->assertSame('&lt;div&gt;', $escaped['only']);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
public function testEscapeArrayDoesNotLeakForeachReference(): void
{
$data = ['first' => '<b>bold</b>', 'last' => '<i>italic</i>'];
$escaped = esc($data);
$this->assertSame('&lt;b&gt;bold&lt;/b&gt;', $escaped['first']);
$this->assertSame('&lt;i&gt;italic&lt;/i&gt;', $escaped['last']);
}
public function testEscapeArrayLastElementNotMutatedAfterCall(): void
{
$data = ['x' => '<script>', 'y' => '<style>'];
$escaped = esc($data);
$this->assertSame('&lt;script&gt;', $escaped['x']);
$this->assertSame('&lt;style&gt;', $escaped['y']);
$this->assertCount(2, $escaped);
}
public function testEscapeArrayReferenceIsCleanedUpOnSingleElement(): void
{
$data = ['only' => '<div>'];
$escaped = esc($data);
$this->assertSame('&lt;div&gt;', $escaped['only']);
}

There is no reference leak, so these tests don't prove anything IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Verified issues on the current code behavior or pull requests that will fix them

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants