Skip to content

Commit

Permalink
qa: rework maximum key length detection/validation
Browse files Browse the repository at this point in the history
Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
  • Loading branch information
boesing committed May 16, 2021
1 parent 107b848 commit ef1132b
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 18 deletions.
57 changes: 40 additions & 17 deletions src/Psr/SimpleCache/SimpleCacheDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
use Exception;
use Laminas\Cache\Exception\InvalidArgumentException as LaminasCacheInvalidArgumentException;
use Laminas\Cache\Psr\SerializationTrait;
use Laminas\Cache\Storage\Capabilities;
use Laminas\Cache\Storage\ClearByNamespaceInterface;
use Laminas\Cache\Storage\FlushableInterface;
use Laminas\Cache\Storage\StorageInterface;
use Psr\SimpleCache\CacheInterface as SimpleCacheInterface;
use Throwable;
use Traversable;
use function get_class;
use function sprintf;

/**
* Decorate a laminas-cache storage adapter for usage as a PSR-16 implementation.
Expand Down Expand Up @@ -50,6 +53,11 @@ class SimpleCacheDecorator implements SimpleCacheInterface
*/
private $utc;

/**
* @var int
*/
private $maximumKeyLength;

public function __construct(StorageInterface $storage)
{
if ($this->isSerializationRequired($storage)) {
Expand All @@ -61,7 +69,10 @@ public function __construct(StorageInterface $storage)
));
}

$this->memoizeTtlCapabilities($storage);
$capabilities = $storage->getCapabilities();
$this->memoizeTtlCapabilities($capabilities);
$this->memoizeMaximumKeyLengthCapability($storage, $capabilities);

$this->storage = $storage;
$this->utc = new DateTimeZone('UTC');
}
Expand Down Expand Up @@ -341,33 +352,20 @@ private function validateKey($key)
));
}

// get storage adapter capability value
$maximumKeyLength = $this->storage->getCapabilities()->getMaxKeyLength();

if ($maximumKeyLength === -1) {
// default to <= 64 characters as per PSR-16
$maximumKeyLength = 64;
}

//skip length check, if adapter capability reports unlimited key length
if ($maximumKeyLength > 0 && preg_match('/^.{'.($maximumKeyLength + 1).',}/u', $key)) {
if ($this->maximumKeyLength > 0 && preg_match('/^.{'.($this->maximumKeyLength + 1).',}/u', $key)) {
throw new SimpleCacheInvalidArgumentException(sprintf(
'Invalid key "%s" provided; key is too long. Must be no more than %d characters',
$key,
$maximumKeyLength
$this->maximumKeyLength
));
}
}

/**
* Determine if the storage adapter provides per-item TTL capabilities
*
* @param StorageInterface $storage
* @return void
*/
private function memoizeTtlCapabilities(StorageInterface $storage)
private function memoizeTtlCapabilities(Capabilities $capabilities): void
{
$capabilities = $storage->getCapabilities();
$this->providesPerItemTtl = $capabilities->getStaticTtl() && (0 < $capabilities->getMinTtl());
}

Expand Down Expand Up @@ -451,4 +449,29 @@ private function convertIterableToArray($iterable, $useKeys, $forMethod)
}
return $array;
}

private function memoizeMaximumKeyLengthCapability(StorageInterface $storage, Capabilities $capabilities): void
{
$maximumKeyLength = $capabilities->getMaxKeyLength();

if ($maximumKeyLength === Capabilities::UNLIMITED_KEY_LENGTH) {
$this->maximumKeyLength = Capabilities::UNLIMITED_KEY_LENGTH;
return;
}

if ($maximumKeyLength === Capabilities::UNKNOWN_KEY_LENGTH) {
// For backward compatibility, assume adapters which do not provide a maximum key length do support 64 chars
$maximumKeyLength = 64;
}

if ($maximumKeyLength < 64) {
throw new SimpleCacheInvalidArgumentException(sprintf(
'The storage adapter "%s" does not fulfill the minimum requirements for PSR-16:'
.' The maximum key length capability must allow at least 64 characters.',
get_class($storage)
));
}

$this->maximumKeyLength = $maximumKeyLength;
}
}
5 changes: 4 additions & 1 deletion src/Storage/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

class Capabilities
{
public const UNKNOWN_KEY_LENGTH = -1;
public const UNLIMITED_KEY_LENGTH = 0;

/**
* The storage instance
*
Expand Down Expand Up @@ -461,7 +464,7 @@ public function setLockOnExpire(stdClass $marker, $timeout)
*/
public function getMaxKeyLength()
{
return $this->getCapability('maxKeyLength', -1);
return $this->getCapability('maxKeyLength', self::UNKNOWN_KEY_LENGTH);
}

/**
Expand Down
28 changes: 28 additions & 0 deletions test/Psr/SimpleCache/SimpleCacheDecoratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace LaminasTest\Cache\Psr\SimpleCache;

use ArrayIterator;
use Generator;
use Laminas\Cache\Exception;
use Laminas\Cache\Psr\SimpleCache\SimpleCacheDecorator;
use Laminas\Cache\Psr\SimpleCache\SimpleCacheException;
Expand All @@ -18,6 +19,7 @@
use Prophecy\Prophecy\ObjectProphecy;
use Psr\SimpleCache\CacheInterface as SimpleCacheInterface;
use ReflectionProperty;
use stdClass;

/**
* Test the PSR-16 decorator.
Expand Down Expand Up @@ -53,6 +55,16 @@ class SimpleCacheDecoratorTest extends TestCase
/** @var SimpleCacheDecorator */
private $cache;

/**
* @psalm-return Generator<non-empty-string,array{0:Capabilities}>
*/
public function unsupportedCapabilities(): Generator
{
yield 'minimum key length <64 characters' => [
$this->getMockCapabilities(null, true, 60, 63)->reveal(),
];
}

protected function setUp(): void
{
$this->options = $this->prophesize(AdapterOptions::class);
Expand Down Expand Up @@ -839,4 +851,20 @@ public function testUseTtlFromOptionsOnSetMultipleMocking()

self::assertTrue($this->cache->setMultiple(['foo' => 'bar', 'boo' => 'baz']));
}

/**
* @dataProvider unsupportedCapabilities
*/
public function testWillThrowExceptionWhenStorageDoesNotFulfillMinimumRequirements(Capabilities $capabilities): void
{
$storage = $this->createMock(StorageInterface::class);
$storage
->method('getCapabilities')
->willReturn($capabilities);

$this->expectException(SimpleCacheInvalidArgumentException::class);
$this->expectExceptionMessage('does not fulfill the minimum requirements for PSR-16');

new SimpleCacheDecorator($storage);
}
}

0 comments on commit ef1132b

Please sign in to comment.