Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: Throw exception when passing objects to Headers::set() #1140

2 changes: 1 addition & 1 deletion Neos.Flow/Classes/Http/AbstractMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public function hasHeader($name)
* GMT previously. GMT is used synonymously with UTC as per RFC 2616 3.3.1.
*
* @param string $name Name of the header, for example "Location", "Content-Description" etc.
* @param array|string|\DateTime $values An array of values or a single value for the specified header field
* @param array|string|\DateTimeInterface $values An array of strings or a single string/DateTime for the specified header field
* @param boolean $replaceExistingHeader If a header with the same name should be replaced. Default is TRUE.
* @return self This message, for method chaining
* @throws \InvalidArgumentException
Expand Down
2 changes: 1 addition & 1 deletion Neos.Flow/Classes/Http/BaseRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public function setContent($content)
{
if (is_resource($content) && get_resource_type($content) === 'stream' && stream_is_local($content)) {
$streamMetaData = stream_get_meta_data($content);
$this->headers->set('Content-Length', filesize($streamMetaData['uri']));
$this->headers->set('Content-Length', (string)filesize($streamMetaData['uri']));
$this->headers->set('Content-Type', MediaTypes::getMediaTypeFromFilename($streamMetaData['uri']));
}

Expand Down
16 changes: 12 additions & 4 deletions Neos.Flow/Classes/Http/Headers.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ public static function createFromServer(array $server)
foreach ($server as $name => $value) {
if (strpos($name, 'HTTP_') === 0) {
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
$headerFields[$name] = $value;
$headerFields[$name] = (string)$value;
} elseif ($name == 'REDIRECT_REMOTE_AUTHORIZATION' && !isset($headerFields['Authorization'])) {
$headerFields['Authorization'] = $value;
} elseif (in_array($name, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) {
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name))));
$headerFields[$name] = $value;
$headerFields[$name] = (string)$value;
}
}
return new self($headerFields);
Expand All @@ -95,7 +95,7 @@ public static function createFromServer(array $server)
* GMT previously. GMT is used synonymously with UTC as per RFC 2616 3.3.1.
*
* @param string $name Name of the header, for example "Location", "Content-Description" etc.
* @param array|string|\DateTime $values An array of values or a single value for the specified header field
* @param string|string[]|\DateTime $values An array of values or a single value for the specified header field
* @param boolean $replaceExistingHeader If a header with the same name should be replaced. Default is TRUE.
* @return void
* @throws \InvalidArgumentException
Expand All @@ -107,8 +107,16 @@ public function set($name, $values, $replaceExistingHeader = true)
$date = clone $values;
$date->setTimezone(new \DateTimeZone('GMT'));
$values = [$date->format('D, d M Y H:i:s') . ' GMT'];
} elseif (is_string($values)) {
$values = [$values];
} elseif (is_array($values)) {
array_walk($values, function ($item) {
if (!is_string($item)) {
throw new \InvalidArgumentException(sprintf('The header value must be a string, string array or an instance of DateTimeInterface, but the given array contains an instance of %s', is_object($item) ? get_class($item) : gettype($item)), 1523973518);
}
});
} else {
$values = (array) $values;
throw new \InvalidArgumentException(sprintf('The header value must be a string, string array or an instance of DateTimeInterface, given: %s', is_object($values) ? get_class($values) : gettype($values)), 1513332376);
}

