Skip to content

Commit

Permalink
Merge pull request #4637 from MGatner/cache-hash
Browse files Browse the repository at this point in the history
Cache Key Validation
  • Loading branch information
MGatner authored May 5, 2021
2 parents 2b5e848 + cf52dda commit 2e0e020
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 75 deletions.
51 changes: 51 additions & 0 deletions system/Cache/Handlers/BaseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,63 @@
use Closure;
use CodeIgniter\Cache\CacheInterface;
use Exception;
use InvalidArgumentException;

/**
* Base class for cache handling
*/
abstract class BaseHandler implements CacheInterface
{
/**
* Reserved characters that cannot be used in a key or tag.
* From https://github.com/symfony/cache-contracts/blob/c0446463729b89dd4fa62e9aeecc80287323615d/ItemInterface.php#L43
*/
public const RESERVED_CHARACTERS = '{}()/\@:';

/**
* Maximum key length.
*/
public const MAX_KEY_LENGTH = PHP_INT_MAX;

/**
* Prefix to apply to cache keys.
* May not be used by all handlers.
*
* @var string
*/
protected $prefix;

/**
* Validates a cache key according to PSR-6.
* Keys that exceed MAX_KEY_LENGTH are hashed.
* From https://github.com/symfony/cache/blob/7b024c6726af21fd4984ac8d1eae2b9f3d90de88/CacheItem.php#L158
*
* @param string $key The key to validate
* @param string $prefix Optional prefix to include in length calculations
*
* @throws InvalidArgumentException When $key is not valid
*/
public static function validateKey($key, $prefix = ''): string
{
if (! is_string($key))
{
throw new InvalidArgumentException('Cache key must be a string');
}
if ($key === '')
{
throw new InvalidArgumentException('Cache key cannot be empty.');
}
if (strpbrk($key, self::RESERVED_CHARACTERS) !== false)
{
throw new InvalidArgumentException('Cache key contains reserved characters ' . self::RESERVED_CHARACTERS);
}

// If the key with prefix exceeds the length then return the hashed version
return strlen($prefix . $key) > static::MAX_KEY_LENGTH ? $prefix . md5($key) : $prefix . $key;
}

//--------------------------------------------------------------------

/**
* Get an item from the cache, or execute the given Closure and store the result.
*
Expand Down
37 changes: 16 additions & 21 deletions system/Cache/Handlers/FileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@
class FileHandler extends BaseHandler
{
/**
* Prefixed to all cache names.
*
* @var string
* Maximum key length.
*/
protected $prefix;
public const MAX_KEY_LENGTH = PHP_MAXPATHLEN;

/**
* Where to store cached files on the disk.
Expand Down Expand Up @@ -94,8 +92,7 @@ public function initialize()
*/
public function get(string $key)
{
$key = $this->prefix . $key;

$key = static::validateKey($key, $this->prefix);
$data = $this->getItem($key);

return is_array($data) ? $data['data'] : null;
Expand All @@ -114,7 +111,7 @@ public function get(string $key)
*/
public function save(string $key, $value, int $ttl = 60)
{
$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

$contents = [
'time' => time(),
Expand Down Expand Up @@ -152,7 +149,7 @@ public function save(string $key, $value, int $ttl = 60)
*/
public function delete(string $key)
{
$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

return is_file($this->path . $key) && unlink($this->path . $key);
}
Expand All @@ -170,7 +167,7 @@ public function deleteMatching(string $pattern)
{
$deleted = 0;

foreach (glob($this->path . $pattern, GLOB_NOSORT) as $filename)
foreach (glob($this->path . $pattern, GLOB_NOSORT) as $filename)
{
if (is_file($filename) && @unlink($filename))
{
Expand All @@ -193,8 +190,7 @@ public function deleteMatching(string $pattern)
*/
public function increment(string $key, int $offset = 1)
{
$key = $this->prefix . $key;

$key = static::validateKey($key, $this->prefix);
$data = $this->getItem($key);

if ($data === false)
Expand Down Expand Up @@ -222,12 +218,11 @@ public function increment(string $key, int $offset = 1)
* @param string $key Cache ID
* @param integer $offset Step/value to increase by
*
* @return bool
* @return boolean
*/
public function decrement(string $key, int $offset = 1)
{
$key = $this->prefix . $key;

$key = static::validateKey($key, $this->prefix);
$data = $this->getItem($key);

if ($data === false)
Expand Down Expand Up @@ -288,7 +283,7 @@ public function getCacheInfo()
*/
public function getMetaData(string $key)
{
$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

if (! is_file($this->path . $key))
{
Expand Down Expand Up @@ -342,26 +337,26 @@ public function isSupported(): bool
* Does the heavy lifting of actually retrieving the file and
* verifying it's age.
*
* @param string $key
* @param string $filename
*
* @return boolean|mixed
*/
protected function getItem(string $key)
protected function getItem(string $filename)
{
if (! is_file($this->path . $key))
if (! is_file($this->path . $filename))
{
return false;
}

$data = unserialize(file_get_contents($this->path . $key));
$data = unserialize(file_get_contents($this->path . $filename));

// @phpstan-ignore-next-line
if ($data['ttl'] > 0 && time() > $data['time'] + $data['ttl'])
{
// If the file is still there then remove it
if (is_file($this->path . $key))
if (is_file($this->path . $filename))
{
unlink($this->path . $key);
unlink($this->path . $filename);
}

return false;
Expand Down
20 changes: 6 additions & 14 deletions system/Cache/Handlers/MemcachedHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@
*/
class MemcachedHandler extends BaseHandler
{
/**
* Prefixed to all cache names.
*
* @var string
*/
protected $prefix;

/**
* The memcached object
*
Expand Down Expand Up @@ -166,7 +159,7 @@ public function initialize()
*/
public function get(string $key)
{
$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

if ($this->memcached instanceof Memcached)
{
Expand Down Expand Up @@ -206,7 +199,7 @@ public function get(string $key)
*/
public function save(string $key, $value, int $ttl = 60)
{
$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

if (! $this->config['raw'])
{
Expand Down Expand Up @@ -242,7 +235,7 @@ public function save(string $key, $value, int $ttl = 60)
*/
public function delete(string $key)
{
$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

return $this->memcached->delete($key);
}
Expand Down Expand Up @@ -278,7 +271,7 @@ public function increment(string $key, int $offset = 1)
return false;
}

$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

// @phpstan-ignore-next-line
return $this->memcached->increment($key, $offset, $offset, 60);
Expand All @@ -301,7 +294,7 @@ public function decrement(string $key, int $offset = 1)
return false;
}

$key = $this->prefix . $key;
$key = static::validateKey($key, $this->prefix);

//FIXME: third parameter isn't other handler actions.
// @phpstan-ignore-next-line
Expand Down Expand Up @@ -349,8 +342,7 @@ public function getCacheInfo()
*/
public function getMetaData(string $key)
{
$key = $this->prefix . $key;

$key = static::validateKey($key, $this->prefix);
$stored = $this->memcached->get($key);

// if not an array, don't try to count for PHP7.2
Expand Down
25 changes: 16 additions & 9 deletions system/Cache/Handlers/PredisHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@
*/
class PredisHandler extends BaseHandler
{
/**
* Prefixed to all cache names.
*
* @var string
*/
protected $prefix;

/**
* Default config
*
Expand Down Expand Up @@ -101,8 +94,12 @@ public function initialize()
*/
public function get(string $key)
{
$data = array_combine(
['__ci_type', '__ci_value'],
$key = static::validateKey($key);

$data = array_combine([
'__ci_type',
'__ci_value',
],
$this->redis->hmget($key, ['__ci_type', '__ci_value'])
);

Expand Down Expand Up @@ -141,6 +138,8 @@ public function get(string $key)
*/
public function save(string $key, $value, int $ttl = 60)
{
$key = static::validateKey($key);

switch ($dataType = gettype($value))
{
case 'array':
Expand Down Expand Up @@ -179,6 +178,8 @@ public function save(string $key, $value, int $ttl = 60)
*/
public function delete(string $key)
{
$key = static::validateKey($key);

return $this->redis->del($key) === 1;
}

Expand Down Expand Up @@ -215,6 +216,8 @@ public function deleteMatching(string $pattern)
*/
public function increment(string $key, int $offset = 1)
{
$key = static::validateKey($key);

return $this->redis->hincrby($key, 'data', $offset);
}

Expand All @@ -230,6 +233,8 @@ public function increment(string $key, int $offset = 1)
*/
public function decrement(string $key, int $offset = 1)
{
$key = static::validateKey($key);

return $this->redis->hincrby($key, 'data', -$offset);
}

Expand Down Expand Up @@ -273,6 +278,8 @@ public function getCacheInfo()
*/
public function getMetaData(string $key)
{
$key = static::validateKey($key);

$data = array_combine(['__ci_value'], $this->redis->hmget($key, ['__ci_value']));

if (isset($data['__ci_value']) && $data['__ci_value'] !== false)
Expand Down
Loading

0 comments on commit 2e0e020

Please sign in to comment.