summaryrefslogtreecommitdiffstats
path: root/library/Icinga/File/Storage
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/File/Storage')
-rw-r--r--library/Icinga/File/Storage/LocalFileStorage.php158
-rw-r--r--library/Icinga/File/Storage/StorageInterface.php94
-rw-r--r--library/Icinga/File/Storage/TemporaryLocalFileStorage.php59
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);
+ }
+}