diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php index faf2459..3bf9fb4 100644 --- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php +++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php @@ -120,6 +120,7 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array * ), * ); * @endcode + * Backend implementations must keep the order of items. */ public function setMultiple(array $items); diff --git a/core/lib/Drupal/Core/PhpStorage/CacheStorage.php b/core/lib/Drupal/Core/PhpStorage/CacheStorage.php new file mode 100644 index 0000000..daf2cfb --- /dev/null +++ b/core/lib/Drupal/Core/PhpStorage/CacheStorage.php @@ -0,0 +1,199 @@ +configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function exists($name) { + $key = $this->getKeyFromFilename($name); + $cids = [$key, "$key:mtime"]; + $this->cacheBackend()->getMultiple($cids); + return !$cids; + } + + /** + * {@inheritdoc} + */ + public function load($name) { + $key = $this->getKeyFromFilename($name); + $cached = $this->cacheBackend()->get("$key:mtime"); + if (!$cached) { + return FALSE; + } + // Load the data from cache. + // Hook in the fake phar wrapper. Opcode included in PHP 5.5 hardwires file + // and phar as the only two stream wrappers which can be opcode cached. + // The file protocol is used to read local files and will be triggered + // multiple times by the classloader as the container class is loaded. + // So for better performance use the phar protocol. + stream_wrapper_unregister('phar'); + stream_wrapper_register('phar', 'Drupal\Core\StreamWrapper\StreamWrapperForCacheStorage'); + StreamWrapperForCacheStorage::init($this, $cached->data); + $return = (include "phar://$name") !== FALSE; + #var_dump(opcache_get_status(TRUE)['scripts']["phar://$name"]); + // Restore the system wrapper. + stream_wrapper_restore('phar'); + return $return; + } + + /** + * @param $name + * @return bool|resource + */ + public function open($name) { + $key = $this->getKeyFromFilename(substr($name, 7)); + if (!$cached = $this->cacheBackend()->get($key)) { + return FALSE; + } + // Copy it into a file in memory. + if (!$handle = fopen('php://memory', 'rwb')) { + return FALSE; + } + if (fwrite($handle, $cached->data) === FALSE || fseek($handle, 0) === -1) { + fclose($handle); + return FALSE; + } + return $handle; + } + + /** + * {@inheritdoc} + */ + public function save($name, $code) { + $key = $this->getKeyFromFilename($name); + // We do not need a real mtime, we just need a timestamp that changes when + // the code changes. + $hash = hash('sha256', $code); + $mtime = hexdec(substr($hash, 0, 7)); + $this->cacheBackend()->setMultiple([ + $key => ['data' => $code], + "$key:mtime" => ['data' => $mtime], + ]); + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function writeable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function delete($name) { + $return = $this->exists($name); + $key = $this->getKeyFromFilename($name); + // Delete nonetheless because between exists and delete the entry might've + // been written. + $this->cacheBackend()->delete($key); + return $return; + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + $this->cacheBackend()->deleteAll(); + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getFullPath($name) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function listAll() { + // Only PhpBackEnd::invalidateAll() uses this method and that's not + // compatible anyways since it relies on getFullPath(). + throw new \BadMethodCallException('CacheStorage::listall() is not implemented.'); + } + + /** + * @return \Drupal\Core\Cache\CacheBackendInterface + */ + protected function cacheBackend() { + if (!isset($this->cacheBackend)) { + if (isset($this->configuration['cache_backend_factory'])) { + $this->cacheBackend = call_user_func($this->configuration['cache_backend_factory'], $this->configuration); + } + else { + $this->cacheBackend = static::getDatabaseBackend($this->configuration); + } + } + return $this->cacheBackend; + } + + /** + * Construct a database cache backend. + */ + protected static function getDatabaseBackend($configuration) { + $connection = Database::getConnection(); + return new DatabaseBackend($connection, new DatabaseCacheTagsChecksum($connection), 'php_' . $configuration['bin']); + } + + /** + * Return a secret key based on the filename. + * + * By using a secret key, a SQL injection does not lead immediately to + * arbitrary PHP inclusion. + * + * @param string $filename + * The filename. + * + * @return string + * The secret hash. + */ + protected function getKeyFromFilename($filename) { + return hash_hmac('sha256', $filename, $this->configuration['secret']); + } + +} diff --git a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php index 3386bba..5d8b4c5 100644 --- a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php +++ b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php @@ -48,6 +48,9 @@ static function get($name) { ); } $class = isset($configuration['class']) ? $configuration['class'] : 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage'; + if ($name == 'service_container') { + $class = 'Drupal\Core\PhpStorage\CacheStorage'; + } if (!isset($configuration['bin'])) { $configuration['bin'] = $name; } diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperForCacheStorage.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperForCacheStorage.php new file mode 100644 index 0000000..6ef53d0 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperForCacheStorage.php @@ -0,0 +1,102 @@ +open($path); + return (bool) static::$handle; + } + + public function stream_read($count) { + return fread(static::$handle, $count); + } + + public function stream_stat() { + return fstat(static::$handle); + } + + public function url_stat() { + $return = [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => static::$mtime, + 'ctime' => 0, + 'blksize' => -1, + 'blocks' => -1, + ]; + return $return + array_values($return); + } + +} diff --git a/core/tests/Drupal/Tests/Core/PhpStorage/CacheStorageTest.php b/core/tests/Drupal/Tests/Core/PhpStorage/CacheStorageTest.php new file mode 100644 index 0000000..da5a272 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/PhpStorage/CacheStorageTest.php @@ -0,0 +1,40 @@ +randomMachineName(); + $storage = new CacheStorage([ + 'secret' => $secret, + 'cache_backend_factory' => function (array $configuration) { return new MemoryBackend($configuration['bin']);}, + 'bin' => 'test' + ]); + $this->assertCRUD($storage); + } + +}