From d4918f13cfa6cca146b7a85cf61d4e04ea8e8045 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Thu, 23 Mar 2017 10:24:24 +0100 Subject: [PATCH 1/2] Guard Date header in ResponseHeaderBag instead of Response --- .../Component/HttpFoundation/Response.php | 19 ---------- .../HttpFoundation/ResponseHeaderBag.php | 20 ++++++++++ .../Tests/ResponseHeaderBagTest.php | 37 +++++++++++++++++++ 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 4af1e0bae2ecf..3ea4cb6170452 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -201,11 +201,6 @@ public function __construct($content = '', $status = 200, $headers = array()) $this->setContent($content); $this->setStatusCode($status); $this->setProtocolVersion('1.0'); - - /* RFC2616 - 14.18 says all Responses need to have a Date */ - if (!$this->headers->has('Date')) { - $this->setDate(\DateTime::createFromFormat('U', time())); - } } /** @@ -334,11 +329,6 @@ public function sendHeaders() return $this; } - /* RFC2616 - 14.18 says all Responses need to have a Date */ - if (!$this->headers->has('Date')) { - $this->setDate(\DateTime::createFromFormat('U', time())); - } - // headers foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { foreach ($values as $value) { @@ -648,15 +638,6 @@ public function mustRevalidate() */ public function getDate() { - /* - RFC2616 - 14.18 says all Responses need to have a Date. - Make sure we provide one even if it the header - has been removed in the meantime. - */ - if (!$this->headers->has('Date')) { - $this->setDate(\DateTime::createFromFormat('U', time())); - } - return $this->headers->getDate('Date'); } diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index df2931be05a2e..236528de21fe8 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -51,6 +51,11 @@ public function __construct(array $headers = array()) if (!isset($this->headers['cache-control'])) { $this->set('Cache-Control', ''); } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!isset($this->headers['date'])) { + $this->initDate(); + } } /** @@ -90,6 +95,10 @@ public function replace(array $headers = array()) if (!isset($this->headers['cache-control'])) { $this->set('Cache-Control', ''); } + + if (!isset($this->headers['date'])) { + $this->initDate(); + } } /** @@ -156,6 +165,10 @@ public function remove($key) if ('cache-control' === $uniqueKey) { $this->computedCacheControl = array(); } + + if ('date' === $uniqueKey) { + $this->initDate(); + } } /** @@ -338,4 +351,11 @@ protected function computeCacheControlValue() return $header; } + + private function initDate() + { + $now = \DateTime::createFromFormat('U', time()); + $now->setTimezone(new \DateTimeZone('UTC')); + $this->set('Date', $now->format('D, d M Y H:i:s').' GMT'); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index 724328ae850e4..b7e7e6b3f8523 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -332,6 +332,43 @@ public function provideMakeDispositionFail() ); } + public function testDateHeaderAddedOnCreation() + { + $now = time(); + + $bag = new ResponseHeaderBag(); + $this->assertTrue($bag->has('Date')); + + $this->assertEquals($now, $bag->getDate('Date')->getTimestamp()); + } + + public function testDateHeaderCanBeSetOnCreation() + { + $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT'; + $bag = new ResponseHeaderBag(array('Date' => $someDate)); + + $this->assertEquals($someDate, $bag->get('Date')); + } + + public function testDateHeaderWillBeRecreatedWhenRemoved() + { + $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT'; + $bag = new ResponseHeaderBag(array('Date' => $someDate)); + $bag->remove('Date'); + + // a (new) Date header is still present + $this->assertTrue($bag->has('Date')); + $this->assertNotEquals($someDate, $bag->get('Date')); + } + + public function testDateHeaderWillBeRecreatedWhenHeadersAreReplaced() + { + $bag = new ResponseHeaderBag(); + $bag->replace(array()); + + $this->assertTrue($bag->has('Date')); + } + private function assertSetCookieHeader($expected, ResponseHeaderBag $actual) { $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual)); From 32154b14191658cdd32bdf1300ab10fe86c76ba6 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 16 Jun 2017 22:58:11 +0200 Subject: [PATCH 2/2] Fix tests The headers returned now also include the "Date", but adding it to the expected outcome would add additional noise. So instead I chose to test that only the given headers would remain present, preserving case. The Cache-Control was not important here, we have other tests covering that aspect. --- .../Tests/ResponseHeaderBagTest.php | 54 ++++++------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index b7e7e6b3f8523..1d7341f335a93 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -20,48 +20,24 @@ */ class ResponseHeaderBagTest extends TestCase { - /** - * @dataProvider provideAllPreserveCase - */ - public function testAllPreserveCase($headers, $expected) + public function testAllPreserveCase() { - $bag = new ResponseHeaderBag($headers); + $headers = array( + 'fOo' => 'BAR', + 'ETag' => 'xyzzy', + 'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==', + 'P3P' => 'CP="CAO PSA OUR"', + 'WWW-Authenticate' => 'Basic realm="WallyWorld"', + 'X-UA-Compatible' => 'IE=edge,chrome=1', + 'X-XSS-Protection' => '1; mode=block', + ); - $this->assertEquals($expected, $bag->allPreserveCase(), '->allPreserveCase() gets all input keys in original case'); - } + $bag = new ResponseHeaderBag($headers); + $allPreservedCase = $bag->allPreserveCase(); - public function provideAllPreserveCase() - { - return array( - array( - array('fOo' => 'BAR'), - array('fOo' => array('BAR'), 'Cache-Control' => array('no-cache, private')), - ), - array( - array('ETag' => 'xyzzy'), - array('ETag' => array('xyzzy'), 'Cache-Control' => array('private, must-revalidate')), - ), - array( - array('Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ=='), - array('Content-MD5' => array('Q2hlY2sgSW50ZWdyaXR5IQ=='), 'Cache-Control' => array('no-cache, private')), - ), - array( - array('P3P' => 'CP="CAO PSA OUR"'), - array('P3P' => array('CP="CAO PSA OUR"'), 'Cache-Control' => array('no-cache, private')), - ), - array( - array('WWW-Authenticate' => 'Basic realm="WallyWorld"'), - array('WWW-Authenticate' => array('Basic realm="WallyWorld"'), 'Cache-Control' => array('no-cache, private')), - ), - array( - array('X-UA-Compatible' => 'IE=edge,chrome=1'), - array('X-UA-Compatible' => array('IE=edge,chrome=1'), 'Cache-Control' => array('no-cache, private')), - ), - array( - array('X-XSS-Protection' => '1; mode=block'), - array('X-XSS-Protection' => array('1; mode=block'), 'Cache-Control' => array('no-cache, private')), - ), - ); + foreach (array_keys($headers) as $headerName) { + $this->assertArrayHasKey($headerName, $allPreservedCase, '->allPreserveCase() gets all input keys in original case'); + } } public function testCacheControlHeader()