From 3c4d29901af1419ca39bf05e7d7008c8fe0b5ae6 Mon Sep 17 00:00:00 2001 From: Romain Canon Date: Wed, 31 Aug 2022 00:56:30 +0200 Subject: [PATCH] fix: prevent illegal characters in PSR-16 cache keys @see https://www.php-fig.org/psr/psr-16/#12-definitions --- ...rsionedCache.php => KeySanitizerCache.php} | 41 +++++++++++-------- src/Library/Container.php | 6 +-- ...acheTest.php => KeySanitizerCacheTest.php} | 10 ++--- 3 files changed, 32 insertions(+), 25 deletions(-) rename src/Cache/{VersionedCache.php => KeySanitizerCache.php} (53%) rename tests/Unit/Cache/{VersionedCacheTest.php => KeySanitizerCacheTest.php} (89%) diff --git a/src/Cache/VersionedCache.php b/src/Cache/KeySanitizerCache.php similarity index 53% rename from src/Cache/VersionedCache.php rename to src/Cache/KeySanitizerCache.php index 0e55b70..e155f00 100644 --- a/src/Cache/VersionedCache.php +++ b/src/Cache/KeySanitizerCache.php @@ -4,22 +4,28 @@ declare(strict_types=1); namespace CuyZ\Valinor\Cache; +use Closure; use CuyZ\Valinor\Utility\Package; use Psr\SimpleCache\CacheInterface; use Traversable; +use function sha1; + /** * @internal * * @template EntryType * @implements CacheInterface */ -final class VersionedCache implements CacheInterface +final class KeySanitizerCache implements CacheInterface { - /** @var CacheInterface */ + private static string $version; + + /** @var CacheInterface */ private CacheInterface $delegate; - private string $version; + /** @var Closure(string): string */ + private Closure $sanitize; /** * @param CacheInterface $delegate @@ -27,23 +33,29 @@ final class VersionedCache implements CacheInterface public function __construct(CacheInterface $delegate) { $this->delegate = $delegate; - /** @infection-ignore-all */ - $this->version = PHP_VERSION . '/' . Package::version(); + + // Two things: + // 1. We append the current version of the package to the cache key in + // order to avoid collisions between entries from different versions + // of the library. + // 2. The key is sha1'd so that it does not contain illegal characters. + // @see https://www.php-fig.org/psr/psr-16/#12-definitions + $this->sanitize = static fn (string $key) => sha1("$key." . self::$version ??= PHP_VERSION . '/' . Package::version()); } public function get($key, $default = null) { - return $this->delegate->get($this->key($key), $default); + return $this->delegate->get(($this->sanitize)($key), $default); } public function set($key, $value, $ttl = null): bool { - return $this->delegate->set($this->key($key), $value, $ttl); + return $this->delegate->set(($this->sanitize)($key), $value, $ttl); } public function delete($key): bool { - return $this->delegate->delete($this->key($key)); + return $this->delegate->delete(($this->sanitize)($key)); } public function clear(): bool @@ -53,7 +65,7 @@ final class VersionedCache implements CacheInterface public function has($key): bool { - return $this->delegate->has($this->key($key)); + return $this->delegate->has(($this->sanitize)($key)); } /** @@ -62,7 +74,7 @@ final class VersionedCache implements CacheInterface public function getMultiple($keys, $default = null): Traversable { foreach ($keys as $key) { - yield $key => $this->delegate->get($this->key($key), $default); + yield $key => $this->delegate->get(($this->sanitize)($key), $default); } } @@ -71,7 +83,7 @@ final class VersionedCache implements CacheInterface $versionedValues = []; foreach ($values as $key => $value) { - $versionedValues[$this->key($key)] = $value; + $versionedValues[($this->sanitize)($key)] = $value; } return $this->delegate->setMultiple($versionedValues, $ttl); @@ -82,14 +94,9 @@ final class VersionedCache implements CacheInterface $transformedKeys = []; foreach ($keys as $key) { - $transformedKeys[] = $this->key($key); + $transformedKeys[] = ($this->sanitize)($key); } return $this->delegate->deleteMultiple($transformedKeys); } - - private function key(string $key): string - { - return "$key.$this->version"; - } } diff --git a/src/Library/Container.php b/src/Library/Container.php index 02d1d28..daa3aba 100644 --- a/src/Library/Container.php +++ b/src/Library/Container.php @@ -6,7 +6,7 @@ namespace CuyZ\Valinor\Library; use CuyZ\Valinor\Cache\ChainCache; use CuyZ\Valinor\Cache\RuntimeCache; -use CuyZ\Valinor\Cache\VersionedCache; +use CuyZ\Valinor\Cache\KeySanitizerCache; use CuyZ\Valinor\Cache\Warmup\RecursiveCacheWarmupService; use CuyZ\Valinor\Definition\FunctionsContainer; use CuyZ\Valinor\Definition\Repository\AttributesRepository; @@ -222,10 +222,10 @@ final class Container $cache = new RuntimeCache(); if (isset($settings->cache)) { - $cache = new ChainCache($cache, $settings->cache); + $cache = new ChainCache($cache, new KeySanitizerCache($settings->cache)); } - return new VersionedCache($cache); + return $cache; }, ]; } diff --git a/tests/Unit/Cache/VersionedCacheTest.php b/tests/Unit/Cache/KeySanitizerCacheTest.php similarity index 89% rename from tests/Unit/Cache/VersionedCacheTest.php rename to tests/Unit/Cache/KeySanitizerCacheTest.php index b18b2a0..13eb03d 100644 --- a/tests/Unit/Cache/VersionedCacheTest.php +++ b/tests/Unit/Cache/KeySanitizerCacheTest.php @@ -4,25 +4,25 @@ declare(strict_types=1); namespace CuyZ\Valinor\Tests\Unit\Cache; -use CuyZ\Valinor\Cache\VersionedCache; +use CuyZ\Valinor\Cache\KeySanitizerCache; use CuyZ\Valinor\Tests\Fake\Cache\FakeCache; use PHPUnit\Framework\TestCase; use function iterator_to_array; -final class VersionedCacheTest extends TestCase +final class KeySanitizerCacheTest extends TestCase { private FakeCache $delegate; - /** @var VersionedCache */ - private VersionedCache $cache; + /** @var KeySanitizerCache */ + private KeySanitizerCache $cache; protected function setUp(): void { parent::setUp(); $this->delegate = new FakeCache(); - $this->cache = new VersionedCache($this->delegate); + $this->cache = new KeySanitizerCache($this->delegate); } public function test_set_value_sets_value_in_delegate_with_changed_key(): void