mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-25 23:04:51 +02:00
Implement local file storage
This commit is contained in:
parent
2caa07ce9b
commit
2cced5fe13
150
library/Icinga/File/Storage/LocalFileStorage.php
Normal file
150
library/Icinga/File/Storage/LocalFileStorage.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?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 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()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return new LocalFileStorageIterator($this->baseDir);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
library/Icinga/File/Storage/LocalFileStorageIterator.php
Normal file
44
library/Icinga/File/Storage/LocalFileStorageIterator.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\File\Storage;
|
||||||
|
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This class will be removed once we require PHP 5.6
|
||||||
|
*/
|
||||||
|
class LocalFileStorageIterator extends RecursiveIteratorIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $baseDir
|
||||||
|
*/
|
||||||
|
public function __construct($baseDir)
|
||||||
|
{
|
||||||
|
parent::__construct(new RecursiveDirectoryIterator($baseDir, RecursiveDirectoryIterator::SKIP_DOTS));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
parent::key();
|
||||||
|
return $this->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
/** @var RecursiveDirectoryIterator $innerIterator */
|
||||||
|
$innerIterator = $this->getInnerIterator();
|
||||||
|
|
||||||
|
/** @var \SplFileInfo $current */
|
||||||
|
$current = parent::current();
|
||||||
|
|
||||||
|
$subPath = $innerIterator->getSubPath();
|
||||||
|
|
||||||
|
return $subPath === ''
|
||||||
|
? $current->getFilename()
|
||||||
|
: str_replace(DIRECTORY_SEPARATOR, '/', $subPath) . '/' . $current->getFilename();
|
||||||
|
}
|
||||||
|
}
|
94
library/Icinga/File/Storage/StorageInterface.php
Normal file
94
library/Icinga/File/Storage/StorageInterface.php
Normal file
@ -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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
53
library/Icinga/File/Storage/TemporaryLocalFileStorage.php
Normal file
53
library/Icinga/File/Storage/TemporaryLocalFileStorage.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?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()
|
||||||
|
{
|
||||||
|
$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()) {
|
||||||
|
rmdir($path);
|
||||||
|
} else {
|
||||||
|
unlink($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rmdir($this->baseDir);
|
||||||
|
}
|
||||||
|
}
|
212
test/php/library/Icinga/File/Storage/LocalFileStorageTest.php
Normal file
212
test/php/library/Icinga/File/Storage/LocalFileStorageTest.php
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Tests\Icinga\File\Storage;
|
||||||
|
|
||||||
|
use ErrorException;
|
||||||
|
use Exception;
|
||||||
|
use Icinga\File\Storage\LocalFileStorage;
|
||||||
|
use Icinga\File\Storage\TemporaryLocalFileStorage;
|
||||||
|
use Icinga\Test\BaseTestCase;
|
||||||
|
|
||||||
|
class LocalFileStorageTest extends BaseTestCase
|
||||||
|
{
|
||||||
|
public function __construct($name = null, array $data = array(), $dataName = '')
|
||||||
|
{
|
||||||
|
parent::__construct($name, $data, $dataName);
|
||||||
|
|
||||||
|
error_reporting(E_ALL | E_STRICT);
|
||||||
|
|
||||||
|
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
||||||
|
if (error_reporting() === 0) {
|
||||||
|
// Error was suppressed with the @-operator
|
||||||
|
return false; // Continue with the normal error handler
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($errno) {
|
||||||
|
case E_NOTICE:
|
||||||
|
case E_WARNING:
|
||||||
|
case E_STRICT:
|
||||||
|
case E_RECOVERABLE_ERROR:
|
||||||
|
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Continue with the normal error handler
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetIterator()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
static::assertSame(array('foobar'), array_values(iterator_to_array($lfs->getIterator())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotReadableError
|
||||||
|
*/
|
||||||
|
public function testGetIteratorThrowsNotReadableError()
|
||||||
|
{
|
||||||
|
$lfs = new LocalFileStorage('/notreadabledirectory');
|
||||||
|
$lfs->getIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHas()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
static::assertFalse($lfs->has('foobar'));
|
||||||
|
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
static::assertTrue($lfs->has('foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foo/bar', 'Hello world!');
|
||||||
|
static::assertSame('Hello world!', $lfs->read('foo/bar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\AlreadyExistsException
|
||||||
|
*/
|
||||||
|
public function testCreateThrowsAlreadyExistsException()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotWritableError
|
||||||
|
*/
|
||||||
|
public function testCreateThrowsNotWritableError()
|
||||||
|
{
|
||||||
|
$lfs = new LocalFileStorage('/notwritabledirectory');
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRead()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
static::assertSame('Hello world!', $lfs->read('foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotFoundError
|
||||||
|
*/
|
||||||
|
public function testReadThrowsNotFoundError()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->read('foobar');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotReadableError
|
||||||
|
*/
|
||||||
|
public function testReadThrowsNotReadableError()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
chmod($lfs->resolvePath('foobar'), 0);
|
||||||
|
$lfs->read('foobar');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdate()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
$lfs->update('foobar', 'Hello universe!');
|
||||||
|
static::assertSame('Hello universe!', $lfs->read('foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotFoundError
|
||||||
|
*/
|
||||||
|
public function testUpdateThrowsNotFoundError()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->update('foobar', 'Hello universe!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotWritableError
|
||||||
|
*/
|
||||||
|
public function testUpdateThrowsNotWritableError()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
chmod($lfs->resolvePath('foobar'), 0);
|
||||||
|
$lfs->update('foobar', 'Hello universe!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
$lfs->delete('foobar');
|
||||||
|
static::assertFalse($lfs->has('foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotFoundError
|
||||||
|
*/
|
||||||
|
public function testDeleteThrowsNotFoundError()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->delete('foobar');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotWritableError
|
||||||
|
*/
|
||||||
|
public function testDeleteThrowsNotWritableError()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
|
||||||
|
$baseDir = dirname($lfs->resolvePath('foobar'));
|
||||||
|
chmod($baseDir, 0500);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$lfs->delete('foobar');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
chmod($baseDir, 0700);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
chmod($baseDir, 0700);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolvePath()
|
||||||
|
{
|
||||||
|
$lfs = new LocalFileStorage('/notreadabledirectory');
|
||||||
|
static::assertSame('/notreadabledirectory/foobar', $lfs->resolvePath('./notRelevant/../foobar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolvePathAssertExistence()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->create('foobar', 'Hello world!');
|
||||||
|
$lfs->resolvePath('./notRelevant/../foobar', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\NotFoundError
|
||||||
|
*/
|
||||||
|
public function testResolvePathThrowsNotFoundError()
|
||||||
|
{
|
||||||
|
$lfs = new TemporaryLocalFileStorage();
|
||||||
|
$lfs->resolvePath('foobar', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testResolvePathThrowsInvalidArgumentException()
|
||||||
|
{
|
||||||
|
$lfs = new LocalFileStorage('/notreadabledirectory');
|
||||||
|
$lfs->resolvePath('../foobar');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user