Skip to content

Commit 11d949b

Browse files
Merge branch '2.1'
2 parents a7b4320 + 53491b6 commit 11d949b

File tree

4 files changed

+94
-5
lines changed

4 files changed

+94
-5
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
## 2.1.1 - 2022-03-20
11+
12+
### Fixed
13+
14+
- Validate header values properly
15+
1016
## 2.1.0 - 2021-10-06
1117

1218
### Changed

composer.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@
8787
"bamarni/composer-bin-plugin": true
8888
},
8989
"preferred-install": "dist",
90-
"sort-packages": true
90+
"sort-packages": true,
91+
"allow-plugins": {
92+
"bamarni/composer-bin-plugin": true
93+
}
9194
}
9295
}

src/MessageTrait.php

+35-4
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,14 @@ private function setHeaders(array $headers): void
171171
private function normalizeHeaderValue($value): array
172172
{
173173
if (!is_array($value)) {
174-
return $this->trimHeaderValues([$value]);
174+
return $this->trimAndValidateHeaderValues([$value]);
175175
}
176176

177177
if (count($value) === 0) {
178178
throw new \InvalidArgumentException('Header value can not be an empty array.');
179179
}
180180

181-
return $this->trimHeaderValues($value);
181+
return $this->trimAndValidateHeaderValues($value);
182182
}
183183

184184
/**
@@ -195,7 +195,7 @@ private function normalizeHeaderValue($value): array
195195
*
196196
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
197197
*/
198-
private function trimHeaderValues(array $values): array
198+
private function trimAndValidateHeaderValues(array $values): array
199199
{
200200
return array_map(function ($value) {
201201
if (!is_scalar($value) && null !== $value) {
@@ -205,7 +205,10 @@ private function trimHeaderValues(array $values): array
205205
));
206206
}
207207

208-
return trim((string) $value, " \t");
208+
$trimmed = trim((string) $value, " \t");
209+
$this->assertValue($trimmed);
210+
211+
return $trimmed;
209212
}, array_values($values));
210213
}
211214

@@ -232,4 +235,32 @@ private function assertHeader($header): void
232235
);
233236
}
234237
}
238+
239+
/**
240+
* @see https://tools.ietf.org/html/rfc7230#section-3.2
241+
*
242+
* field-value = *( field-content / obs-fold )
243+
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
244+
* field-vchar = VCHAR / obs-text
245+
* VCHAR = %x21-7E
246+
* obs-text = %x80-FF
247+
* obs-fold = CRLF 1*( SP / HTAB )
248+
*/
249+
private function assertValue(string $value): void
250+
{
251+
// The regular expression intentionally does not support the obs-fold production, because as
252+
// per RFC 7230#3.2.4:
253+
//
254+
// A sender MUST NOT generate a message that includes
255+
// line folding (i.e., that has any field-value that contains a match to
256+
// the obs-fold rule) unless the message is intended for packaging
257+
// within the message/http media type.
258+
//
259+
// Clients must not send a request with line folding and a server sending folded headers is
260+
// likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
261+
// folding is not likely to break any legitimate use case.
262+
if (! preg_match('/^(?:[\x21-\x7E\x80-\xFF](?:[\x20\x09]+[\x21-\x7E\x80-\xFF])?)*$/', $value)) {
263+
throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value));
264+
}
265+
}
235266
}

tests/RequestTest.php

+49
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,53 @@ public function testAddsPortToHeaderAndReplacePreviousPort(): void
293293
$r = $r->withUri(new Uri('http://foo.com:8125/bar'));
294294
self::assertSame('foo.com:8125', $r->getHeaderLine('host'));
295295
}
296+
297+
/**
298+
* @dataProvider provideHeaderValuesContainingNotAllowedChars
299+
*/
300+
public function testContainsNotAllowedCharsOnHeaderValue(string $value): void
301+
{
302+
$this->expectException(\InvalidArgumentException::class);
303+
$this->expectExceptionMessage(sprintf('"%s" is not valid header value', $value));
304+
305+
$r = new Request(
306+
'GET',
307+
'http://foo.com/baz?bar=bam',
308+
[
309+
'testing' => $value
310+
]
311+
);
312+
}
313+
314+
public function provideHeaderValuesContainingNotAllowedChars(): iterable
315+
{
316+
// Explicit tests for newlines as the most common exploit vector.
317+
$tests = [
318+
["new\nline"],
319+
["new\r\nline"],
320+
["new\rline"],
321+
// Line folding is technically allowed, but deprecated.
322+
// We don't support it.
323+
["new\r\n line"],
324+
];
325+
326+
for ($i = 0; $i <= 0xff; $i++) {
327+
if (\chr($i) == "\t") {
328+
continue;
329+
}
330+
if (\chr($i) == " ") {
331+
continue;
332+
}
333+
if ($i >= 0x21 && $i <= 0x7e) {
334+
continue;
335+
}
336+
if ($i >= 0x80) {
337+
continue;
338+
}
339+
340+
$tests[] = ["foo" . \chr($i) . "bar"];
341+
}
342+
343+
return $tests;
344+
}
296345
}

0 commit comments

Comments
 (0)