switch ($name) {
Expand Down
4 changes: 2 additions & 2 deletions Neos.Flow/Classes/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -487,13 +487,13 @@ public function makeStandardsCompliant(Request $request)

if ($request->getMethod() === 'HEAD') {
if (!$this->headers->has('Content-Length')) {
$this->headers->set('Content-Length', strlen($this->content));
$this->headers->set('Content-Length', (string)strlen($this->content));
}
$this->content = '';
}

if (!$this->headers->has('Content-Length')) {
$this->headers->set('Content-Length', strlen($this->content));
$this->headers->set('Content-Length', (string)strlen($this->content));
}

if ($this->headers->has('Transfer-Encoding')) {
Expand Down
80 changes: 79 additions & 1 deletion Neos.Flow/Tests/Unit/Http/HeadersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,40 @@ public function setGetAndGetAllConvertDatesFromDateObjectsToStringAndViceVersa()
$this->assertEquals($nowInGmt->format(DATE_RFC2822), $headers->get('X-Test-Run-At')->format(DATE_RFC2822));
}

public function setThrowsExceptionForInvalidValuesDataProvider()
{
return [
[new \stdClass()],
[1234],
[true],
[['foo', 'bar', 123]],
];
}

/**
* @test
* @expectedException \InvalidArgumentException
* @dataProvider setThrowsExceptionForInvalidValuesDataProvider
*/
public function setThrowsExceptionForInvalidValues($value)
{
$headers = new Headers();
$headers->set('X-Test', $value);
}

/**
* @test
* @expectedException \InvalidArgumentException
*/
public function setThrowsExceptionWhenValueIsAnInteger()
{
$headers = new Headers();
$headers->set('X-Test', 123);
}


#$headers->set('X-Array', ['some', 'array', 123]);

/**
* @test
*/
Expand Down Expand Up @@ -329,7 +363,7 @@ public function setCacheControlDirectiveSetsVisibilityCorrectly()
public function setExceptsHttpsHeaders()
{
$headers = new Headers();
$headers->set('HTTPS', 1);
$headers->set('HTTPS', '1');
}

/**
Expand Down Expand Up @@ -469,4 +503,48 @@ public function getCacheControlDirectiveReturnsTheSpecifiedDirectiveValueIfPrese
}
$this->assertEquals($value, $headers->getCacheControlDirective($name));
}

/**
* @test
*/
public function getPreparedValuesRendersStringsAsIs()
{
$headers = new Headers();
$headers->set('X-String', 'Some String');

$expectedResult = [
'X-String: Some String',
];
$this->assertSame($expectedResult, $headers->getPreparedValues());
}

/**
* @test
*/
public function getPreparedValuesRendersDateTimeInstancesAsGmtString()
{
$headers = new Headers();
$headers->set('X-Date', new \DateTimeImmutable('1980-12-13'));

$expectedResult = [
'X-Date: Sat, 13 Dec 1980 00:00:00 GMT',
];
$this->assertSame($expectedResult, $headers->getPreparedValues());
}

/**
* @test
*/
public function getPreparedValuesRendersOneHeaderPerArrayItem()
{
$headers = new Headers();
$headers->set('X-Array', ['some', 'array', '123']);

$expectedResult = [
'X-Array: some',
'X-Array: array',
'X-Array: 123',
];
$this->assertSame($expectedResult, $headers->getPreparedValues());
}
}
12 changes: 6 additions & 6 deletions Neos.Flow/Tests/Unit/Http/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,9 @@ public function getAgeReturnsTimeSpecifiedInAgeHeaderIfExists()

$response = new Response();
$response->setNow($now);
$response->setHeader('Age', 123);
$response->setHeader('Age', '123');

$this->assertSame(123, $response->getAge());
$this->assertSame('123', $response->getAge());
}

/**
Expand Down Expand Up @@ -472,7 +472,7 @@ public function makeStandardsCompliantRemovesTheContentLengthHeaderIfTransferLen

$response->setContent($content);
$response->setHeader('Transfer-Encoding', 'chunked');
$response->setHeader('Content-Length', strlen($content));
$response->setHeader('Content-Length', (string)strlen($content));
$response->makeStandardsCompliant($request);
$this->assertFalse($response->hasHeader('Content-Length'));
}
Expand Down Expand Up @@ -526,9 +526,9 @@ public function makeStandardsCompliantSetsBodyAndContentLengthForHeadRequests()
$this->assertEquals(strlen($content), $response->getHeader('Content-Length'));

$response = new Response();
$response->setHeader('Content-Length', 275);
$response->setHeader('Content-Length', '275');
$response->makeStandardsCompliant($request);
$this->assertEquals(275, $response->getHeader('Content-Length'));
$this->assertEquals('275', $response->getHeader('Content-Length'));
}

/**
Expand Down Expand Up @@ -560,7 +560,7 @@ public function makeStandardsCompliantRemovesMaxAgeDireciveIfExpiresHeaderIsPres
public function makeStandardsCompliantReturns304ResponseIfResourceWasNotModified()
{
$modifiedSince = \DateTime::createFromFormat(DATE_RFC2822, 'Sun, 20 May 2012 12:00:00 GMT');
$lastModified = \DateTime::createFromFormat(DATE_RFC2822, 'Fr, 18 May 2012 12:00:00 GMT');
$lastModified = \DateTime::createFromFormat(DATE_RFC2822, 'Fri, 18 May 2012 12:00:00 GMT');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change got bigger than I wanted it to be. But this one is a good example why it might be worth it:
$lastModified evalutated false because the given date was invalid. The test didn't actually test what it was supposed to test. It only failed because false is no longer allowed


$request = Request::create(new Uri('http://localhost'));
$response = new Response();
Expand Down