Skip to content

Commit 5f8aa24

Browse files
michalsnneznaika0paulbalandan
authored
Merge commit from fork
Co-authored-by: neznaika0 <[email protected]> Co-authored-by: John Paul E. Balandan, CPA <[email protected]>
1 parent 119330c commit 5f8aa24

File tree

3 files changed

+159
-2
lines changed

3 files changed

+159
-2
lines changed

system/HTTP/Header.php

+70-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\HTTP;
1515

16+
use InvalidArgumentException;
1617
use Stringable;
1718

1819
/**
@@ -54,7 +55,7 @@ class Header implements Stringable
5455
*/
5556
public function __construct(string $name, $value = null)
5657
{
57-
$this->name = $name;
58+
$this->setName($name);
5859
$this->setValue($value);
5960
}
6061

@@ -81,9 +82,12 @@ public function getValue()
8182
* Sets the name of the header, overwriting any previous value.
8283
*
8384
* @return $this
85+
*
86+
* @throws InvalidArgumentException
8487
*/
8588
public function setName(string $name)
8689
{
90+
$this->validateName($name);
8791
$this->name = $name;
8892

8993
return $this;
@@ -95,10 +99,16 @@ public function setName(string $name)
9599
* @param array<int|string, array<string, string>|string>|string|null $value
96100
*
97101
* @return $this
102+
*
103+
* @throws InvalidArgumentException
98104
*/
99105
public function setValue($value = null)
100106
{
101-
$this->value = is_array($value) ? $value : (string) $value;
107+
$value = is_array($value) ? $value : (string) $value;
108+
109+
$this->validateValue($value);
110+
111+
$this->value = $value;
102112

103113
return $this;
104114
}
@@ -110,13 +120,17 @@ public function setValue($value = null)
110120
* @param array<string, string>|string|null $value
111121
*
112122
* @return $this
123+
*
124+
* @throws InvalidArgumentException
113125
*/
114126
public function appendValue($value = null)
115127
{
116128
if ($value === null) {
117129
return $this;
118130
}
119131

132+
$this->validateValue($value);
133+
120134
if (! is_array($this->value)) {
121135
$this->value = [$this->value];
122136
}
@@ -135,13 +149,17 @@ public function appendValue($value = null)
135149
* @param array<string, string>|string|null $value
136150
*
137151
* @return $this
152+
*
153+
* @throws InvalidArgumentException
138154
*/
139155
public function prependValue($value = null)
140156
{
141157
if ($value === null) {
142158
return $this;
143159
}
144160

161+
$this->validateValue($value);
162+
145163
if (! is_array($this->value)) {
146164
$this->value = [$this->value];
147165
}
@@ -193,4 +211,54 @@ public function __toString(): string
193211
{
194212
return $this->name . ': ' . $this->getValueLine();
195213
}
214+
215+
/**
216+
* Validate header name.
217+
*
218+
* Regex is based on code from a guzzlehttp/psr7 library.
219+
*
220+
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
221+
*
222+
* @throws InvalidArgumentException
223+
*/
224+
private function validateName(string $name): void
225+
{
226+
if (preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $name) !== 1) {
227+
throw new InvalidArgumentException('The header name is not valid as per RFC 7230.');
228+
}
229+
}
230+
231+
/**
232+
* Validate header value.
233+
*
234+
* Regex is based on code from a guzzlehttp/psr7 library.
235+
*
236+
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
237+
*
238+
* @param array<int|string, array<string, string>|string>|int|string $value
239+
*
240+
* @throws InvalidArgumentException
241+
*/
242+
private function validateValue(array|int|string $value): void
243+
{
244+
if (is_int($value)) {
245+
return;
246+
}
247+
248+
if (is_array($value)) {
249+
foreach ($value as $key => $val) {
250+
$this->validateValue($key);
251+
$this->validateValue($val);
252+
}
253+
254+
return;
255+
}
256+
257+
// The regular expression excludes obs-fold per RFC 7230#3.2.4, as sending folded lines
258+
// is deprecated and rare. This obscure HTTP/1.1 feature is unlikely to impact legitimate
259+
// use cases. Libraries like Guzzle and AMPHP follow the same principle.
260+
if (preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value) !== 1) {
261+
throw new InvalidArgumentException('The header value is not valid as per RFC 7230.');
262+
}
263+
}
196264
}

tests/system/HTTP/HeaderTest.php

+81
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
use CodeIgniter\Test\CIUnitTestCase;
1717
use Error;
18+
use InvalidArgumentException;
19+
use PHPUnit\Framework\Attributes\DataProvider;
1820
use PHPUnit\Framework\Attributes\Group;
1921
use stdClass;
2022

@@ -234,4 +236,83 @@ public function testHeaderToStringShowsEntireHeader(): void
234236

235237
$this->assertSame($expected, (string) $header);
236238
}
239+
240+
/**
241+
* @param string $name
242+
*/
243+
#[DataProvider('invalidNamesProvider')]
244+
public function testInvalidHeaderNames($name): void
245+
{
246+
$this->expectException(InvalidArgumentException::class);
247+
248+
new Header($name, 'text/html');
249+
}
250+
251+
/**
252+
* @return list<list<string>>
253+
*/
254+
public static function invalidNamesProvider(): array
255+
{
256+
return [
257+
["Content-Type\r\n\r\n"],
258+
["Content-Type\r\n"],
259+
["Content-Type\n"],
260+
["\tContent-Type\t"],
261+
["\n\nContent-Type\n\n"],
262+
["\r\nContent-Type"],
263+
["\nContent-Type"],
264+
["Content\r\n-Type"],
265+
["\n"],
266+
["\r\n"],
267+
["\t"],
268+
[' Content-Type '],
269+
['Content - Type'],
270+
["Content\x00Type"],
271+
[':Content-Type'],
272+
['Content-Type:'],
273+
[''],
274+
];
275+
}
276+
277+
/**
278+
* @param array<int|string, array<string, string>|string>|string|null $value
279+
*/
280+
#[DataProvider('invalidValuesProvider')]
281+
public function testInvalidHeaderValues($value): void
282+
{
283+
$this->expectException(InvalidArgumentException::class);
284+
285+
new Header('X-Test-Header', $value);
286+
}
287+
288+
/**
289+
* @return list<list<array<(int|string), string>|string>>
290+
*/
291+
public static function invalidValuesProvider(): array
292+
{
293+
return [
294+
["Header\n Value"],
295+
["Header\r\n Value"],
296+
["Header\r Value"],
297+
["Header Value\n"],
298+
["\nHeader Value"],
299+
["Header Value\r\n"],
300+
["\n\rHeader Value"],
301+
["\n\nHeader Value\n\n"],
302+
[
303+
["Header\n Value"],
304+
["Header\r\n Value"],
305+
],
306+
[
307+
[
308+
"Header\n" => 'Value',
309+
],
310+
],
311+
[
312+
[
313+
'Header' => "Value\r\n",
314+
],
315+
],
316+
];
317+
}
237318
}

user_guide_src/source/changelogs/v4.5.8.rst

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ Release Date: Unreleased
1010
:local:
1111
:depth: 3
1212

13+
********
14+
SECURITY
15+
********
16+
17+
- **Header:** *Validation of header name and value* was fixed.
18+
See the `Security advisory GHSA-x5mq-jjr3-vmx6 <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-x5mq-jjr3-vmx6>`_
19+
for more information.
20+
1321
********
1422
BREAKING
1523
********

0 commit comments

Comments
 (0)