diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index c831ba2ac3ff..fd04a7b23d1d 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -177,19 +177,15 @@ public function write(Request $request, Response $response) $key = $this->getCacheKey($request); $storedEnv = $this->persistRequest($request); - // write the response body to the entity store if this is the original response - if (!$response->headers->has('X-Content-Digest')) { - $digest = $this->generateContentDigest($response); + $digest = $this->generateContentDigest($response); + $response->headers->set('X-Content-Digest', $digest); - if (!$this->save($digest, $response->getContent())) { - throw new \RuntimeException('Unable to store the entity.'); - } - - $response->headers->set('X-Content-Digest', $digest); + if (!$this->save($digest, $response->getContent(), false)) { + throw new \RuntimeException('Unable to store the entity.'); + } - if (!$response->headers->has('Transfer-Encoding')) { - $response->headers->set('Content-Length', \strlen($response->getContent())); - } + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); } // read existing cache entries, remove non-varying, and add this one to the list @@ -362,15 +358,20 @@ private function load($key) /** * Save data for the given key. * - * @param string $key The store key - * @param string $data The data to store + * @param string $key The store key + * @param string $data The data to store + * @param bool $overwrite Whether existing data should be overwritten * * @return bool */ - private function save($key, $data) + private function save($key, $data, $overwrite = true) { $path = $this->getPath($key); + if (!$overwrite && file_exists($path)) { + return true; + } + if (isset($this->locks[$key])) { $fp = $this->locks[$key]; @ftruncate($fp, 0); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php index 77cb34cfa555..07c41542697e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php @@ -97,6 +97,27 @@ public function testSetsTheXContentDigestResponseHeaderBeforeStoring() $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); } + public function testDoesNotTrustXContentDigestFromUpstream() + { + $response = new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere']); + + $cacheKey = $this->store->write($this->request, $response); + $entries = $this->getStoreMetadata($cacheKey); + list(, $res) = $entries[0]; + + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $response->headers->get('X-Content-Digest')); + } + + public function testWritesResponseEvenIfXContentDigestIsPresent() + { + // Prime the store + $this->store->write($this->request, new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere'])); + + $response = $this->store->lookup($this->request); + $this->assertNotNull($response); + } + public function testFindsAStoredEntryWithLookup() { $this->storeSimpleEntry();