Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
105
core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
Normal file
105
core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\PhpStorage\FileReadOnlyStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\PhpStorage;
|
||||
|
||||
/**
|
||||
* Reads code as regular PHP files, but won't write them.
|
||||
*/
|
||||
class FileReadOnlyStorage implements PhpStorageInterface {
|
||||
|
||||
/**
|
||||
* The directory where the files should be stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* Constructs this FileStorage object.
|
||||
*
|
||||
* @param $configuration
|
||||
* An associative array, containing at least two keys (the rest are ignored):
|
||||
* - directory: The directory where the files should be stored.
|
||||
* - bin: The storage bin. Multiple storage objects can be instantiated with
|
||||
* the same configuration, but for different bins.
|
||||
*/
|
||||
public function __construct(array $configuration) {
|
||||
|
||||
$this->directory = $configuration['directory'] . '/' . $configuration['bin'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::exists().
|
||||
*/
|
||||
public function exists($name) {
|
||||
return file_exists($this->getFullPath($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::load().
|
||||
*/
|
||||
public function load($name) {
|
||||
// The FALSE returned on failure is enough for the caller to handle this,
|
||||
// we do not want a warning too.
|
||||
return (@include_once $this->getFullPath($name)) !== FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::save().
|
||||
*/
|
||||
public function save($name, $code) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::delete().
|
||||
*/
|
||||
public function delete($name) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFullPath($name) {
|
||||
return $this->directory . '/' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::writeable().
|
||||
*/
|
||||
function writeable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::deleteAll().
|
||||
*/
|
||||
public function deleteAll() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listAll() {
|
||||
$names = array();
|
||||
if (file_exists($this->directory)) {
|
||||
foreach (new \DirectoryIterator($this->directory) as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
$name = $fileinfo->getFilename();
|
||||
if ($name != '.htaccess') {
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
}
|
269
core/lib/Drupal/Component/PhpStorage/FileStorage.php
Normal file
269
core/lib/Drupal/Component/PhpStorage/FileStorage.php
Normal file
|
@ -0,0 +1,269 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\PhpStorage\FileStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\PhpStorage;
|
||||
|
||||
/**
|
||||
* Stores the code as regular PHP files.
|
||||
*/
|
||||
class FileStorage implements PhpStorageInterface {
|
||||
|
||||
/**
|
||||
* The directory where the files should be stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* Constructs this FileStorage object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* An associative array, containing at least these two keys:
|
||||
* - directory: The directory where the files should be stored.
|
||||
* - bin: The storage bin. Multiple storage objects can be instantiated with
|
||||
* the same configuration, but for different bins..
|
||||
*/
|
||||
public function __construct(array $configuration) {
|
||||
$this->directory = $configuration['directory'] . '/' . $configuration['bin'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::exists().
|
||||
*/
|
||||
public function exists($name) {
|
||||
return file_exists($this->getFullPath($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::load().
|
||||
*/
|
||||
public function load($name) {
|
||||
// The FALSE returned on failure is enough for the caller to handle this,
|
||||
// we do not want a warning too.
|
||||
return (@include_once $this->getFullPath($name)) !== FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::save().
|
||||
*/
|
||||
public function save($name, $code) {
|
||||
$path = $this->getFullPath($name);
|
||||
$directory = dirname($path);
|
||||
if ($this->ensureDirectory($directory)) {
|
||||
$htaccess_path = $directory . '/.htaccess';
|
||||
if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) {
|
||||
@chmod($htaccess_path, 0444);
|
||||
}
|
||||
}
|
||||
return (bool) file_put_contents($path, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard .htaccess lines that Drupal writes to file directories.
|
||||
*
|
||||
* @param bool $private
|
||||
* (optional) Set to FALSE to return the .htaccess lines for an open and
|
||||
* public directory. The default is TRUE, which returns the .htaccess lines
|
||||
* for a private and protected directory.
|
||||
*
|
||||
* @return string
|
||||
* The desired contents of the .htaccess file.
|
||||
*
|
||||
* @see file_create_htaccess()
|
||||
*/
|
||||
public static function htaccessLines($private = TRUE) {
|
||||
$lines = <<<EOF
|
||||
# Turn off all options we don't need.
|
||||
Options None
|
||||
Options +FollowSymLinks
|
||||
|
||||
# Set the catch-all handler to prevent scripts from being executed.
|
||||
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
|
||||
<Files *>
|
||||
# Override the handler again if we're run later in the evaluation list.
|
||||
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
|
||||
</Files>
|
||||
|
||||
# If we know how to do it safely, disable the PHP engine entirely.
|
||||
<IfModule mod_php5.c>
|
||||
php_flag engine off
|
||||
</IfModule>
|
||||
EOF;
|
||||
|
||||
if ($private) {
|
||||
$lines = <<<EOF
|
||||
# Deny all requests from Apache 2.4+.
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
|
||||
# Deny all requests from Apache 2.0-2.2.
|
||||
<IfModule !mod_authz_core.c>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
$lines
|
||||
EOF;
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the directory exists, has the right permissions, and a .htaccess.
|
||||
*
|
||||
* For compatibility with open_basedir, the requested directory is created
|
||||
* using a recursion logic that is based on the relative directory path/tree:
|
||||
* It works from the end of the path recursively back towards the root
|
||||
* directory, until an existing parent directory is found. From there, the
|
||||
* subdirectories are created.
|
||||
*
|
||||
* @param string $directory
|
||||
* The directory path.
|
||||
* @param int $mode
|
||||
* The mode, permissions, the directory should have.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the directory exists or has been created, FALSE otherwise.
|
||||
*/
|
||||
protected function ensureDirectory($directory, $mode = 0777) {
|
||||
if ($this->createDirectory($directory, $mode)) {
|
||||
$htaccess_path = $directory . '/.htaccess';
|
||||
if (!file_exists($htaccess_path) && file_put_contents($htaccess_path, static::htaccessLines())) {
|
||||
@chmod($htaccess_path, 0444);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the requested directory exists and has the right permissions.
|
||||
*
|
||||
* For compatibility with open_basedir, the requested directory is created
|
||||
* using a recursion logic that is based on the relative directory path/tree:
|
||||
* It works from the end of the path recursively back towards the root
|
||||
* directory, until an existing parent directory is found. From there, the
|
||||
* subdirectories are created.
|
||||
*
|
||||
* @param string $directory
|
||||
* The directory path.
|
||||
* @param int $mode
|
||||
* The mode, permissions, the directory should have.
|
||||
* @param bool $is_backwards_recursive
|
||||
* Internal use only.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the directory exists or has been created, FALSE otherwise.
|
||||
*/
|
||||
protected function createDirectory($directory, $mode = 0777, $is_backwards_recursive = FALSE) {
|
||||
// If the directory exists already, there's nothing to do.
|
||||
if (is_dir($directory)) {
|
||||
return TRUE;
|
||||
}
|
||||
// Otherwise, try to create the directory and ensure to set its permissions,
|
||||
// because mkdir() obeys the umask of the current process.
|
||||
if (is_dir($parent = dirname($directory))) {
|
||||
// If the parent directory exists, then the backwards recursion must end,
|
||||
// regardless of whether the subdirectory could be created.
|
||||
if ($status = mkdir($directory)) {
|
||||
// Only try to chmod() if the subdirectory could be created.
|
||||
$status = chmod($directory, $mode);
|
||||
}
|
||||
return $is_backwards_recursive ? TRUE : $status;
|
||||
}
|
||||
// If the parent directory and the requested directory does not exist and
|
||||
// could not be created above, walk the requested directory path back up
|
||||
// until an existing directory is hit, and from there, recursively create
|
||||
// the sub-directories. Only if that recursion succeeds, create the final,
|
||||
// originally requested subdirectory.
|
||||
return $this->createDirectory($parent, $mode, TRUE) && mkdir($directory) && chmod($directory, $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::delete().
|
||||
*/
|
||||
public function delete($name) {
|
||||
$path = $this->getFullPath($name);
|
||||
if (file_exists($path)) {
|
||||
return $this->unlink($path);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFullPath($name) {
|
||||
return $this->directory . '/' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::writeable().
|
||||
*/
|
||||
public function writeable() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::deleteAll().
|
||||
*/
|
||||
public function deleteAll() {
|
||||
return $this->unlink($this->directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes files and/or directories in the specified path.
|
||||
*
|
||||
* If the specified path is a directory the method will
|
||||
* call itself recursively to process the contents. Once the contents have
|
||||
* been removed the directory will also be removed.
|
||||
*
|
||||
* @param string $path
|
||||
* A string containing either a file or directory path.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE for success or if path does not exist, FALSE in the event of an
|
||||
* error.
|
||||
*/
|
||||
protected function unlink($path) {
|
||||
if (file_exists($path)) {
|
||||
if (is_dir($path)) {
|
||||
// Ensure the folder is writable.
|
||||
@chmod($path, 0777);
|
||||
foreach (new \DirectoryIterator($path) as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
$this->unlink($fileinfo->getPathName());
|
||||
}
|
||||
}
|
||||
return @rmdir($path);
|
||||
}
|
||||
// Windows needs the file to be writable.
|
||||
@chmod($path, 0700);
|
||||
return @unlink($path);
|
||||
}
|
||||
// If there's nothing to delete return TRUE anyway.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listAll() {
|
||||
$names = array();
|
||||
if (file_exists($this->directory)) {
|
||||
foreach (new \DirectoryIterator($this->directory) as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
$name = $fileinfo->getFilename();
|
||||
if ($name != '.htaccess') {
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\PhpStorage;
|
||||
|
||||
/**
|
||||
* Stores PHP code in files with securely hashed names.
|
||||
*
|
||||
* The goal of this class is to ensure that if a PHP file is replaced with
|
||||
* an untrusted one, it does not get loaded. Since mtime granularity is 1
|
||||
* second, we cannot prevent an attack that happens within one second of the
|
||||
* initial save(). However, it is very unlikely for an attacker exploiting an
|
||||
* upload or file write vulnerability to also know when a legitimate file is
|
||||
* being saved, discover its hash, undo its file permissions, and override the
|
||||
* file with an upload all within a single second. Being able to accomplish
|
||||
* that would indicate a site very likely vulnerable to many other attack
|
||||
* vectors.
|
||||
*
|
||||
* Each file is stored in its own unique containing directory. The hash is based
|
||||
* on the virtual file name, the containing directory's mtime, and a
|
||||
* cryptographically hard to guess secret string. Thus, even if the hashed file
|
||||
* name is discovered and replaced by an untrusted file (e.g., via a
|
||||
* move_uploaded_file() invocation by a script that performs insufficient
|
||||
* validation), the directory's mtime gets updated in the process, invalidating
|
||||
* the hash and preventing the untrusted file from getting loaded.
|
||||
*
|
||||
* This class does not protect against overwriting a file in-place (e.g. a
|
||||
* malicious module that does a file_put_contents()) since this will not change
|
||||
* the mtime of the directory. MTimeProtectedFileStorage protects against this
|
||||
* at the cost of an additional system call for every load() and exists().
|
||||
*
|
||||
* The containing directory is created with the same name as the virtual file
|
||||
* name (slashes removed) to assist with debugging, since the file itself is
|
||||
* stored with a name that's meaningless to humans.
|
||||
*/
|
||||
class MTimeProtectedFastFileStorage extends FileStorage {
|
||||
|
||||
/**
|
||||
* The secret used in the HMAC.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $secret;
|
||||
|
||||
/**
|
||||
* Constructs this MTimeProtectedFastFileStorage object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* An associated array, containing at least these keys (the rest are
|
||||
* ignored):
|
||||
* - directory: The directory where the files should be stored.
|
||||
* - secret: A cryptographically hard to guess secret string.
|
||||
* -bin. The storage bin. Multiple storage objects can be instantiated with
|
||||
* the same configuration, but for different bins.
|
||||
*/
|
||||
public function __construct(array $configuration) {
|
||||
parent::__construct($configuration);
|
||||
$this->secret = $configuration['secret'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::save().
|
||||
*/
|
||||
public function save($name, $data) {
|
||||
$this->ensureDirectory($this->directory);
|
||||
|
||||
// Write the file out to a temporary location. Prepend with a '.' to keep it
|
||||
// hidden from listings and web servers.
|
||||
$temporary_path = $this->tempnam($this->directory, '.');
|
||||
if (!$temporary_path || !@file_put_contents($temporary_path, $data)) {
|
||||
return FALSE;
|
||||
}
|
||||
// The file will not be chmod() in the future so this is the final
|
||||
// permission.
|
||||
chmod($temporary_path, 0444);
|
||||
|
||||
// Prepare a directory dedicated for just this file. Ensure it has a current
|
||||
// mtime so that when the file (hashed on that mtime) is moved into it, the
|
||||
// mtime remains the same (unless the clock ticks to the next second during
|
||||
// the rename, in which case we'll try again).
|
||||
$directory = $this->getContainingDirectoryFullPath($name);
|
||||
if (file_exists($directory)) {
|
||||
$this->unlink($directory);
|
||||
}
|
||||
$this->ensureDirectory($directory);
|
||||
|
||||
// Move the file to its final place. The mtime of a directory is the time of
|
||||
// the last file create or delete in the directory. So the moving will
|
||||
// update the directory mtime. However, this update will very likely not
|
||||
// show up, because it has a coarse, one second granularity and typical
|
||||
// moves takes significantly less than that. In the unlucky case the clock
|
||||
// ticks during the move, we need to keep trying until the mtime we hashed
|
||||
// on and the updated mtime match.
|
||||
$previous_mtime = 0;
|
||||
$i = 0;
|
||||
while (($mtime = $this->getUncachedMTime($directory)) && ($mtime != $previous_mtime)) {
|
||||
$previous_mtime = $mtime;
|
||||
// Reset the file back in the temporary location if this is not the first
|
||||
// iteration.
|
||||
if ($i > 0) {
|
||||
$this->unlink($temporary_path);
|
||||
$temporary_path = $this->tempnam($this->directory, '.');
|
||||
rename($full_path, $temporary_path);
|
||||
// Make sure to not loop infinitely on a hopelessly slow filesystem.
|
||||
if ($i > 10) {
|
||||
$this->unlink($temporary_path);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
$full_path = $this->getFullPath($name, $directory, $mtime);
|
||||
rename($temporary_path, $full_path);
|
||||
$i++;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path where the file is or should be stored.
|
||||
*
|
||||
* This function creates a file path that includes a unique containing
|
||||
* directory for the file and a file name that is a hash of the virtual file
|
||||
* name, a cryptographic secret, and the containing directory mtime. If the
|
||||
* file is overridden by an insecure upload script, the directory mtime gets
|
||||
* modified, invalidating the file, thus protecting against untrusted code
|
||||
* getting executed.
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
* @param string $directory
|
||||
* (optional) The directory containing the file. If not passed, this is
|
||||
* retrieved by calling getContainingDirectoryFullPath().
|
||||
* @param int $directory_mtime
|
||||
* (optional) The mtime of $directory. Can be passed to avoid an extra
|
||||
* filesystem call when the mtime of the directory is already known.
|
||||
*
|
||||
* @return string
|
||||
* The full path where the file is or should be stored.
|
||||
*/
|
||||
public function getFullPath($name, &$directory = NULL, &$directory_mtime = NULL) {
|
||||
if (!isset($directory)) {
|
||||
$directory = $this->getContainingDirectoryFullPath($name);
|
||||
}
|
||||
if (!isset($directory_mtime)) {
|
||||
$directory_mtime = file_exists($directory) ? filemtime($directory) : 0;
|
||||
}
|
||||
return $directory . '/' . hash_hmac('sha256', $name, $this->secret . $directory_mtime) . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($name) {
|
||||
$path = $this->getContainingDirectoryFullPath($name);
|
||||
if (file_exists($path)) {
|
||||
return $this->unlink($path);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path of the containing directory where the file is or should
|
||||
* be stored.
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
*
|
||||
* @return string
|
||||
* The full path of the containing directory where the file is or should be
|
||||
* stored.
|
||||
*/
|
||||
protected function getContainingDirectoryFullPath($name) {
|
||||
// Remove the .php file extension from the directory name.
|
||||
// Within a single directory, a subdirectory cannot have the same name as a
|
||||
// file. Thus, when switching between MTimeProtectedFastFileStorage and
|
||||
// FileStorage, the subdirectory or the file cannot be created in case the
|
||||
// other file type exists already.
|
||||
if (substr($name, -4) === '.php') {
|
||||
$name = substr($name, 0, -4);
|
||||
}
|
||||
return $this->directory . '/' . str_replace('/', '#', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears PHP's stat cache and returns the directory's mtime.
|
||||
*/
|
||||
protected function getUncachedMTime($directory) {
|
||||
clearstatcache(TRUE, $directory);
|
||||
return filemtime($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* A brute force tempnam implementation supporting streams.
|
||||
*
|
||||
* @param $directory
|
||||
* The directory where the temporary filename will be created.
|
||||
* @param $prefix
|
||||
* The prefix of the generated temporary filename.
|
||||
* @return string
|
||||
* Returns the new temporary filename (with path), or FALSE on failure.
|
||||
*/
|
||||
protected function tempnam($directory, $prefix) {
|
||||
do {
|
||||
$path = $directory . '/' . $prefix . substr(str_shuffle(hash('sha256', microtime())), 0, 10);
|
||||
} while (file_exists($path));
|
||||
return $path;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\PhpStorage\MTimeProtectedFileStorage.
|
||||
*/
|
||||
namespace Drupal\Component\PhpStorage;
|
||||
|
||||
/**
|
||||
* Stores PHP code in files with securely hashed names.
|
||||
*
|
||||
* The goal of this class is to ensure that if a PHP file is replaced with
|
||||
* an untrusted one, it does not get loaded. Since mtime granularity is 1
|
||||
* second, we cannot prevent an attack that happens within one second of the
|
||||
* initial save(). However, it is very unlikely for an attacker exploiting an
|
||||
* upload or file write vulnerability to also know when a legitimate file is
|
||||
* being saved, discover its hash, undo its file permissions, and override the
|
||||
* file with an upload all within a single second. Being able to accomplish
|
||||
* that would indicate a site very likely vulnerable to many other attack
|
||||
* vectors.
|
||||
*
|
||||
* Each file is stored in its own unique containing directory. The hash is
|
||||
* based on the virtual file name, the containing directory's mtime, and a
|
||||
* cryptographically hard to guess secret string. Thus, even if the hashed file
|
||||
* name is discovered and replaced by an untrusted file (e.g., via a
|
||||
* move_uploaded_file() invocation by a script that performs insufficient
|
||||
* validation), the directory's mtime gets updated in the process, invalidating
|
||||
* the hash and preventing the untrusted file from getting loaded. Also, the
|
||||
* file mtime will be checked providing security against overwriting in-place,
|
||||
* at the cost of an additional system call for every load() and exists().
|
||||
*
|
||||
* The containing directory is created with the same name as the virtual file
|
||||
* name (slashes replaced with hashmarks) to assist with debugging, since the
|
||||
* file itself is stored with a name that's meaningless to humans.
|
||||
*/
|
||||
class MTimeProtectedFileStorage extends MTimeProtectedFastFileStorage {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::load().
|
||||
*/
|
||||
public function load($name) {
|
||||
if (($filename = $this->checkFile($name)) !== FALSE) {
|
||||
// Inline parent::load() to avoid an expensive getFullPath() call.
|
||||
return (@include_once $filename) !== FALSE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::exists().
|
||||
*/
|
||||
public function exists($name) {
|
||||
return $this->checkFile($name) !== FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a protected file exists and sets the filename too.
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
* return string
|
||||
* The full path where the file is if it is valid, FALSE otherwise.
|
||||
*/
|
||||
protected function checkFile($name) {
|
||||
$filename = $this->getFullPath($name, $directory, $directory_mtime);
|
||||
return file_exists($filename) && filemtime($filename) <= $directory_mtime ? $filename : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPath($name) {
|
||||
return $this->checkFile($name);
|
||||
}
|
||||
|
||||
}
|
102
core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
Normal file
102
core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\PhpStorage\PhpStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\PhpStorage;
|
||||
|
||||
/**
|
||||
* Stores and loads PHP code.
|
||||
*
|
||||
* Each interface function takes $name as a parameter. This is a virtual file
|
||||
* name: for example, 'foo.php' or 'some/relative/path/to/foo.php'. The
|
||||
* storage implementation may store these as files within the local file system,
|
||||
* use a remote stream, combine multiple virtual files into an archive, store
|
||||
* them in database records, or use some other storage technique.
|
||||
*/
|
||||
interface PhpStorageInterface {
|
||||
|
||||
/**
|
||||
* Checks whether the PHP code exists in storage.
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the virtual file exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($name);
|
||||
|
||||
/**
|
||||
* Loads PHP code from storage.
|
||||
*
|
||||
* Depending on storage implementation, exists() checks can be expensive, so
|
||||
* this function may be called for a file that doesn't exist, and that should
|
||||
* not result in errors. This function does not return anything, so it is
|
||||
* up to the caller to determine if any code was loaded (for example, check
|
||||
* class_exists() or function_exists() for what was expected in the code).
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
*/
|
||||
public function load($name);
|
||||
|
||||
/**
|
||||
* Saves PHP code to storage.
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
* @param string $code
|
||||
* The PHP code to be saved.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the save succeeded, FALSE if it failed.
|
||||
*/
|
||||
public function save($name, $code);
|
||||
|
||||
/**
|
||||
* Whether this is a writeable storage.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function writeable();
|
||||
|
||||
/**
|
||||
* Deletes PHP code from storage.
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the delete succeeded, FALSE if it failed.
|
||||
*/
|
||||
public function delete($name);
|
||||
|
||||
/**
|
||||
* Removes all files in this bin.
|
||||
*/
|
||||
public function deleteAll();
|
||||
|
||||
/**
|
||||
* Gets the full file path.
|
||||
*
|
||||
* @param string $name
|
||||
* The virtual file name. Can be a relative path.
|
||||
*
|
||||
* @return string|FALSE
|
||||
* The full file path for the provided name. Return FALSE if the
|
||||
* implementation needs to prevent access to the file.
|
||||
*/
|
||||
public function getFullPath($name);
|
||||
|
||||
/**
|
||||
* Lists all the files in the storage.
|
||||
*
|
||||
* @return array
|
||||
* Array of filenames.
|
||||
*/
|
||||
public function listAll();
|
||||
|
||||
}
|
Reference in a new issue