diff options
Diffstat (limited to 'library/Icinga/File/Storage')
-rw-r--r-- | library/Icinga/File/Storage/LocalFileStorage.php | 158 | ||||
-rw-r--r-- | library/Icinga/File/Storage/StorageInterface.php | 94 | ||||
-rw-r--r-- | library/Icinga/File/Storage/TemporaryLocalFileStorage.php | 59 |
3 files changed, 311 insertions, 0 deletions
diff --git a/library/Icinga/File/Storage/LocalFileStorage.php b/library/Icinga/File/Storage/LocalFileStorage.php new file mode 100644 index 0000000..e38167e --- /dev/null +++ b/library/Icinga/File/Storage/LocalFileStorage.php @@ -0,0 +1,158 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\File\Storage; + +use ErrorException; +use Icinga\Exception\AlreadyExistsException; +use Icinga\Exception\NotFoundError; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; +use InvalidArgumentException; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Traversable; +use UnexpectedValueException; + +/** + * Stores files in the local file system + */ +class LocalFileStorage implements StorageInterface +{ + /** + * The root directory of this storage + * + * @var string + */ + protected $baseDir; + + /** + * Constructor + * + * @param string $baseDir The root directory of this storage + */ + public function __construct($baseDir) + { + $this->baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR); + } + + public function getIterator(): Traversable + { + try { + return new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $this->baseDir, + RecursiveDirectoryIterator::CURRENT_AS_FILEINFO + | RecursiveDirectoryIterator::KEY_AS_PATHNAME + | RecursiveDirectoryIterator::SKIP_DOTS + ) + ); + } catch (UnexpectedValueException $e) { + throw new NotReadableError('Couldn\'t read the directory "%s": %s', $this->baseDir, $e); + } + } + + public function has($path) + { + return is_file($this->resolvePath($path)); + } + + public function create($path, $content) + { + $resolvedPath = $this->resolvePath($path); + + $this->ensureDir(dirname($resolvedPath)); + + try { + $stream = fopen($resolvedPath, 'x'); + } catch (ErrorException $e) { + throw new AlreadyExistsException('Couldn\'t create the file "%s": %s', $path, $e); + } + + try { + fclose($stream); + chmod($resolvedPath, 0664); + file_put_contents($resolvedPath, $content); + } catch (ErrorException $e) { + throw new NotWritableError('Couldn\'t create the file "%s": %s', $path, $e); + } + } + + public function read($path) + { + $resolvedPath = $this->resolvePath($path, true); + + try { + return file_get_contents($resolvedPath); + } catch (ErrorException $e) { + throw new NotReadableError('Couldn\'t read the file "%s": %s', $path, $e); + } + } + + public function update($path, $content) + { + $resolvedPath = $this->resolvePath($path, true); + + try { + file_put_contents($resolvedPath, $content); + } catch (ErrorException $e) { + throw new NotWritableError('Couldn\'t update the file "%s": %s', $path, $e); + } + } + + public function delete($path) + { + $resolvedPath = $this->resolvePath($path, true); + + try { + unlink($resolvedPath); + } catch (ErrorException $e) { + throw new NotWritableError('Couldn\'t delete the file "%s": %s', $path, $e); + } + } + + public function resolvePath($path, $assertExistence = false) + { + if ($assertExistence && ! $this->has($path)) { + throw new NotFoundError('No such file: "%s"', $path); + } + + $steps = preg_split('~/~', $path, -1, PREG_SPLIT_NO_EMPTY); + for ($i = 0; $i < count($steps);) { + if ($steps[$i] === '.') { + array_splice($steps, $i, 1); + } elseif ($steps[$i] === '..' && $i > 0 && $steps[$i - 1] !== '..') { + array_splice($steps, $i - 1, 2); + --$i; + } else { + ++$i; + } + } + + if ($steps[0] === '..') { + throw new InvalidArgumentException('Paths above the base directory are not allowed'); + } + + return $this->baseDir . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $steps); + } + + /** + * Ensure that the given directory exists + * + * @param string $dir + * + * @throws NotWritableError + */ + protected function ensureDir($dir) + { + if (! is_dir($dir)) { + $this->ensureDir(dirname($dir)); + + try { + mkdir($dir, 02770); + } catch (ErrorException $e) { + throw new NotWritableError('Couldn\'t create the directory "%s": %s', $dir, $e); + } + } + } +} diff --git a/library/Icinga/File/Storage/StorageInterface.php b/library/Icinga/File/Storage/StorageInterface.php new file mode 100644 index 0000000..f416b00 --- /dev/null +++ b/library/Icinga/File/Storage/StorageInterface.php @@ -0,0 +1,94 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\File\Storage; + +use Icinga\Exception\AlreadyExistsException; +use Icinga\Exception\NotFoundError; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; +use IteratorAggregate; +use Traversable; + +interface StorageInterface extends IteratorAggregate +{ + /** + * Iterate over all existing files' paths + * + * @return Traversable + * + * @throws NotReadableError If the file list can't be read + */ + public function getIterator(): Traversable; + + /** + * Return whether the given file exists + * + * @param string $path + * + * @return bool + */ + public function has($path); + + /** + * Create the given file with the given content + * + * @param string $path + * @param mixed $content + * + * @return $this + * + * @throws AlreadyExistsException If the file already exists + * @throws NotWritableError If the file can't be written to + */ + public function create($path, $content); + + /** + * Load the content of the given file + * + * @param string $path + * + * @return mixed + * + * @throws NotFoundError If the file can't be found + * @throws NotReadableError If the file can't be read + */ + public function read($path); + + /** + * Overwrite the given file with the given content + * + * @param string $path + * @param mixed $content + * + * @return $this + * + * @throws NotFoundError If the file can't be found + * @throws NotWritableError If the file can't be written to + */ + public function update($path, $content); + + /** + * Delete the given file + * + * @param string $path + * + * @return $this + * + * @throws NotFoundError If the file can't be found + * @throws NotWritableError If the file can't be deleted + */ + public function delete($path); + + /** + * Get the absolute path to the given file + * + * @param string $path + * @param bool $assertExistence Whether to require that the given file exists + * + * @return string + * + * @throws NotFoundError If the file has to exist, but can't be found + */ + public function resolvePath($path, $assertExistence = false); +} diff --git a/library/Icinga/File/Storage/TemporaryLocalFileStorage.php b/library/Icinga/File/Storage/TemporaryLocalFileStorage.php new file mode 100644 index 0000000..faf91f5 --- /dev/null +++ b/library/Icinga/File/Storage/TemporaryLocalFileStorage.php @@ -0,0 +1,59 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\File\Storage; + +use ErrorException; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; + +/** + * Stores files in a temporary directory + */ +class TemporaryLocalFileStorage extends LocalFileStorage +{ + /** + * Constructor + */ + public function __construct() + { + $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(); + mkdir($path, 0700); + + parent::__construct($path); + } + + /** + * Destructor + */ + public function __destruct() + { + // Some classes may have cleaned up the tmp file, so we need to check this + // beforehand to prevent an unexpected crash. + if (! @realpath($this->baseDir)) { + return; + } + + $directoryIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $this->baseDir, + RecursiveDirectoryIterator::CURRENT_AS_FILEINFO + | RecursiveDirectoryIterator::KEY_AS_PATHNAME + | RecursiveDirectoryIterator::SKIP_DOTS + ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($directoryIterator as $path => $entry) { + /** @var \SplFileInfo $entry */ + + if ($entry->isDir() && ! $entry->isLink()) { + rmdir($path); + } else { + unlink($path); + } + } + + rmdir($this->baseDir); + } +} |