diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index bbb1f846e4cf5..34e1366f5df57 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -16,6 +16,7 @@ use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\ArrayTrait; +use Symfony\Component\Cache\Traits\KeyTrait; use Symfony\Contracts\Cache\CacheInterface; /** @@ -24,6 +25,7 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { use ArrayTrait; + use KeyTrait; private $createCacheItem; @@ -70,6 +72,7 @@ public function get(string $key, callable $callback, float $beta = null, array & */ public function getItem($key) { + $key = $this->encodeKey($key); if (!$isHit = $this->hasItem($key)) { $this->values[$key] = $value = null; } else { @@ -86,6 +89,7 @@ public function getItem($key) public function getItems(array $keys = []) { foreach ($keys as $key) { + $key = $this->encodeKey($key); if (!\is_string($key) || !isset($this->expiries[$key])) { CacheItem::validateKey($key); } @@ -100,6 +104,7 @@ public function getItems(array $keys = []) public function deleteItems(array $keys) { foreach ($keys as $key) { + $key = $this->encodeKey($key); $this->deleteItem($key); } @@ -158,6 +163,6 @@ public function commit() */ public function delete(string $key): bool { - return $this->deleteItem($key); + return $this->deleteItem($this->encodeKey($key)); } } diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index 5c294d03fd530..52593a9823c7d 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -15,6 +15,7 @@ use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\KeyTrait; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Service\ResetInterface; @@ -27,6 +28,8 @@ */ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { + use KeyTrait; + protected $pool; private $calls = []; @@ -44,6 +47,7 @@ public function get(string $key, callable $callback, float $beta = null, array & throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_class($this->pool), CacheInterface::class)); } + $key = $this->encodeKey($key); $isHit = true; $callback = function (CacheItem $item) use ($callback, &$isHit) { $isHit = $item->isHit(); @@ -73,6 +77,7 @@ public function get(string $key, callable $callback, float $beta = null, array & public function getItem($key) { $event = $this->start(__FUNCTION__); + $key = $this->encodeKey($key); try { $item = $this->pool->getItem($key); } finally { @@ -83,7 +88,6 @@ public function getItem($key) } else { ++$event->misses; } - return $item; } @@ -92,6 +96,7 @@ public function getItem($key) */ public function hasItem($key) { + $key = $this->encodeKey($key); $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->hasItem($key); @@ -105,6 +110,7 @@ public function hasItem($key) */ public function deleteItem($key) { + $key = $this->encodeKey($key); $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->deleteItem($key); @@ -153,6 +159,7 @@ public function getItems(array $keys = []) $f = function () use ($result, $event) { $event->result = []; foreach ($result as $key => $item) { + $key = $this->encodeKey($key); if ($event->result[$key] = $item->isHit()) { ++$event->hits; } else { @@ -184,6 +191,7 @@ public function clear() public function deleteItems(array $keys) { $event = $this->start(__FUNCTION__); + $keys = array_map([$this, 'encodeKey'], $keys); $event->result['keys'] = $keys; try { return $event->result['result'] = $this->pool->deleteItems($keys); @@ -243,6 +251,7 @@ public function reset() public function delete(string $key): bool { $event = $this->start(__FUNCTION__); + $key = $this->encodeKey($key); try { return $event->result[$key] = $this->pool->deleteItem($key); } finally { diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index 92eb9c39dfa32..d52214de44762 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -123,8 +123,8 @@ public function tag($tags): ItemInterface if ('' === $tag) { throw new InvalidArgumentException('Cache tag length must be greater than zero'); } - if (false !== strpbrk($tag, '{}()/\@:')) { - throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag)); + if (false !== strpbrk($tag, '{}()\:')) { + throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()\:', $tag)); } $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; } @@ -171,8 +171,8 @@ public static function validateKey($key) if ('' === $key) { throw new InvalidArgumentException('Cache key length must be greater than zero'); } - if (false !== strpbrk($key, '{}()/\@:')) { - throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key)); + if (false !== strpbrk($key, '{}()\:')) { + throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()\:', $key)); } return $key; diff --git a/src/Symfony/Component/Cache/Simple/Psr6Cache.php b/src/Symfony/Component/Cache/Simple/Psr6Cache.php index fceb9ba70fd80..5ec1bb4a4342c 100644 --- a/src/Symfony/Component/Cache/Simple/Psr6Cache.php +++ b/src/Symfony/Component/Cache/Simple/Psr6Cache.php @@ -20,6 +20,7 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\KeyTrait; use Symfony\Component\Cache\Traits\ProxyTrait; /** @@ -28,6 +29,7 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterface { use ProxyTrait; + use KeyTrait; private const METADATA_EXPIRY_OFFSET = 1527506807; @@ -89,6 +91,7 @@ public function get($key, $default = null) */ public function set($key, $value, $ttl = null) { + $key = $this->encodeKey($key); try { if (null !== $f = $this->createCacheItem) { $item = $f($key, $value); @@ -113,6 +116,7 @@ public function set($key, $value, $ttl = null) public function delete($key) { try { + $key = $this->encodeKey($key); return $this->pool->deleteItem($key); } catch (SimpleCacheException $e) { throw $e; @@ -151,6 +155,7 @@ public function getMultiple($keys, $default = null) if (!$this->pool instanceof AdapterInterface) { foreach ($items as $key => $item) { + $key = $this->encodeKey($key); $values[$key] = $item->isHit() ? $item->get() : $default; } @@ -158,6 +163,7 @@ public function getMultiple($keys, $default = null) } foreach ($items as $key => $item) { + $key = $this->encodeKey($key); if (!$item->isHit()) { $values[$key] = $default; continue; @@ -192,11 +198,13 @@ public function setMultiple($values, $ttl = null) if (null !== $f = $this->createCacheItem) { $valuesIsArray = false; foreach ($values as $key => $value) { + $key = $this->encodeKey($key); $items[$key] = $f($key, $value, true); } } elseif ($valuesIsArray) { $items = []; foreach ($values as $key => $value) { + $key = $this->encodeKey($key); $items[] = (string) $key; } $items = $this->pool->getItems($items); @@ -205,6 +213,7 @@ public function setMultiple($values, $ttl = null) if (\is_int($key)) { $key = (string) $key; } + $key = $this->encodeKey($key); $items[$key] = $this->pool->getItem($key)->set($value); } } @@ -217,7 +226,7 @@ public function setMultiple($values, $ttl = null) foreach ($items as $key => $item) { if ($valuesIsArray) { - $item->set($values[$key]); + $item->set($values[$this->encodeKey($key)]); } if (null !== $ttl) { $item->expiresAfter($ttl); @@ -254,6 +263,7 @@ public function deleteMultiple($keys) public function has($key) { try { + $key = $this->encodeKey($key); return $this->pool->hasItem($key); } catch (SimpleCacheException $e) { throw $e; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index 0eceb6e572b39..df8e3bf1c86ef 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -27,6 +27,33 @@ protected function setUp() } } + /** + * Data provider for invalid keys. + * @todo : Override method from cache/integration-tests for remove 2 keys (with @ and /) ! See how to update it without override the method ? + * + * @return array + */ + public static function invalidKeys() + { + return [ + [true], + [false], + [null], + [2], + [2.5], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand\\str'], + ['rand:str'], + [new \stdClass()], + [['array']], + ]; + } + public function testGet() { if (isset($this->skippedTests[__FUNCTION__])) { diff --git a/src/Symfony/Component/Cache/Tests/CacheItemTest.php b/src/Symfony/Component/Cache/Tests/CacheItemTest.php index 0e3f4b9a73510..7c0116c575250 100644 --- a/src/Symfony/Component/Cache/Tests/CacheItemTest.php +++ b/src/Symfony/Component/Cache/Tests/CacheItemTest.php @@ -39,9 +39,7 @@ public function provideInvalidKey() ['}'], ['('], [')'], - ['/'], ['\\'], - ['@'], [':'], [true], [null], diff --git a/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php b/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php index 3c8824869bd08..fbda3a3f500fb 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php @@ -26,6 +26,34 @@ protected function setUp() } } + /** + * Data provider for invalid keys. + * @todo : Override method from cache/integration-tests for remove 2 keys (with @ and /) ! See how to update it without override the method ? + * + * @return array + */ + public static function invalidKeys() + { + return [ + [''], + [true], + [false], + [null], + [2], + [2.5], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand\\str'], + ['rand:str'], + [new \stdClass()], + [['array']], + ]; + } + public static function validKeys() { return array_merge(parent::validKeys(), [["a\0b"]]); diff --git a/src/Symfony/Component/Cache/Traits/KeyTrait.php b/src/Symfony/Component/Cache/Traits/KeyTrait.php new file mode 100644 index 0000000000000..9b1ee52ce2dba --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/KeyTrait.php @@ -0,0 +1,17 @@ +