Skip to content

Commit e7b59be

Browse files
bug #36833 [HttpKernel] Fix that the Store would not save responses with the X-Content-Digest header present (mpdude)
This PR was squashed before being merged into the 3.4 branch. Discussion ---------- [HttpKernel] Fix that the `Store` would not save responses with the X-Content-Digest header present | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | | License | MIT | Doc PR | Responses fetched from upstream sources might have a `X-Content-Digest` header, for example if the Symfony Cache is used upstream. This currently prevents the `Store` from saving such responses. In general, the value of this header should not be trusted. As I consider this header an implementation detail of the `Store`, the fix tries to be local to that class; we should not rely on the `HttpCache` or other classes to remove untrustworthy headers for us. This fixes the issue that when using the `HttpCache` in combination with the Symfony HttpClient, responses that have also been cached upstream in an instance of `HttpCache` are not cached locally. It adds the overhead of re-computing the content digest every time the `HttpCache` successfully re-validated a response. Commits ------- d8964fb8b7 [HttpKernel] Fix that the `Store` would not save responses with the X-Content-Digest header present
2 parents 238f491 + 00b5b7c commit e7b59be

File tree

2 files changed

+36
-14
lines changed

2 files changed

+36
-14
lines changed

HttpCache/Store.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -177,19 +177,15 @@ public function write(Request $request, Response $response)
177177
$key = $this->getCacheKey($request);
178178
$storedEnv = $this->persistRequest($request);
179179

180-
// write the response body to the entity store if this is the original response
181-
if (!$response->headers->has('X-Content-Digest')) {
182-
$digest = $this->generateContentDigest($response);
180+
$digest = $this->generateContentDigest($response);
181+
$response->headers->set('X-Content-Digest', $digest);
183182

184-
if (!$this->save($digest, $response->getContent())) {
185-
throw new \RuntimeException('Unable to store the entity.');
186-
}
187-
188-
$response->headers->set('X-Content-Digest', $digest);
183+
if (!$this->save($digest, $response->getContent(), false)) {
184+
throw new \RuntimeException('Unable to store the entity.');
185+
}
189186

190-
if (!$response->headers->has('Transfer-Encoding')) {
191-
$response->headers->set('Content-Length', \strlen($response->getContent()));
192-
}
187+
if (!$response->headers->has('Transfer-Encoding')) {
188+
$response->headers->set('Content-Length', \strlen($response->getContent()));
193189
}
194190

195191
// read existing cache entries, remove non-varying, and add this one to the list
@@ -362,15 +358,20 @@ private function load($key)
362358
/**
363359
* Save data for the given key.
364360
*
365-
* @param string $key The store key
366-
* @param string $data The data to store
361+
* @param string $key The store key
362+
* @param string $data The data to store
363+
* @param bool $overwrite Whether existing data should be overwritten
367364
*
368365
* @return bool
369366
*/
370-
private function save($key, $data)
367+
private function save($key, $data, $overwrite = true)
371368
{
372369
$path = $this->getPath($key);
373370

371+
if (!$overwrite && file_exists($path)) {
372+
return true;
373+
}
374+
374375
if (isset($this->locks[$key])) {
375376
$fp = $this->locks[$key];
376377
@ftruncate($fp, 0);

Tests/HttpCache/StoreTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,27 @@ public function testSetsTheXContentDigestResponseHeaderBeforeStoring()
9797
$this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]);
9898
}
9999

100+
public function testDoesNotTrustXContentDigestFromUpstream()
101+
{
102+
$response = new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere']);
103+
104+
$cacheKey = $this->store->write($this->request, $response);
105+
$entries = $this->getStoreMetadata($cacheKey);
106+
list(, $res) = $entries[0];
107+
108+
$this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]);
109+
$this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $response->headers->get('X-Content-Digest'));
110+
}
111+
112+
public function testWritesResponseEvenIfXContentDigestIsPresent()
113+
{
114+
// Prime the store
115+
$this->store->write($this->request, new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere']));
116+
117+
$response = $this->store->lookup($this->request);
118+
$this->assertNotNull($response);
119+
}
120+
100121
public function testFindsAStoredEntryWithLookup()
101122
{
102123
$this->storeSimpleEntry();

0 commit comments

Comments
 (0)