mirror of
https://github.com/linuxserver/Heimdall.git
synced 2025-12-15 19:33:57 +09:00
Update dependencies
This commit is contained in:
@@ -24,6 +24,10 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
abstract class AbstractSurrogate implements SurrogateInterface
|
||||
{
|
||||
protected $contentTypes;
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 6.3
|
||||
*/
|
||||
protected $phpEscapeMap = [
|
||||
['<?', '<%', '<s', '<S'],
|
||||
['<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'],
|
||||
@@ -40,18 +44,13 @@ abstract class AbstractSurrogate implements SurrogateInterface
|
||||
|
||||
/**
|
||||
* Returns a new cache strategy instance.
|
||||
*
|
||||
* @return ResponseCacheStrategyInterface
|
||||
*/
|
||||
public function createCacheStrategy()
|
||||
public function createCacheStrategy(): ResponseCacheStrategyInterface
|
||||
{
|
||||
return new ResponseCacheStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasSurrogateCapability(Request $request)
|
||||
public function hasSurrogateCapability(Request $request): bool
|
||||
{
|
||||
if (null === $value = $request->headers->get('Surrogate-Capability')) {
|
||||
return false;
|
||||
@@ -61,7 +60,7 @@ abstract class AbstractSurrogate implements SurrogateInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function addSurrogateCapability(Request $request)
|
||||
{
|
||||
@@ -71,10 +70,7 @@ abstract class AbstractSurrogate implements SurrogateInterface
|
||||
$request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function needsParsing(Response $response)
|
||||
public function needsParsing(Response $response): bool
|
||||
{
|
||||
if (!$control = $response->headers->get('Surrogate-Control')) {
|
||||
return false;
|
||||
@@ -85,10 +81,7 @@ abstract class AbstractSurrogate implements SurrogateInterface
|
||||
return (bool) preg_match($pattern, $control);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors)
|
||||
public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors): string
|
||||
{
|
||||
$subRequest = Request::create($uri, Request::METHOD_GET, [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all());
|
||||
|
||||
@@ -115,6 +108,8 @@ abstract class AbstractSurrogate implements SurrogateInterface
|
||||
|
||||
/**
|
||||
* Remove the Surrogate from the Surrogate-Control header.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function removeFromControl(Response $response)
|
||||
{
|
||||
@@ -133,4 +128,15 @@ abstract class AbstractSurrogate implements SurrogateInterface
|
||||
$response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value));
|
||||
}
|
||||
}
|
||||
|
||||
protected static function generateBodyEvalBoundary(): string
|
||||
{
|
||||
static $cookie;
|
||||
$cookie = hash('xxh128', $cookie ?? $cookie = random_bytes(16), true);
|
||||
$boundary = base64_encode($cookie);
|
||||
|
||||
\assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === \strlen($boundary));
|
||||
|
||||
return $boundary;
|
||||
}
|
||||
}
|
||||
|
||||
28
vendor/symfony/http-kernel/HttpCache/Esi.php
vendored
28
vendor/symfony/http-kernel/HttpCache/Esi.php
vendored
@@ -27,13 +27,13 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*/
|
||||
class Esi extends AbstractSurrogate
|
||||
{
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return 'esi';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function addSurrogateControl(Response $response)
|
||||
{
|
||||
@@ -42,10 +42,7 @@ class Esi extends AbstractSurrogate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = '')
|
||||
public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string
|
||||
{
|
||||
$html = sprintf('<esi:include src="%s"%s%s />',
|
||||
$uri,
|
||||
@@ -60,10 +57,7 @@ class Esi extends AbstractSurrogate
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(Request $request, Response $response)
|
||||
public function process(Request $request, Response $response): Response
|
||||
{
|
||||
$type = $response->headers->get('Content-Type');
|
||||
if (empty($type)) {
|
||||
@@ -80,8 +74,8 @@ class Esi extends AbstractSurrogate
|
||||
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#s', '', $content);
|
||||
$content = preg_replace('#<esi\:comment[^>]+>#s', '', $content);
|
||||
|
||||
$boundary = self::generateBodyEvalBoundary();
|
||||
$chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
|
||||
$chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
|
||||
|
||||
$i = 1;
|
||||
while (isset($chunks[$i])) {
|
||||
@@ -95,16 +89,10 @@ class Esi extends AbstractSurrogate
|
||||
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
|
||||
}
|
||||
|
||||
$chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, %s, %s) ?>'."\n",
|
||||
var_export($options['src'], true),
|
||||
var_export($options['alt'] ?? '', true),
|
||||
isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false'
|
||||
);
|
||||
++$i;
|
||||
$chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
|
||||
++$i;
|
||||
$chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n";
|
||||
$i += 2;
|
||||
}
|
||||
$content = implode('', $chunks);
|
||||
$content = $boundary.implode('', $chunks).$boundary;
|
||||
|
||||
$response->setContent($content);
|
||||
$response->headers->set('X-Body-Eval', 'ESI');
|
||||
|
||||
159
vendor/symfony/http-kernel/HttpCache/HttpCache.php
vendored
159
vendor/symfony/http-kernel/HttpCache/HttpCache.php
vendored
@@ -29,13 +29,15 @@ use Symfony\Component\HttpKernel\TerminableInterface;
|
||||
*/
|
||||
class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
{
|
||||
private $kernel;
|
||||
private $store;
|
||||
private $request;
|
||||
private $surrogate;
|
||||
private $surrogateCacheStrategy;
|
||||
private $options = [];
|
||||
private $traces = [];
|
||||
public const BODY_EVAL_BOUNDARY_LENGTH = 24;
|
||||
|
||||
private HttpKernelInterface $kernel;
|
||||
private StoreInterface $store;
|
||||
private Request $request;
|
||||
private ?SurrogateInterface $surrogate;
|
||||
private ?ResponseCacheStrategyInterface $surrogateCacheStrategy = null;
|
||||
private array $options = [];
|
||||
private array $traces = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -60,6 +62,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
* on responses that don't explicitly state whether the response is
|
||||
* public or private via a Cache-Control directive. (default: Authorization and Cookie)
|
||||
*
|
||||
* * skip_response_headers Set of response headers that are never cached even if a response is cacheable (public).
|
||||
* (default: Set-Cookie)
|
||||
*
|
||||
* * allow_reload Specifies whether the client can force a cache reload by including a
|
||||
* Cache-Control "no-cache" directive in the request. Set it to ``true``
|
||||
* for compliance with RFC 2616. (default: false)
|
||||
@@ -78,26 +83,33 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
* the cache can serve a stale response when an error is encountered (default: 60).
|
||||
* This setting is overridden by the stale-if-error HTTP Cache-Control extension
|
||||
* (see RFC 5861).
|
||||
*
|
||||
* * terminate_on_cache_hit Specifies if the kernel.terminate event should be dispatched even when the cache
|
||||
* was hit (default: true).
|
||||
* Unless your application needs to process events on cache hits, it is recommended
|
||||
* to set this to false to avoid having to bootstrap the Symfony framework on a cache hit.
|
||||
*/
|
||||
public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = [])
|
||||
public function __construct(HttpKernelInterface $kernel, StoreInterface $store, ?SurrogateInterface $surrogate = null, array $options = [])
|
||||
{
|
||||
$this->store = $store;
|
||||
$this->kernel = $kernel;
|
||||
$this->surrogate = $surrogate;
|
||||
|
||||
// needed in case there is a fatal error because the backend is too slow to respond
|
||||
register_shutdown_function([$this->store, 'cleanup']);
|
||||
register_shutdown_function($this->store->cleanup(...));
|
||||
|
||||
$this->options = array_merge([
|
||||
'debug' => false,
|
||||
'default_ttl' => 0,
|
||||
'private_headers' => ['Authorization', 'Cookie'],
|
||||
'skip_response_headers' => ['Set-Cookie'],
|
||||
'allow_reload' => false,
|
||||
'allow_revalidate' => false,
|
||||
'stale_while_revalidate' => 2,
|
||||
'stale_if_error' => 60,
|
||||
'trace_level' => 'none',
|
||||
'trace_header' => 'X-Symfony-Cache',
|
||||
'terminate_on_cache_hit' => true,
|
||||
], $options);
|
||||
|
||||
if (!isset($options['trace_level'])) {
|
||||
@@ -107,25 +119,21 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
|
||||
/**
|
||||
* Gets the current store.
|
||||
*
|
||||
* @return StoreInterface
|
||||
*/
|
||||
public function getStore()
|
||||
public function getStore(): StoreInterface
|
||||
{
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of events that took place during processing of the last request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTraces()
|
||||
public function getTraces(): array
|
||||
{
|
||||
return $this->traces;
|
||||
}
|
||||
|
||||
private function addTraces(Response $response)
|
||||
private function addTraces(Response $response): void
|
||||
{
|
||||
$traceString = null;
|
||||
|
||||
@@ -144,10 +152,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
|
||||
/**
|
||||
* Returns a log message for the events of the last request processing.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLog()
|
||||
public function getLog(): string
|
||||
{
|
||||
$log = [];
|
||||
foreach ($this->traces as $request => $traces) {
|
||||
@@ -159,20 +165,16 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
|
||||
/**
|
||||
* Gets the Request instance associated with the main request.
|
||||
*
|
||||
* @return Request
|
||||
*/
|
||||
public function getRequest()
|
||||
public function getRequest(): Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Kernel instance.
|
||||
*
|
||||
* @return HttpKernelInterface
|
||||
*/
|
||||
public function getKernel()
|
||||
public function getKernel(): HttpKernelInterface
|
||||
{
|
||||
return $this->kernel;
|
||||
}
|
||||
@@ -180,19 +182,14 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
/**
|
||||
* Gets the Surrogate instance.
|
||||
*
|
||||
* @return SurrogateInterface
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function getSurrogate()
|
||||
public function getSurrogate(): SurrogateInterface
|
||||
{
|
||||
return $this->surrogate;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true)
|
||||
public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response
|
||||
{
|
||||
// FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism
|
||||
if (HttpKernelInterface::MAIN_REQUEST === $type) {
|
||||
@@ -246,10 +243,19 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function terminate(Request $request, Response $response)
|
||||
{
|
||||
// Do not call any listeners in case of a cache hit.
|
||||
// This ensures identical behavior as if you had a separate
|
||||
// reverse caching proxy such as Varnish and the like.
|
||||
if ($this->options['terminate_on_cache_hit']) {
|
||||
trigger_deprecation('symfony/http-kernel', '6.2', 'Setting "terminate_on_cache_hit" to "true" is deprecated and will be changed to "false" in Symfony 7.0.');
|
||||
} elseif (\in_array('fresh', $this->traces[$this->getTraceKey($request)] ?? [], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->getKernel() instanceof TerminableInterface) {
|
||||
$this->getKernel()->terminate($request, $response);
|
||||
}
|
||||
@@ -259,10 +265,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
* Forwards the Request to the backend without storing the Response in the cache.
|
||||
*
|
||||
* @param bool $catch Whether to process exceptions
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function pass(Request $request, bool $catch = false)
|
||||
protected function pass(Request $request, bool $catch = false): Response
|
||||
{
|
||||
$this->record($request, 'pass');
|
||||
|
||||
@@ -274,13 +278,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
*
|
||||
* @param bool $catch Whether to process exceptions
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @see RFC2616 13.10
|
||||
*/
|
||||
protected function invalidate(Request $request, bool $catch = false)
|
||||
protected function invalidate(Request $request, bool $catch = false): Response
|
||||
{
|
||||
$response = $this->pass($request, $catch);
|
||||
|
||||
@@ -322,11 +324,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
*
|
||||
* @param bool $catch Whether to process exceptions
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function lookup(Request $request, bool $catch = false)
|
||||
protected function lookup(Request $request, bool $catch = false): Response
|
||||
{
|
||||
try {
|
||||
$entry = $this->store->lookup($request);
|
||||
@@ -370,10 +370,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
* GET request with the backend.
|
||||
*
|
||||
* @param bool $catch Whether to process exceptions
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function validate(Request $request, Response $entry, bool $catch = false)
|
||||
protected function validate(Request $request, Response $entry, bool $catch = false): Response
|
||||
{
|
||||
$subRequest = clone $request;
|
||||
|
||||
@@ -433,10 +431,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
* stores it in the cache if is cacheable.
|
||||
*
|
||||
* @param bool $catch Whether to process exceptions
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function fetch(Request $request, bool $catch = false)
|
||||
protected function fetch(Request $request, bool $catch = false): Response
|
||||
{
|
||||
$subRequest = clone $request;
|
||||
|
||||
@@ -469,11 +465,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function forward(Request $request, bool $catch = false, Response $entry = null)
|
||||
protected function forward(Request $request, bool $catch = false, ?Response $entry = null)
|
||||
{
|
||||
if ($this->surrogate) {
|
||||
$this->surrogate->addSurrogateCapability($request);
|
||||
}
|
||||
$this->surrogate?->addSurrogateCapability($request);
|
||||
|
||||
// always a "master" request (as the real master request can be in cache)
|
||||
$response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST, $catch);
|
||||
@@ -523,7 +517,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
Anyway, a client that received a message without a "Date" header MUST add it.
|
||||
*/
|
||||
if (!$response->headers->has('Date')) {
|
||||
$response->setDate(\DateTime::createFromFormat('U', time()));
|
||||
$response->setDate(\DateTimeImmutable::createFromFormat('U', time()));
|
||||
}
|
||||
|
||||
$this->processResponseBody($request, $response);
|
||||
@@ -539,10 +533,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
|
||||
/**
|
||||
* Checks whether the cache entry is "fresh enough" to satisfy the Request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFreshEnough(Request $request, Response $entry)
|
||||
protected function isFreshEnough(Request $request, Response $entry): bool
|
||||
{
|
||||
if (!$entry->isFresh()) {
|
||||
return $this->lock($request, $entry);
|
||||
@@ -560,7 +552,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
*
|
||||
* @return bool true if the cache entry can be returned even if it is staled, false otherwise
|
||||
*/
|
||||
protected function lock(Request $request, Response $entry)
|
||||
protected function lock(Request $request, Response $entry): bool
|
||||
{
|
||||
// try to acquire a lock to call the backend
|
||||
$lock = $this->store->lock($request);
|
||||
@@ -603,13 +595,24 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
/**
|
||||
* Writes the Response to the cache.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function store(Request $request, Response $response)
|
||||
{
|
||||
try {
|
||||
$this->store->write($request, $response);
|
||||
$restoreHeaders = [];
|
||||
foreach ($this->options['skip_response_headers'] as $header) {
|
||||
if (!$response->headers->has($header)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$restoreHeaders[$header] = $response->headers->all($header);
|
||||
$response->headers->remove($header);
|
||||
}
|
||||
|
||||
$this->store->write($request, $response);
|
||||
$this->record($request, 'store');
|
||||
|
||||
$response->headers->set('Age', $response->getAge());
|
||||
@@ -619,6 +622,10 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
if ($this->options['debug']) {
|
||||
throw $e;
|
||||
}
|
||||
} finally {
|
||||
foreach ($restoreHeaders as $header => $values) {
|
||||
$response->headers->set($header, $values);
|
||||
}
|
||||
}
|
||||
|
||||
// now that the response is cached, release the lock
|
||||
@@ -628,15 +635,25 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
/**
|
||||
* Restores the Response body.
|
||||
*/
|
||||
private function restoreResponseBody(Request $request, Response $response)
|
||||
private function restoreResponseBody(Request $request, Response $response): void
|
||||
{
|
||||
if ($response->headers->has('X-Body-Eval')) {
|
||||
\assert(self::BODY_EVAL_BOUNDARY_LENGTH === 24);
|
||||
|
||||
ob_start();
|
||||
|
||||
if ($response->headers->has('X-Body-File')) {
|
||||
include $response->headers->get('X-Body-File');
|
||||
} else {
|
||||
eval('; ?>'.$response->getContent().'<?php ;');
|
||||
$content = $response->getContent();
|
||||
$boundary = substr($content, 0, 24);
|
||||
$j = strpos($content, $boundary, 24);
|
||||
echo substr($content, 24, $j - 24);
|
||||
$i = $j + 24;
|
||||
|
||||
while (false !== $j = strpos($content, $boundary, $i)) {
|
||||
[$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4);
|
||||
$i = $j + 24;
|
||||
|
||||
echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors);
|
||||
echo $part;
|
||||
}
|
||||
|
||||
$response->setContent(ob_get_clean());
|
||||
@@ -657,9 +674,12 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
$response->headers->remove('X-Body-File');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function processResponseBody(Request $request, Response $response)
|
||||
{
|
||||
if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) {
|
||||
if ($this->surrogate?->needsParsing($response)) {
|
||||
$this->surrogate->process($request, $response);
|
||||
}
|
||||
}
|
||||
@@ -688,7 +708,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
/**
|
||||
* Records that an event took place.
|
||||
*/
|
||||
private function record(Request $request, string $event)
|
||||
private function record(Request $request, string $event): void
|
||||
{
|
||||
$this->traces[$this->getTraceKey($request)][] = $event;
|
||||
}
|
||||
@@ -713,12 +733,13 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
private function mayServeStaleWhileRevalidate(Response $entry): bool
|
||||
{
|
||||
$timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate');
|
||||
$timeout ??= $this->options['stale_while_revalidate'];
|
||||
|
||||
if (null === $timeout) {
|
||||
$timeout = $this->options['stale_while_revalidate'];
|
||||
}
|
||||
$age = $entry->getAge();
|
||||
$maxAge = $entry->getMaxAge() ?? 0;
|
||||
$ttl = $maxAge - $age;
|
||||
|
||||
return abs($entry->getTtl() ?? 0) < $timeout;
|
||||
return abs($ttl) < $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,10 +34,11 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
*/
|
||||
private const INHERIT_DIRECTIVES = ['public', 'immutable'];
|
||||
|
||||
private $embeddedResponses = 0;
|
||||
private $isNotCacheableResponseEmbedded = false;
|
||||
private $age = 0;
|
||||
private $flagDirectives = [
|
||||
private int $embeddedResponses = 0;
|
||||
private bool $isNotCacheableResponseEmbedded = false;
|
||||
private int $age = 0;
|
||||
private \DateTimeInterface|null|false $lastModified = null;
|
||||
private array $flagDirectives = [
|
||||
'no-cache' => null,
|
||||
'no-store' => null,
|
||||
'no-transform' => null,
|
||||
@@ -47,14 +48,14 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
'private' => null,
|
||||
'immutable' => null,
|
||||
];
|
||||
private $ageDirectives = [
|
||||
private array $ageDirectives = [
|
||||
'max-age' => null,
|
||||
's-maxage' => null,
|
||||
'expires' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function add(Response $response)
|
||||
{
|
||||
@@ -90,10 +91,15 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
$expires = $response->getExpires();
|
||||
$expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null;
|
||||
$this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0, $isHeuristicallyCacheable);
|
||||
|
||||
if (false !== $this->lastModified) {
|
||||
$lastModified = $response->getLastModified();
|
||||
$this->lastModified = $lastModified ? max($this->lastModified, $lastModified) : false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function update(Response $response)
|
||||
{
|
||||
@@ -102,17 +108,16 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove validation related headers of the master response,
|
||||
// because some of the response content comes from at least
|
||||
// one embedded response (which likely has a different caching strategy).
|
||||
// Remove Etag since it cannot be merged from embedded responses.
|
||||
$response->setEtag(null);
|
||||
$response->setLastModified(null);
|
||||
|
||||
$this->add($response);
|
||||
|
||||
$response->headers->set('Age', $this->age);
|
||||
|
||||
if ($this->isNotCacheableResponseEmbedded) {
|
||||
$response->setLastModified(null);
|
||||
|
||||
if ($this->flagDirectives['no-store']) {
|
||||
$response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
} else {
|
||||
@@ -122,6 +127,8 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$response->setLastModified($this->lastModified ?: null);
|
||||
|
||||
$flags = array_filter($this->flagDirectives);
|
||||
|
||||
if (isset($flags['must-revalidate'])) {
|
||||
@@ -147,7 +154,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
|
||||
if (is_numeric($this->ageDirectives['expires'])) {
|
||||
$date = clone $response->getDate();
|
||||
$date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds');
|
||||
$date = $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds');
|
||||
$response->setExpires($date);
|
||||
}
|
||||
}
|
||||
@@ -162,17 +169,14 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
// RFC2616: A response received with a status code of 200, 203, 300, 301 or 410
|
||||
// MAY be stored by a cache […] unless a cache-control directive prohibits caching.
|
||||
if ($response->headers->hasCacheControlDirective('no-cache')
|
||||
|| $response->headers->getCacheControlDirective('no-store')
|
||||
|| $response->headers->hasCacheControlDirective('no-store')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Last-Modified and Etag headers cannot be merged, they render the response uncacheable
|
||||
// Etag headers cannot be merged, they render the response uncacheable
|
||||
// by default (except if the response also has max-age etc.).
|
||||
if (\in_array($response->getStatusCode(), [200, 203, 300, 301, 410])
|
||||
&& null === $response->getLastModified()
|
||||
&& null === $response->getEtag()
|
||||
) {
|
||||
if (null === $response->getEtag() && \in_array($response->getStatusCode(), [200, 203, 300, 301, 410])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -211,7 +215,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
* as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve
|
||||
* as an upper bound. In this case, we can proceed and possibly keep the directive on the final response.
|
||||
*/
|
||||
private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable)
|
||||
private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable): void
|
||||
{
|
||||
if (null === $value) {
|
||||
if ($isHeuristicallyCacheable) {
|
||||
|
||||
@@ -27,11 +27,15 @@ interface ResponseCacheStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Adds a Response.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add(Response $response);
|
||||
|
||||
/**
|
||||
* Updates the Response HTTP headers based on the embedded Responses.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update(Response $response);
|
||||
}
|
||||
|
||||
30
vendor/symfony/http-kernel/HttpCache/Ssi.php
vendored
30
vendor/symfony/http-kernel/HttpCache/Ssi.php
vendored
@@ -21,16 +21,13 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*/
|
||||
class Ssi extends AbstractSurrogate
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return 'ssi';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function addSurrogateControl(Response $response)
|
||||
{
|
||||
@@ -39,18 +36,12 @@ class Ssi extends AbstractSurrogate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = '')
|
||||
public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string
|
||||
{
|
||||
return sprintf('<!--#include virtual="%s" -->', $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(Request $request, Response $response)
|
||||
public function process(Request $request, Response $response): Response
|
||||
{
|
||||
$type = $response->headers->get('Content-Type');
|
||||
if (empty($type)) {
|
||||
@@ -64,9 +55,8 @@ class Ssi extends AbstractSurrogate
|
||||
|
||||
// we don't use a proper XML parser here as we can have SSI tags in a plain text response
|
||||
$content = $response->getContent();
|
||||
|
||||
$boundary = self::generateBodyEvalBoundary();
|
||||
$chunks = preg_split('#<!--\#include\s+(.*?)\s*-->#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
|
||||
$chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
|
||||
|
||||
$i = 1;
|
||||
while (isset($chunks[$i])) {
|
||||
@@ -80,14 +70,10 @@ class Ssi extends AbstractSurrogate
|
||||
throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.');
|
||||
}
|
||||
|
||||
$chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, \'\', false) ?>'."\n",
|
||||
var_export($options['virtual'], true)
|
||||
);
|
||||
++$i;
|
||||
$chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
|
||||
++$i;
|
||||
$chunks[$i] = $boundary.$options['virtual']."\n\n\n";
|
||||
$i += 2;
|
||||
}
|
||||
$content = implode('', $chunks);
|
||||
$content = $boundary.implode('', $chunks).$boundary;
|
||||
|
||||
$response->setContent($content);
|
||||
$response->headers->set('X-Body-Eval', 'SSI');
|
||||
|
||||
70
vendor/symfony/http-kernel/HttpCache/Store.php
vendored
70
vendor/symfony/http-kernel/HttpCache/Store.php
vendored
@@ -26,24 +26,37 @@ class Store implements StoreInterface
|
||||
{
|
||||
protected $root;
|
||||
/** @var \SplObjectStorage<Request, string> */
|
||||
private $keyCache;
|
||||
private \SplObjectStorage $keyCache;
|
||||
/** @var array<string, resource> */
|
||||
private $locks = [];
|
||||
private array $locks = [];
|
||||
private array $options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* The available options are:
|
||||
*
|
||||
* * private_headers Set of response headers that should not be stored
|
||||
* when a response is cached. (default: Set-Cookie)
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function __construct(string $root)
|
||||
public function __construct(string $root, array $options = [])
|
||||
{
|
||||
$this->root = $root;
|
||||
if (!is_dir($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) {
|
||||
throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root));
|
||||
}
|
||||
$this->keyCache = new \SplObjectStorage();
|
||||
$this->options = array_merge([
|
||||
'private_headers' => ['Set-Cookie'],
|
||||
], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanups storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cleanup()
|
||||
{
|
||||
@@ -61,7 +74,7 @@ class Store implements StoreInterface
|
||||
*
|
||||
* @return bool|string true if the lock is acquired, the path to the current lock otherwise
|
||||
*/
|
||||
public function lock(Request $request)
|
||||
public function lock(Request $request): bool|string
|
||||
{
|
||||
$key = $this->getCacheKey($request);
|
||||
|
||||
@@ -88,7 +101,7 @@ class Store implements StoreInterface
|
||||
*
|
||||
* @return bool False if the lock file does not exist or cannot be unlocked, true otherwise
|
||||
*/
|
||||
public function unlock(Request $request)
|
||||
public function unlock(Request $request): bool
|
||||
{
|
||||
$key = $this->getCacheKey($request);
|
||||
|
||||
@@ -103,7 +116,7 @@ class Store implements StoreInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isLocked(Request $request)
|
||||
public function isLocked(Request $request): bool
|
||||
{
|
||||
$key = $this->getCacheKey($request);
|
||||
|
||||
@@ -125,10 +138,8 @@ class Store implements StoreInterface
|
||||
|
||||
/**
|
||||
* Locates a cached Response for the Request provided.
|
||||
*
|
||||
* @return Response|null
|
||||
*/
|
||||
public function lookup(Request $request)
|
||||
public function lookup(Request $request): ?Response
|
||||
{
|
||||
$key = $this->getCacheKey($request);
|
||||
|
||||
@@ -167,11 +178,9 @@ class Store implements StoreInterface
|
||||
* Existing entries are read and any that match the response are removed. This
|
||||
* method calls write with the new list of cache entries.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function write(Request $request, Response $response)
|
||||
public function write(Request $request, Response $response): string
|
||||
{
|
||||
$key = $this->getCacheKey($request);
|
||||
$storedEnv = $this->persistRequest($request);
|
||||
@@ -186,7 +195,7 @@ class Store implements StoreInterface
|
||||
if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) {
|
||||
throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.');
|
||||
}
|
||||
// Everything seems ok, omit writing content to disk
|
||||
// Everything seems ok, omit writing content to disk
|
||||
} else {
|
||||
$digest = $this->generateContentDigest($response);
|
||||
$response->headers->set('X-Content-Digest', $digest);
|
||||
@@ -216,6 +225,10 @@ class Store implements StoreInterface
|
||||
$headers = $this->persistResponse($response);
|
||||
unset($headers['age']);
|
||||
|
||||
foreach ($this->options['private_headers'] as $h) {
|
||||
unset($headers[strtolower($h)]);
|
||||
}
|
||||
|
||||
array_unshift($entries, [$storedEnv, $headers]);
|
||||
|
||||
if (!$this->save($key, serialize($entries))) {
|
||||
@@ -227,17 +240,17 @@ class Store implements StoreInterface
|
||||
|
||||
/**
|
||||
* Returns content digest for $response.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateContentDigest(Response $response)
|
||||
protected function generateContentDigest(Response $response): string
|
||||
{
|
||||
return 'en'.hash('sha256', $response->getContent());
|
||||
return 'en'.hash('xxh128', $response->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all cache entries that match the request.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function invalidate(Request $request)
|
||||
@@ -309,7 +322,7 @@ class Store implements StoreInterface
|
||||
*
|
||||
* @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise
|
||||
*/
|
||||
public function purge(string $url)
|
||||
public function purge(string $url): bool
|
||||
{
|
||||
$http = preg_replace('#^https:#', 'http:', $url);
|
||||
$https = preg_replace('#^http:#', 'https:', $url);
|
||||
@@ -404,6 +417,9 @@ class Store implements StoreInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath(string $key)
|
||||
{
|
||||
return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6);
|
||||
@@ -418,10 +434,8 @@ class Store implements StoreInterface
|
||||
* If the same URI can have more than one representation, based on some
|
||||
* headers, use a Vary header to indicate them, and each representation will
|
||||
* be stored independently under the same cache key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateCacheKey(Request $request)
|
||||
protected function generateCacheKey(Request $request): string
|
||||
{
|
||||
return 'md'.hash('sha256', $request->getUri());
|
||||
}
|
||||
@@ -460,15 +474,25 @@ class Store implements StoreInterface
|
||||
/**
|
||||
* Restores a Response from the HTTP headers and body.
|
||||
*/
|
||||
private function restoreResponse(array $headers, string $path = null): Response
|
||||
private function restoreResponse(array $headers, ?string $path = null): ?Response
|
||||
{
|
||||
$status = $headers['X-Status'][0];
|
||||
unset($headers['X-Status']);
|
||||
$content = null;
|
||||
|
||||
if (null !== $path) {
|
||||
$headers['X-Body-File'] = [$path];
|
||||
unset($headers['x-body-file']);
|
||||
|
||||
if ($headers['X-Body-Eval'] ?? $headers['x-body-eval'] ?? false) {
|
||||
$content = file_get_contents($path);
|
||||
\assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === 24);
|
||||
if (48 > \strlen($content) || substr($content, -24) !== substr($content, 0, 24)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Response($path, $status, $headers);
|
||||
return new Response($content, $status, $headers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,8 @@ interface StoreInterface
|
||||
{
|
||||
/**
|
||||
* Locates a cached Response for the Request provided.
|
||||
*
|
||||
* @return Response|null
|
||||
*/
|
||||
public function lookup(Request $request);
|
||||
public function lookup(Request $request): ?Response;
|
||||
|
||||
/**
|
||||
* Writes a cache entry to the store for the given Request and Response.
|
||||
@@ -39,10 +37,12 @@ interface StoreInterface
|
||||
*
|
||||
* @return string The key under which the response is stored
|
||||
*/
|
||||
public function write(Request $request, Response $response);
|
||||
public function write(Request $request, Response $response): string;
|
||||
|
||||
/**
|
||||
* Invalidates all cache entries that match the request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function invalidate(Request $request);
|
||||
|
||||
@@ -51,31 +51,33 @@ interface StoreInterface
|
||||
*
|
||||
* @return bool|string true if the lock is acquired, the path to the current lock otherwise
|
||||
*/
|
||||
public function lock(Request $request);
|
||||
public function lock(Request $request): bool|string;
|
||||
|
||||
/**
|
||||
* Releases the lock for the given Request.
|
||||
*
|
||||
* @return bool False if the lock file does not exist or cannot be unlocked, true otherwise
|
||||
*/
|
||||
public function unlock(Request $request);
|
||||
public function unlock(Request $request): bool;
|
||||
|
||||
/**
|
||||
* Returns whether or not a lock exists.
|
||||
*
|
||||
* @return bool true if lock exists, false otherwise
|
||||
*/
|
||||
public function isLocked(Request $request);
|
||||
public function isLocked(Request $request): bool;
|
||||
|
||||
/**
|
||||
* Purges data for the given URL.
|
||||
*
|
||||
* @return bool true if the URL exists and has been purged, false otherwise
|
||||
*/
|
||||
public function purge(string $url);
|
||||
public function purge(string $url): bool;
|
||||
|
||||
/**
|
||||
* Cleanups storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cleanup();
|
||||
}
|
||||
|
||||
@@ -18,27 +18,23 @@ interface SurrogateInterface
|
||||
{
|
||||
/**
|
||||
* Returns surrogate name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Returns a new cache strategy instance.
|
||||
*
|
||||
* @return ResponseCacheStrategyInterface
|
||||
*/
|
||||
public function createCacheStrategy();
|
||||
public function createCacheStrategy(): ResponseCacheStrategyInterface;
|
||||
|
||||
/**
|
||||
* Checks that at least one surrogate has Surrogate capability.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSurrogateCapability(Request $request);
|
||||
public function hasSurrogateCapability(Request $request): bool;
|
||||
|
||||
/**
|
||||
* Adds Surrogate-capability to the given Request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addSurrogateCapability(Request $request);
|
||||
|
||||
@@ -46,42 +42,36 @@ interface SurrogateInterface
|
||||
* Adds HTTP headers to specify that the Response needs to be parsed for Surrogate.
|
||||
*
|
||||
* This method only adds an Surrogate HTTP header if the Response has some Surrogate tags.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addSurrogateControl(Response $response);
|
||||
|
||||
/**
|
||||
* Checks that the Response needs to be parsed for Surrogate tags.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function needsParsing(Response $response);
|
||||
public function needsParsing(Response $response): bool;
|
||||
|
||||
/**
|
||||
* Renders a Surrogate tag.
|
||||
*
|
||||
* @param string $alt An alternate URI
|
||||
* @param string $comment A comment to add as an esi:include tag
|
||||
*
|
||||
* @return string
|
||||
* @param string|null $alt An alternate URI
|
||||
* @param string $comment A comment to add as an esi:include tag
|
||||
*/
|
||||
public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = '');
|
||||
public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = true, string $comment = ''): string;
|
||||
|
||||
/**
|
||||
* Replaces a Response Surrogate tags with the included resource content.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, Response $response);
|
||||
public function process(Request $request, Response $response): Response;
|
||||
|
||||
/**
|
||||
* Handles a Surrogate from the cache.
|
||||
*
|
||||
* @param string $alt An alternative URI
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors);
|
||||
public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors): string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user