Fix parsing empty header values.

Replace the complex and somewhat unfixable regexp based parser for
a parser that handles each line individually. Normalize multi-line
headers to replace multiple spaces with a single one. Section 4.2 of the
HTTP1.1 standard states

> Any LWS that occurs between field-content MAY be replaced with
> a single SP before interpreting the field value or forwarding the
> message downstream.

This makes me somewhat confident that we can safely normalize
multi-line HTTP header values.

Refs #8330
This commit is contained in:
mark_story 2016-02-24 22:25:58 -05:00
parent fc714a6451
commit 1a170e1eec
2 changed files with 29 additions and 10 deletions

View file

@ -268,18 +268,28 @@ class HttpSocketResponse implements ArrayAccess {
return false; return false;
} }
preg_match_all("/(.+):(.+)(?:(?<![\t ])\r\n|\$)/Uis", $header, $matches, PREG_SET_ORDER); preg_match_all("/(.+):(.+)(?:\r\n|\$)/Uis", $header, $matches, PREG_SET_ORDER);
$lines = explode("\r\n", $header);
$header = array(); $header = array();
foreach ($matches as $match) { foreach ($lines as $line) {
list(, $field, $value) = $match; if (strlen($line) === 0) {
continue;
}
$continuation = false;
$first = substr($line, 0, 1);
// Multi-line header
if ($first === ' ' || $first === "\t") {
$value .= preg_replace("/\s+/", ' ', $line);
$continuation = true;
} elseif (strpos($line, ':') !== false) {
list($field, $value) = explode(':', $line, 2);
$field = $this->_unescapeToken($field);
}
$value = trim($value); $value = trim($value);
$value = preg_replace("/[\t ]\r\n/", "\r\n", $value); if (!isset($header[$field]) || $continuation) {
$field = $this->_unescapeToken($field);
if (!isset($header[$field])) {
$header[$field] = $value; $header[$field] = $value;
} else { } else {
$header[$field] = array_merge((array)$header[$field], (array)$value); $header[$field] = array_merge((array)$header[$field], (array)$value);

View file

@ -267,10 +267,19 @@ class HttpResponseTest extends CakeTestCase {
); );
$this->assertEquals($expected, $r); $this->assertEquals($expected, $r);
$header = "Multi-Line: I am a \r\nmulti line\t\r\nfield value.\r\nSingle-Line: I am not\r\n"; $header = "Date:Sat, 07 Apr 2007 10:10:25 GMT\r\nLink: \r\nX-Total-Count: 19\r\n";
$r = $this->HttpResponse->parseHeader($header); $r = $this->HttpResponse->parseHeader($header);
$expected = array( $expected = array(
'Multi-Line' => "I am a\r\nmulti line\r\nfield value.", 'Date' => 'Sat, 07 Apr 2007 10:10:25 GMT',
'Link' => '',
'X-Total-Count' => '19',
);
$this->assertEquals($expected, $r);
$header = "Multi-Line: I am a\r\n multi line \r\n\tfield value.\r\nSingle-Line: I am not\r\n";
$r = $this->HttpResponse->parseHeader($header);
$expected = array(
'Multi-Line' => "I am a multi line field value.",
'Single-Line' => 'I am not' 'Single-Line' => 'I am not'
); );
$this->assertEquals($expected, $r); $this->assertEquals($expected, $r);