diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
index 941d4c6fa033d..d0f2155143b7f 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
@@ -633,14 +633,6 @@ protected function store(Request $request, Response $response)
*/
private function restoreResponseBody(Request $request, Response $response)
{
- if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) {
- $response->setContent(null);
- $response->headers->remove('X-Body-Eval');
- $response->headers->remove('X-Body-File');
-
- return;
- }
-
if ($response->headers->has('X-Body-Eval')) {
ob_start();
@@ -656,7 +648,11 @@ private function restoreResponseBody(Request $request, Response $response)
$response->headers->set('Content-Length', strlen($response->getContent()));
}
} elseif ($response->headers->has('X-Body-File')) {
- $response->setContent(file_get_contents($response->headers->get('X-Body-File')));
+ // Response does not include possibly dynamic content (ESI, SSI), so we need
+ // not handle the content for HEAD requests
+ if (!$request->isMethod('HEAD')) {
+ $response->setContent(file_get_contents($response->headers->get('X-Body-File')));
+ }
} else {
return;
}
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php
index d75bd00efb6fc..32977cf387c24 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php
@@ -1133,7 +1133,7 @@ public function testEsiCacheSendsTheLowestTtl()
array(
'status' => 200,
'body' => 'Hello World!',
- 'headers' => array('Cache-Control' => 's-maxage=300'),
+ 'headers' => array('Cache-Control' => 's-maxage=200'),
),
array(
'status' => 200,
@@ -1147,8 +1147,33 @@ public function testEsiCacheSendsTheLowestTtl()
$this->request('GET', '/', array(), array(), true);
$this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent());
- // check for 100 or 99 as the test can be executed after a second change
- $this->assertTrue(in_array($this->response->getTtl(), array(99, 100)));
+ $this->assertEquals(100, $this->response->getTtl());
+ }
+
+ public function testEsiCacheSendsTheLowestTtlForHeadRequests()
+ {
+ $responses = array(
+ array(
+ 'status' => 200,
+ 'body' => 'I am a long-lived master response, but I embed a short-lived resource: ',
+ 'headers' => array(
+ 'Cache-Control' => 's-maxage=300',
+ 'Surrogate-Control' => 'content="ESI/1.0"',
+ ),
+ ),
+ array(
+ 'status' => 200,
+ 'body' => 'I am a short-lived resource',
+ 'headers' => array('Cache-Control' => 's-maxage=100'),
+ ),
+ );
+
+ $this->setNextResponses($responses);
+
+ $this->request('HEAD', '/', array(), array(), true);
+
+ $this->assertEmpty($this->response->getContent());
+ $this->assertEquals(100, $this->response->getTtl());
}
public function testEsiCacheForceValidation()
@@ -1184,6 +1209,37 @@ public function testEsiCacheForceValidation()
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
}
+ public function testEsiCacheForceValidationForHeadRequests()
+ {
+ $responses = array(
+ array(
+ 'status' => 200,
+ 'body' => 'I am the master response and use expiration caching, but I embed another resource: ',
+ 'headers' => array(
+ 'Cache-Control' => 's-maxage=300',
+ 'Surrogate-Control' => 'content="ESI/1.0"',
+ ),
+ ),
+ array(
+ 'status' => 200,
+ 'body' => 'I am the embedded resource and use validation caching',
+ 'headers' => array('ETag' => 'foobar'),
+ ),
+ );
+
+ $this->setNextResponses($responses);
+
+ $this->request('HEAD', '/', array(), array(), true);
+
+ // The response has been assembled from expiration and validation based resources
+ // This can neither be cached nor revalidated, so it should be private/no cache
+ $this->assertEmpty($this->response->getContent());
+ $this->assertNull($this->response->getTtl());
+ $this->assertTrue($this->response->mustRevalidate());
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
+ $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
+ }
+
public function testEsiRecalculateContentLengthHeader()
{
$responses = array(
@@ -1192,7 +1248,6 @@ public function testEsiRecalculateContentLengthHeader()
'body' => '',
'headers' => array(
'Content-Length' => 26,
- 'Cache-Control' => 's-maxage=300',
'Surrogate-Control' => 'content="ESI/1.0"',
),
),
@@ -1210,6 +1265,37 @@ public function testEsiRecalculateContentLengthHeader()
$this->assertEquals(12, $this->response->headers->get('Content-Length'));
}
+ public function testEsiRecalculateContentLengthHeaderForHeadRequest()
+ {
+ $responses = array(
+ array(
+ 'status' => 200,
+ 'body' => '',
+ 'headers' => array(
+ 'Content-Length' => 26,
+ 'Surrogate-Control' => 'content="ESI/1.0"',
+ ),
+ ),
+ array(
+ 'status' => 200,
+ 'body' => 'Hello World!',
+ 'headers' => array(),
+ ),
+ );
+
+ $this->setNextResponses($responses);
+
+ $this->request('HEAD', '/', array(), array(), true);
+
+ // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13
+ // "The Content-Length entity-header field indicates the size of the entity-body,
+ // in decimal number of OCTETs, sent to the recipient or, in the case of the HEAD
+ // method, the size of the entity-body that would have been sent had the request
+ // been a GET."
+ $this->assertEmpty($this->response->getContent());
+ $this->assertEquals(12, $this->response->headers->get('Content-Length'));
+ }
+
public function testClientIpIsAlwaysLocalhostForForwardedRequests()
{
$this->setNextResponse();
@@ -1301,6 +1387,35 @@ public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses()
$this->assertNull($this->response->getLastModified());
}
+ public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponsesAndHeadRequest()
+ {
+ $time = \DateTime::createFromFormat('U', time());
+
+ $responses = array(
+ array(
+ 'status' => 200,
+ 'body' => '',
+ 'headers' => array(
+ 'Surrogate-Control' => 'content="ESI/1.0"',
+ 'ETag' => 'hey',
+ 'Last-Modified' => $time->format(DATE_RFC2822),
+ ),
+ ),
+ array(
+ 'status' => 200,
+ 'body' => 'Hey!',
+ 'headers' => array(),
+ ),
+ );
+
+ $this->setNextResponses($responses);
+
+ $this->request('HEAD', '/', array(), array(), true);
+ $this->assertEmpty($this->response->getContent());
+ $this->assertNull($this->response->getETag());
+ $this->assertNull($this->response->getLastModified());
+ }
+
public function testDoesNotCacheOptionsRequest()
{
$this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'get');