Skip to content

Commit 07475df

Browse files
authored
Merge pull request #84 from WoltLab/value-trim
Trim whitespace in header values
2 parents 736ba5c + 74c04d4 commit 07475df

File tree

5 files changed

+73
-3
lines changed

5 files changed

+73
-3
lines changed

src/AbstractSerializer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ protected static function splitStream(StreamInterface $stream) : array
9595
if (! isset($headers[$currentHeader])) {
9696
$headers[$currentHeader] = [];
9797
}
98-
$headers[$currentHeader][] = ltrim($matches['value']);
98+
$headers[$currentHeader][] = trim($matches['value'], "\t ");
9999
continue;
100100
}
101101

@@ -109,7 +109,7 @@ protected static function splitStream(StreamInterface $stream) : array
109109

110110
// Append continuation to last header value found
111111
$value = array_pop($headers[$currentHeader]);
112-
$headers[$currentHeader][] = $value . ' ' . ltrim($line);
112+
$headers[$currentHeader][] = $value . ' ' . trim($line, "\t ");
113113
}
114114

115115
// use RelativeStream to avoid copying initial stream into memory

src/MessageTrait.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,8 @@ private function filterHeaderValue($values) : array
398398
return array_map(function ($value) {
399399
HeaderSecurity::assertValid($value);
400400

401-
return (string) $value;
401+
// Remove optional whitespace (OWS, RFC 7230#3.2.3) around the header value.
402+
return trim((string) $value, "\t ");
402403
}, array_values($values));
403404
}
404405

test/MessageTraitTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,27 @@ public function testWithAddedHeaderAllowsHeaderContinuations(): void
336336
$this->assertSame("value,\r\n second value", $message->getHeaderLine('X-Foo-Bar'));
337337
}
338338

339+
/** @return non-empty-array<non-empty-string, array{non-empty-string}> */
340+
public function headersWithWhitespace(): array
341+
{
342+
return [
343+
'no' => ["Baz"],
344+
'leading' => [" Baz"],
345+
'trailing' => ["Baz "],
346+
'both' => [" Baz "],
347+
'mixed' => [" \t Baz\t \t"],
348+
];
349+
}
350+
351+
/**
352+
* @dataProvider headersWithWhitespace
353+
*/
354+
public function testWithHeaderTrimsWhitespace(string $value): void
355+
{
356+
$message = $this->message->withHeader('X-Foo-Bar', $value);
357+
$this->assertSame(trim($value, "\t "), $message->getHeaderLine('X-Foo-Bar'));
358+
}
359+
339360
/** @return non-empty-array<non-empty-string, array{int|float}> */
340361
public function numericHeaderValuesProvider(): array
341362
{

test/Request/SerializerTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,30 @@ public function testCanDeserializeResponseWithHeaderContinuations($text) : void
247247
$this->assertSame('Baz; Bat', $request->getHeaderLine('X-Foo-Bar'));
248248
}
249249

250+
/** @return non-empty-array<non-empty-string, array{non-empty-string}> */
251+
public function headersWithWhitespace(): array
252+
{
253+
return [
254+
'no' => ["POST /foo HTTP/1.0\r\nContent-Type: text/plain\r\nX-Foo-Bar:Baz\r\n\r\nContent!"],
255+
'leading' => ["POST /foo HTTP/1.0\r\nContent-Type: text/plain\r\nX-Foo-Bar: Baz\r\n\r\nContent!"],
256+
'trailing' => ["POST /foo HTTP/1.0\r\nContent-Type: text/plain\r\nX-Foo-Bar:Baz \r\n\r\nContent!"],
257+
'both' => ["POST /foo HTTP/1.0\r\nContent-Type: text/plain\r\nX-Foo-Bar: Baz \r\n\r\nContent!"],
258+
'mixed' => ["POST /foo HTTP/1.0\r\nContent-Type: text/plain\r\nX-Foo-Bar: \t Baz\t \t\r\n\r\nContent!"],
259+
];
260+
}
261+
262+
/**
263+
* @dataProvider headersWithWhitespace
264+
*/
265+
public function testDeserializationRemovesWhitespaceAroundValues(string $text): void
266+
{
267+
$request = Serializer::fromString($text);
268+
269+
$this->assertInstanceOf(Request::class, $request);
270+
271+
$this->assertSame('Baz', $request->getHeaderLine('X-Foo-Bar'));
272+
}
273+
250274
public function messagesWithInvalidHeaders() : array
251275
{
252276
return [

test/Response/SerializerTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,30 @@ public function testCanDeserializeResponseWithHeaderContinuations($text)
121121
$this->assertSame('Baz; Bat', $response->getHeaderLine('X-Foo-Bar'));
122122
}
123123

124+
/** @return non-empty-array<non-empty-string, array{non-empty-string}> */
125+
public function headersWithWhitespace(): array
126+
{
127+
return [
128+
'no' => ["HTTP/1.0 200 A-OK\r\nContent-Type: text/plain\r\nX-Foo-Bar:Baz\r\n\r\nContent!"],
129+
'leading' => ["HTTP/1.0 200 A-OK\r\nContent-Type: text/plain\r\nX-Foo-Bar: Baz\r\n\r\nContent!"],
130+
'trailing' => ["HTTP/1.0 200 A-OK\r\nContent-Type: text/plain\r\nX-Foo-Bar:Baz \r\n\r\nContent!"],
131+
'both' => ["HTTP/1.0 200 A-OK\r\nContent-Type: text/plain\r\nX-Foo-Bar: Baz \r\n\r\nContent!"],
132+
'mixed' => ["HTTP/1.0 200 A-OK\r\nContent-Type: text/plain\r\nX-Foo-Bar: \t Baz\t \t\r\n\r\nContent!"],
133+
];
134+
}
135+
136+
/**
137+
* @dataProvider headersWithWhitespace
138+
*/
139+
public function testDeserializationRemovesWhitespaceAroundValues(string $text): void
140+
{
141+
$response = Serializer::fromString($text);
142+
143+
$this->assertInstanceOf(Response::class, $response);
144+
145+
$this->assertSame('Baz', $response->getHeaderLine('X-Foo-Bar'));
146+
}
147+
124148
public function testCanDeserializeResponseWithoutBody()
125149
{
126150
$text = "HTTP/1.0 204\r\nX-Foo-Bar: Baz";

0 commit comments

Comments
 (0)