From 85fe2310cbdfd853c79f14bd542cbad50aba177d Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 14 Jan 2025 11:03:01 +0300 Subject: [PATCH 1/2] fix: Add validation in `CodeIgniter\HTTP\Header` --- system/HTTP/Header.php | 49 ++++++++++++++++++++++- tests/system/HTTP/HeaderTest.php | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/system/HTTP/Header.php b/system/HTTP/Header.php index 3832a4413a47..d8335a376799 100644 --- a/system/HTTP/Header.php +++ b/system/HTTP/Header.php @@ -13,6 +13,7 @@ namespace CodeIgniter\HTTP; +use InvalidArgumentException; use Stringable; /** @@ -54,7 +55,7 @@ class Header implements Stringable */ public function __construct(string $name, $value = null) { - $this->name = $name; + $this->setName($name); $this->setValue($value); } @@ -81,9 +82,13 @@ public function getValue() * Sets the name of the header, overwriting any previous value. * * @return $this + * + * @throws InvalidArgumentException */ public function setName(string $name) { + $this->validateName($name); + $this->name = $name; return $this; @@ -95,11 +100,15 @@ public function setName(string $name) * @param array|string>|string|null $value * * @return $this + * + * @throws InvalidArgumentException */ public function setValue($value = null) { $this->value = is_array($value) ? $value : (string) $value; + $this->validateValue($value); + return $this; } @@ -110,6 +119,8 @@ public function setValue($value = null) * @param array|string|null $value * * @return $this + * + * @throws InvalidArgumentException */ public function appendValue($value = null) { @@ -117,6 +128,8 @@ public function appendValue($value = null) return $this; } + $this->validateValue($value); + if (! is_array($this->value)) { $this->value = [$this->value]; } @@ -135,9 +148,13 @@ public function appendValue($value = null) * @param array|string|null $value * * @return $this + * + * @throws InvalidArgumentException */ public function prependValue($value = null) { + $this->validateValue($value); + if ($value === null) { return $this; } @@ -193,4 +210,34 @@ public function __toString(): string { return $this->name . ': ' . $this->getValueLine(); } + + /** + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + * + * @throws InvalidArgumentException + */ + private function validateName(string $name): void + { + if (preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@D", $name) !== 1) { + throw new InvalidArgumentException('Header name must be an RFC 7230 compatible string.'); + } + } + + /** + * @param array|string|null $value + * + * @throws InvalidArgumentException + */ + private function validateValue($value): void + { + if (is_string($value) && preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@D", $value) !== 1) { + throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); + } + + if (is_array($value)) { + array_map(function ($v): void { + $this->validateValue($v); + }, $value); + } + } } diff --git a/tests/system/HTTP/HeaderTest.php b/tests/system/HTTP/HeaderTest.php index 6ca5180ff838..752ff6112018 100644 --- a/tests/system/HTTP/HeaderTest.php +++ b/tests/system/HTTP/HeaderTest.php @@ -15,6 +15,8 @@ use CodeIgniter\Test\CIUnitTestCase; use Error; +use InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use stdClass; @@ -234,4 +236,70 @@ public function testHeaderToStringShowsEntireHeader(): void $this->assertSame($expected, (string) $header); } + + /** + * @param string $name + */ + #[DataProvider('invalidNamesProvider')] + public function testInvalidHeaderNames($name): void + { + $this->expectException(InvalidArgumentException::class); + + new Header($name, 'text/html'); + } + + /** + * @return list> + */ + public static function invalidNamesProvider(): array + { + return [ + ["Content-Type\r\n\r\n"], + ["Content-Type\r\n"], + ["Content-Type\n"], + ["\tContent-Type\t"], + ["\n\nContent-Type\n\n"], + ["\r\nContent-Type"], + ["\nContent-Type"], + ["Content\r\n-Type"], + ["\n"], + ["\r\n"], + ["\t"], + [' Content-Type '], + ['Content - Type'], + [''], + ]; + } + + /** + * @param array|string>|string|null $value + */ + #[DataProvider('invalidValuesProvider')] + public function testInvalidHeaderValues($value): void + { + $this->expectException(InvalidArgumentException::class); + + new Header('X-Test-Header', $value); + } + + /** + * @return list|string>> + */ + public static function invalidValuesProvider(): array + { + return [ + ["Header\n Value"], + ["Header\r\n Value"], + ["Header\r Value"], + ["Header Value\n"], + ["\nHeader Value"], + ["Header Value\r\n"], + ["\n\rHeader Value"], + ["\n\nHeader Value\n\n"], + [ + ["Header\n Value"], + ["Header\r\n Value"], + ], + ]; + } } From b335b82e4e22b373aaaa4855479a4b03010407e9 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 14 Jan 2025 11:03:20 +0300 Subject: [PATCH 2/2] docs: Update changelog --- user_guide_src/source/changelogs/v4.5.8.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/user_guide_src/source/changelogs/v4.5.8.rst b/user_guide_src/source/changelogs/v4.5.8.rst index ae29b59fb165..e02175a6b361 100644 --- a/user_guide_src/source/changelogs/v4.5.8.rst +++ b/user_guide_src/source/changelogs/v4.5.8.rst @@ -14,6 +14,11 @@ Release Date: Unreleased BREAKING ******** +Header +====== + +Added validation of the name and value for ``CodeIgniter\HTTP\Header``. Some specific headers can cause the system to crash. + *************** Message Changes *************** @@ -31,6 +36,7 @@ Bugs Fixed ********** - **Database:** Fixed a bug where ``Builder::affectedRows()`` threw an error when the previous query call failed in ``Postgre`` and ``SQLSRV`` drivers. +- **Header:** Improper headers parsing. Line breaks and other incorrect characters in headers (``CodeIgniter\HTTP\Header``) may break the HTTP request. See https://datatracker.ietf.org/doc/html/rfc7230 for more details. See the repo's `CHANGELOG.md `_