Skip to content

Commit a3f03b3

Browse files
Merge pull request #87 from WoltLab/normalize-line-folding
Normalize line-folded headers values to not contain newlines
2 parents 7556371 + da685b9 commit a3f03b3

File tree

2 files changed

+31
-3
lines changed

2 files changed

+31
-3
lines changed

src/MessageTrait.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use function is_string;
1919
use function preg_match;
2020
use function sprintf;
21+
use function str_replace;
2122
use function strtolower;
2223
use function trim;
2324

@@ -399,8 +400,13 @@ private function filterHeaderValue($values) : array
399400
return array_map(function ($value) {
400401
HeaderSecurity::assertValid($value);
401402

403+
$value = (string)$value;
404+
405+
// Normalize line folding to a single space (RFC 7230#3.2.4).
406+
$value = str_replace(["\r\n\t", "\r\n "], ' ', $value);
407+
402408
// Remove optional whitespace (OWS, RFC 7230#3.2.3) around the header value.
403-
return trim((string) $value, "\t ");
409+
return trim($value, "\t ");
404410
}, array_values($values));
405411
}
406412

test/MessageTraitTest.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,13 @@ public function testDoesNotAllowCRLFInjectionWhenCallingWithAddedHeader($name, $
327327
public function testWithHeaderAllowsHeaderContinuations(): void
328328
{
329329
$message = $this->message->withHeader('X-Foo-Bar', "value,\r\n second value");
330-
$this->assertSame("value,\r\n second value", $message->getHeaderLine('X-Foo-Bar'));
330+
$this->assertSame("value, second value", $message->getHeaderLine('X-Foo-Bar'));
331331
}
332332

333333
public function testWithAddedHeaderAllowsHeaderContinuations(): void
334334
{
335335
$message = $this->message->withAddedHeader('X-Foo-Bar', "value,\r\n second value");
336-
$this->assertSame("value,\r\n second value", $message->getHeaderLine('X-Foo-Bar'));
336+
$this->assertSame("value, second value", $message->getHeaderLine('X-Foo-Bar'));
337337
}
338338

339339
/** @return non-empty-array<non-empty-string, array{non-empty-string}> */
@@ -357,6 +357,28 @@ public function testWithHeaderTrimsWhitespace(string $value): void
357357
$this->assertSame(trim($value, "\t "), $message->getHeaderLine('X-Foo-Bar'));
358358
}
359359

360+
/** @return non-empty-array<non-empty-string, array{non-empty-string}> */
361+
public function headersWithContinuation(): array
362+
{
363+
return [
364+
'space' => ["foo\r\n bar"],
365+
'tab' => ["foo\r\n\tbar"],
366+
];
367+
}
368+
369+
/**
370+
* @dataProvider headersWithContinuation
371+
*/
372+
public function testWithHeaderNormalizesContinuationToNotContainNewlines(string $value): void
373+
{
374+
$message = $this->message->withHeader('X-Foo-Bar', $value);
375+
// Newlines must no longer appear.
376+
$this->assertStringNotContainsString("\r", $message->getHeaderLine('X-Foo-Bar'));
377+
$this->assertStringNotContainsString("\n", $message->getHeaderLine('X-Foo-Bar'));
378+
// But there must be at least one space.
379+
$this->assertStringContainsString(' ', $message->getHeaderLine('X-Foo-Bar'));
380+
}
381+
360382
/** @return non-empty-array<non-empty-string, array{int|float}> */
361383
public function numericHeaderValuesProvider(): array
362384
{

0 commit comments

Comments
 (0)