Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ad1f057

Browse filesBrowse files
committed
Public responses without lifetime should not remove lifetime for the resulting response
1 parent ef06f33 commit ad1f057
Copy full SHA for ad1f057

File tree

2 files changed

+49
-6
lines changed
Filter options

2 files changed

+49
-6
lines changed

‎src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php
+24-5Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,15 @@ public function add(Response $response)
8181
return;
8282
}
8383

84+
$isHeuristicallyCacheable = $response->headers->hasCacheControlDirective('public');
8485
$maxAge = $response->headers->hasCacheControlDirective('max-age') ? (int) $response->headers->getCacheControlDirective('max-age') : null;
85-
$this->storeRelativeAgeDirective('max-age', $maxAge, $age);
86+
$this->storeRelativeAgeDirective('max-age', $maxAge, $age, $isHeuristicallyCacheable);
8687
$sharedMaxAge = $response->headers->hasCacheControlDirective('s-maxage') ? (int) $response->headers->getCacheControlDirective('s-maxage') : $maxAge;
87-
$this->storeRelativeAgeDirective('s-maxage', $sharedMaxAge, $age);
88+
$this->storeRelativeAgeDirective('s-maxage', $sharedMaxAge, $age, $isHeuristicallyCacheable);
8889

8990
$expires = $response->getExpires();
9091
$expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null;
91-
$this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0);
92+
$this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0, $isHeuristicallyCacheable);
9293
}
9394

9495
/**
@@ -199,11 +200,29 @@ private function willMakeFinalResponseUncacheable(Response $response): bool
199200
* we have to subtract the age so that the value is normalized for an age of 0.
200201
*
201202
* If the value is lower than the currently stored value, we update the value, to keep a rolling
202-
* minimal value of each instruction. If the value is NULL, the directive will not be set on the final response.
203+
* minimal value of each instruction.
204+
*
205+
* If the value is NULL and the isHeuristicallyCacheable parameter is false, the directive will
206+
* not be set on the final response. In this case, not all responses had the directive set and no
207+
* value can be found that satisfies the requirements of all responses. The directive will be dropped
208+
* from the final response.
209+
*
210+
* If the isHeuristicallyCacheable parameter is true, however, the current response has been marked
211+
* as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve
212+
* as an upper bound. In this case, we can proceed and possibly keep the directive on the final response.
203213
*/
204-
private function storeRelativeAgeDirective(string $directive, ?int $value, int $age)
214+
private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable)
205215
{
206216
if (null === $value) {
217+
if ($isHeuristicallyCacheable) {
218+
/*
219+
* See https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.2
220+
* This particular response does not require maximum lifetime; heuristics might be applied.
221+
* Other responses, however, might have more stringent requirements on maximum lifetime.
222+
* So, return early here so that the final response can have the more limiting value set.
223+
*/
224+
return;
225+
}
207226
$this->ageDirectives[$directive] = false;
208227
}
209228

‎src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php
+25-1Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ public function cacheControlMergingProvider()
370370
];
371371

372372
yield 'merge max-age and s-maxage' => [
373-
['public' => true, 's-maxage' => '60', 'max-age' => null],
373+
['public' => true, 'max-age' => '60'],
374374
['public' => true, 's-maxage' => 3600],
375375
[
376376
['public' => true, 'max-age' => 60],
@@ -393,6 +393,30 @@ public function cacheControlMergingProvider()
393393
],
394394
];
395395

396+
yield 'public subresponse without lifetime does not remove lifetime for main response' => [
397+
['public' => true, 's-maxage' => '30', 'max-age' => null],
398+
['public' => true, 's-maxage' => '30'],
399+
[
400+
['public' => true],
401+
],
402+
];
403+
404+
yield 'lifetime for subresponse is kept when main response has no lifetime' => [
405+
['public' => true, 'max-age' => '30'],
406+
['public' => true],
407+
[
408+
['public' => true, 'max-age' => '30'],
409+
],
410+
];
411+
412+
yield 's-maxage on the subresponse implies public, so the result is public as well' => [
413+
['public' => true, 'max-age' => '10', 's-maxage' => null],
414+
['public' => true, 'max-age' => '10'],
415+
[
416+
['max-age' => '30', 's-maxage' => '20'],
417+
],
418+
];
419+
396420
yield 'result is private when combining private responses' => [
397421
['no-cache' => false, 'must-revalidate' => false, 'private' => true],
398422
['s-maxage' => 60, 'private' => true],

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.