Skip to content

Commit 998c8db

Browse files
committed
:octocat: HeaderUtil::normalize(): remove CRLF from header names and values (GHSA-wxmh-65f7-jcvw)
1 parent 131a73e commit 998c8db

File tree

2 files changed

+40
-15
lines changed

2 files changed

+40
-15
lines changed

Diff for: src/HeaderUtil.php

+24-13
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212

1313
namespace chillerlan\HTTP\Utils;
1414

15-
use function array_keys, array_values, count, explode, implode,
16-
is_array, is_numeric, is_string, strtolower, trim, ucfirst;
15+
use InvalidArgumentException;
16+
use function array_keys, array_values, count, explode, implode, is_array,
17+
is_numeric, is_scalar, is_string, str_replace, strtolower, trim, ucfirst;
1718

1819
/**
1920
*
@@ -48,27 +49,29 @@ public static function normalize(iterable $headers):array{
4849

4950
// array received from Message::getHeaders()
5051
if(is_array($val)){
51-
foreach($val as $line){
52+
foreach(self::trimValues($val) as $line){
5253
$normalized[$key][$name($line)] = trim($line);
5354
}
5455
}
5556
else{
56-
$normalized[$key][$name($val)] = trim($val);
57+
$val = self::trimValues([$val])[0];
58+
59+
$normalized[$key][$name($val)] = $val;
5760
}
5861
}
5962
// combine header fields with the same name
6063
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
6164
else{
6265

63-
// the key is named, so we assume $val holds the header values only, either as string or array
64-
if(is_array($val)){
65-
$val = implode(', ', array_values($val));
66+
if(!is_array($val)){
67+
$val = [$val];
6668
}
6769

68-
$val = trim((string)($val ?? ''));
70+
/** @noinspection PhpParamsInspection */
71+
$val = implode(', ', array_values(self::trimValues($val)));
6972

7073
// skip if the header already exists but the current value is empty
71-
if(isset($normalized[$key]) && empty($val)){
74+
if(isset($normalized[$key]) && $val === ''){
7275
continue;
7376
}
7477

@@ -117,25 +120,33 @@ protected static function normalizeKV(mixed $value):array{
117120
* OWS = *( SP / HTAB )
118121
*
119122
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
123+
* @see https://github.com/advisories/GHSA-wxmh-65f7-jcvw
120124
*/
121125
public static function trimValues(iterable $values):iterable{
122126

123127
foreach($values as &$value){
124-
$value = trim((string)($value ?? ''), " \t");
128+
129+
if(!is_scalar($value) && $value !== null){
130+
throw new InvalidArgumentException('value is expected to be scalar or null');
131+
}
132+
133+
$value = trim(str_replace(["\r", "\n"], '', (string)($value ?? '')));
125134
}
126135

127136
return $values;
128137
}
129138

130139
/**
131140
* Normalizes a header name, e.g. "con TENT- lenGTh" -> "Content-Length"
141+
*
142+
* @see https://github.com/advisories/GHSA-wxmh-65f7-jcvw
132143
*/
133144
public static function normalizeHeaderName(string $name):string{
134-
$parts = explode('-', $name);
145+
// we'll remove any spaces as well as CRLF in the name, e.g. "con tent" -> "content"
146+
$parts = explode('-', str_replace([' ', "\r", "\n"], '', $name));
135147

136148
foreach($parts as &$part){
137-
// we'll remove any spaces in the name part, e.g. "con tent" -> "content"
138-
$part = ucfirst(strtolower(str_replace(' ', '', trim($part))));
149+
$part = ucfirst(strtolower(trim($part)));
139150
}
140151

141152
return implode('-', $parts);

Diff for: tests/HeaderUtilTest.php

+16-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static function headerDataProvider():array{
4040
'empty value' => [['empty-value' => ''], ['Empty-Value' => '']],
4141
'null value' => [['null-value' => null], ['Null-Value' => '']],
4242
'space in name' => [['space name - header' => 'nope'], ['Spacename-Header' => 'nope']],
43+
'CRLF' => [["CR\rLF-\nin-Na\r\n\r\nme" => " CR\rLF-\nin-va\r\n\r\nlue "], ['Crlf-In-Name' => 'CRLF-in-value']],
4344
];
4445
}
4546

@@ -133,6 +134,7 @@ public static function headerNameProvider():array{
133134
'UPPERCASEKEY' => ['UPPERCASEKEY', 'Uppercasekey'],
134135
'mIxEdCaSeKey' => ['mIxEdCaSeKey', 'Mixedcasekey'],
135136
'31i71casekey' => ['31i71casekey', '31i71casekey'],
137+
'CRLF-In-Name' => ["CR\rLF-\nin-Na\r\n\r\nme", 'Crlf-In-Name'],
136138
];
137139
}
138140

@@ -141,8 +143,20 @@ public function testNormalizeHeaderName(string $name, string $expected):void{
141143
$this::assertSame($expected, HeaderUtil::normalizeHeaderName($name));
142144
}
143145

144-
public function testTrimValues():void{
145-
$this::assertSame(['69', '420'], HeaderUtil::trimValues([69, ' 420 ']));
146+
public static function headerValueProvider():array{
147+
return [
148+
'boolean' => [true, '1'],
149+
'float' => [69.420, '69.42'],
150+
'int' => [69, '69'],
151+
'numeric string' => ['69.420', '69.420'],
152+
'string with whitespace' => [' hello ', 'hello'],
153+
'CRLF-In-Value' => [" CR\rLF-\nIn-Va\r\n\r\nlue ", 'CRLF-In-Value'],
154+
];
155+
}
156+
157+
#[DataProvider('headerValueProvider')]
158+
public function testTrimValues(string|int|float|bool $value, string $expected):void{
159+
$this::assertSame([$expected], HeaderUtil::trimValues([$value]));
146160
}
147161

148162
}

0 commit comments

Comments
 (0)