Skip to content

Commit 85fe231

Browse files
committed
fix: Add validation in CodeIgniter\HTTP\Header
1 parent a3704a1 commit 85fe231

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

system/HTTP/Header.php

Lines changed: 48 additions & 1 deletion
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,13 @@ 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);
91+
8792
$this->name = $name;
8893

8994
return $this;
@@ -95,11 +100,15 @@ public function setName(string $name)
95100
* @param array<int|string, array<string, string>|string>|string|null $value
96101
*
97102
* @return $this
103+
*
104+
* @throws InvalidArgumentException
98105
*/
99106
public function setValue($value = null)
100107
{
101108
$this->value = is_array($value) ? $value : (string) $value;
102109

110+
$this->validateValue($value);
111+
103112
return $this;
104113
}
105114

@@ -110,13 +119,17 @@ public function setValue($value = null)
110119
* @param array<string, string>|string|null $value
111120
*
112121
* @return $this
122+
*
123+
* @throws InvalidArgumentException
113124
*/
114125
public function appendValue($value = null)
115126
{
116127
if ($value === null) {
117128
return $this;
118129
}
119130

131+
$this->validateValue($value);
132+
120133
if (! is_array($this->value)) {
121134
$this->value = [$this->value];
122135
}
@@ -135,9 +148,13 @@ public function appendValue($value = null)
135148
* @param array<string, string>|string|null $value
136149
*
137150
* @return $this
151+
*
152+
* @throws InvalidArgumentException
138153
*/
139154
public function prependValue($value = null)
140155
{
156+
$this->validateValue($value);
157+
141158
if ($value === null) {
142159
return $this;
143160
}
@@ -193,4 +210,34 @@ public function __toString(): string
193210
{
194211
return $this->name . ': ' . $this->getValueLine();
195212
}
213+
214+
/**
215+
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
216+
*
217+
* @throws InvalidArgumentException
218+
*/
219+
private function validateName(string $name): void
220+
{
221+
if (preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@D", $name) !== 1) {
222+
throw new InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
223+
}
224+
}
225+
226+
/**
227+
* @param array<string, string>|string|null $value
228+
*
229+
* @throws InvalidArgumentException
230+
*/
231+
private function validateValue($value): void
232+
{
233+
if (is_string($value) && preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@D", $value) !== 1) {
234+
throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
235+
}
236+
237+
if (is_array($value)) {
238+
array_map(function ($v): void {
239+
$this->validateValue($v);
240+
}, $value);
241+
}
242+
}
196243
}

tests/system/HTTP/HeaderTest.php

Lines changed: 68 additions & 0 deletions
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,70 @@ 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+
[''],
271+
];
272+
}
273+
274+
/**
275+
* @param array<int|string, array<string, string>|string>|string|null $value
276+
*/
277+
#[DataProvider('invalidValuesProvider')]
278+
public function testInvalidHeaderValues($value): void
279+
{
280+
$this->expectException(InvalidArgumentException::class);
281+
282+
new Header('X-Test-Header', $value);
283+
}
284+
285+
/**
286+
* @return list<list<list<string>|string>>
287+
*/
288+
public static function invalidValuesProvider(): array
289+
{
290+
return [
291+
["Header\n Value"],
292+
["Header\r\n Value"],
293+
["Header\r Value"],
294+
["Header Value\n"],
295+
["\nHeader Value"],
296+
["Header Value\r\n"],
297+
["\n\rHeader Value"],
298+
["\n\nHeader Value\n\n"],
299+
[
300+
["Header\n Value"],
301+
["Header\r\n Value"],
302+
],
303+
];
304+
}
237305
}

0 commit comments

Comments
 (0)