Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
24 / 24 |
CRAP | |
100.00% |
306 / 306 |
Wrapper | |
100.00% |
1 / 1 |
|
100.00% |
24 / 24 |
95 | |
100.00% |
306 / 306 |
getStatArray() | |
100.00% |
1 / 1 |
1 | |
100.00% |
14 / 14 |
|||
stripScheme($path) | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
getContainerFromContext($path) | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
stream_tell() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
stream_close() | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
stream_open($path, $mode, $options, &$openedPath) | |
100.00% |
1 / 1 |
22 | |
100.00% |
3 / 3 |
|||
anonymous function () use ($path, $options) | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
stream_write($data) | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
stream_stat() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
url_stat($path) | |
100.00% |
1 / 1 |
2 | |
100.00% |
12 / 12 |
|||
stream_read($bytes) | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
stream_eof() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
mkdir($path, $mode, $options) | |
100.00% |
1 / 1 |
7 | |
100.00% |
22 / 22 |
|||
stream_metadata($path, $option, $value) | |
100.00% |
1 / 1 |
24 | |
100.00% |
78 / 78 |
|||
stream_seek($offset, $whence = SEEK_SET) | |
100.00% |
1 / 1 |
4 | |
100.00% |
10 / 10 |
|||
stream_truncate($newSize) | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
rename($oldname, $newname) | |
100.00% |
1 / 1 |
3 | |
100.00% |
15 / 15 |
|||
unlink($path) | |
100.00% |
1 / 1 |
4 | |
100.00% |
21 / 21 |
|||
rmdir($path) | |
100.00% |
1 / 1 |
5 | |
100.00% |
26 / 26 |
|||
dir_opendir($path) | |
100.00% |
1 / 1 |
4 | |
100.00% |
16 / 16 |
|||
dir_closedir() | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
dir_readdir() | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
dir_rewinddir() | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
stream_lock($operation) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
<?php | |
/* | |
* This file is part of the php-vfs package. | |
* | |
* (c) Michael Donat <michael.donat@me.com> | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
*/ | |
namespace VirtualFileSystem; | |
use VirtualFileSystem\Structure\Directory; | |
use VirtualFileSystem\Structure\File; | |
use VirtualFileSystem\Structure\Link; | |
use VirtualFileSystem\Structure\Root; | |
use VirtualFileSystem\Wrapper\DirectoryHandler; | |
use VirtualFileSystem\Wrapper\FileHandler; | |
/** | |
* Stream wrapper class. This is the class that PHP uses as the stream operations handler. | |
* | |
* @see http://php.net/streamwrapper for informal protocol description | |
* | |
* @author Michael Donat <michael.donat@me.com> | |
* @package php-vfs | |
* @api | |
*/ | |
class Wrapper | |
{ | |
public $context; | |
/** | |
* @var FileHandler | |
*/ | |
protected $currentlyOpenedFile; | |
/** | |
* @var DirectoryHandler | |
*/ | |
protected $currentlyOpenedDir; | |
/** | |
* Returns default expectation for stat() function. | |
* | |
* @see http://php.net/stat | |
* | |
* @return array | |
*/ | |
protected function getStatArray() | |
{ | |
$assoc = array( | |
'dev' => 0, | |
'ino' => 0, | |
'mode' => 0, | |
'nlink' => 0, | |
'uid' => 0, | |
'gid' => 0, | |
'rdev' => 0, | |
'size' => 123, | |
'atime' => 0, | |
'mtime' => 0, | |
'ctime' => 0, | |
'blksize' => -1, | |
'blocks' => -1 | |
); | |
return array_merge(array_values($assoc), $assoc); | |
} | |
/** | |
* Returns path stripped of url scheme (http://, ftp://, test:// etc.) | |
* | |
* @param string $path | |
* | |
* @return string | |
*/ | |
public function stripScheme($path) | |
{ | |
$scheme = preg_split('#://#', $path, 2); | |
$scheme = end($scheme); | |
return '/'.ltrim($scheme, '/'); | |
} | |
/** | |
* Returns Container object fished form default_context_options by scheme. | |
* | |
* @param $path | |
* | |
* @return Container | |
*/ | |
public function getContainerFromContext($path) | |
{ | |
$scheme = current(preg_split('#://#', $path)); | |
$options = stream_context_get_options(stream_context_get_default()); | |
return $options[$scheme]['Container']; | |
} | |
/** | |
* @see http://php.net/streamwrapper.stream-tell | |
* | |
* @return int | |
*/ | |
public function stream_tell() | |
{ | |
return $this->currentlyOpenedFile->position(); | |
} | |
/** | |
* @see http://php.net/streamwrapper.stream-close | |
* | |
* @return void | |
*/ | |
public function stream_close() | |
{ | |
$this->currentlyOpenedFile = null; | |
} | |
/** | |
* Opens stream in selected mode. | |
* | |
* @see http://php.net/streamwrapper.stream-open | |
* | |
* @param string $path | |
* @param int $mode | |
* @param int $options | |
* | |
* @param $openedPath | |
* @throws NotDirectoryException | |
* @throws NotFoundException | |
* @return bool | |
*/ | |
public function stream_open($path, $mode, $options, &$openedPath) | |
{ | |
$container = $this->getContainerFromContext($path); | |
$path = $this->stripScheme($path); | |
$mode = str_split(str_replace('b', '', $mode)); | |
$accessDeniedError = function () use ($path, $options) { | |
if ($options & STREAM_REPORT_ERRORS) { | |
trigger_error(sprintf('fopen(%s): failed to open stream: Permission denied', $path), E_USER_WARNING); | |
} | |
return false; | |
}; | |
$appendMode = in_array('a', $mode); | |
$readMode = in_array('r', $mode); | |
$writeMode = in_array('w', $mode); | |
$extended = in_array('+', $mode); | |
if (!$container->hasNodeAt($path)) { | |
if ($readMode or !$container->hasNodeAt(dirname($path))) { | |
if ($options & STREAM_REPORT_ERRORS) { | |
trigger_error(sprintf('%s: failed to open stream.', $path), E_USER_WARNING); | |
} | |
return false; | |
} | |
$parent = $container->directoryAt(dirname($path)); | |
$permissionHelper = $container->getPermissionHelper($parent); | |
if (!$permissionHelper->isWritable()) { | |
return $accessDeniedError(); | |
} | |
$parent->addFile($container->factory()->getFile(basename($path))); | |
} | |
$file = $container->nodeAt($path); | |
if ($file instanceof Link) { | |
$file = $file->getDestination(); | |
} | |
if (($extended || $writeMode || $appendMode) && $file instanceof Directory) { | |
if ($options & STREAM_REPORT_ERRORS) { | |
trigger_error(sprintf('fopen(%s): failed to open stream: Is a directory', $path), E_USER_WARNING); | |
} | |
return false; | |
} | |
if ($file instanceof Directory) { | |
$dir = $file; | |
$file = $container->factory()->getFile('tmp'); | |
$file->chmod($dir->mode()); | |
$file->chown($dir->user()); | |
$file->chgrp($dir->group()); | |
} | |
$permissionHelper = $container->getPermissionHelper($file); | |
$this->currentlyOpenedFile = new FileHandler(); | |
$this->currentlyOpenedFile->setFile($file); | |
if ($extended) { | |
if (!$permissionHelper->isReadable() or !$permissionHelper->isWritable()) { | |
return $accessDeniedError(); | |
} | |
$this->currentlyOpenedFile->setReadWriteMode(); | |
} elseif ($readMode) { | |
if (!$permissionHelper->isReadable()) { | |
return $accessDeniedError(); | |
} | |
$this->currentlyOpenedFile->setReadOnlyMode(); | |
} else { // a or w are for write only | |
if (!$permissionHelper->isWritable()) { | |
return $accessDeniedError(); | |
} | |
$this->currentlyOpenedFile->setWriteOnlyMode(); | |
} | |
if ($appendMode) { | |
$this->currentlyOpenedFile->seekToEnd(); | |
} elseif ($writeMode) { | |
$this->currentlyOpenedFile->truncate(); | |
clearstatcache(); | |
} | |
$openedPath = $file->path(); | |
return true; | |
} | |
/** | |
* Writes data to stream. | |
* | |
* @see http://php.net/streamwrapper.stream-write | |
* | |
* @param $data | |
* | |
* @return int | |
*/ | |
public function stream_write($data) | |
{ | |
if (!$this->currentlyOpenedFile->isOpenedForWriting()) { | |
return false; | |
} | |
//file access time changes so stat cache needs to be cleared | |
$written = $this->currentlyOpenedFile->write($data); | |
clearstatcache(); | |
return $written; | |
} | |
/** | |
* Returns stat data for file inclusion. Currently disabled. | |
* | |
* @see http://php.net/streamwrapper.stream-stat | |
* | |
* @return bool | |
*/ | |
public function stream_stat() | |
{ | |
return true; | |
} | |
/** | |
* Returns file stat information | |
* | |
* @see http://php.net/stat | |
* | |
* @param string $path | |
* | |
* @return array|bool | |
*/ | |
public function url_stat($path) | |
{ | |
try { | |
$file = $this->getContainerFromContext($path)->nodeAt($this->stripScheme($path)); | |
return array_merge($this->getStatArray(), array( | |
'mode' => $file->mode(), | |
'uid' => $file->user(), | |
'gid' => $file->group(), | |
'atime' => $file->atime(), | |
'mtime' => $file->mtime(), | |
'ctime' => $file->ctime(), | |
'size' => $file->size() | |
)); | |
} catch (NotFoundException $e) { | |
return false; | |
} | |
} | |
/** | |
* Reads and returns $bytes amount of bytes from stream. | |
* | |
* @see http://php.net/streamwrapper.stream-read | |
* | |
* @param int $bytes | |
* | |
* @return string | |
*/ | |
public function stream_read($bytes) | |
{ | |
if (!$this->currentlyOpenedFile->isOpenedForReading()) { | |
return null; | |
} | |
$data = $this->currentlyOpenedFile->read($bytes); | |
//file access time changes so stat cache needs to be cleared | |
clearstatcache(); | |
return $data; | |
} | |
/** | |
* Checks whether pointer has reached EOF. | |
* | |
* @see http://php.net/streamwrapper.stream-eof | |
* | |
* @return bool | |
*/ | |
public function stream_eof() | |
{ | |
return $this->currentlyOpenedFile->atEof(); | |
} | |
/** | |
* Called in response to mkdir to create directory. | |
* | |
* @see http://php.net/streamwrapper.mkdir | |
* | |
* @param string $path | |
* @param int $mode | |
* @param int $options | |
* | |
* @return bool | |
*/ | |
public function mkdir($path, $mode, $options) | |
{ | |
$container = $this->getContainerFromContext($path); | |
$path = $this->stripScheme($path); | |
$recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE); | |
try { | |
//need to check all parents for permissions | |
$parentPath = $path; | |
while ($parentPath = dirname($parentPath)) { | |
try { | |
$parent = $container->nodeAt($parentPath); | |
$permissionHelper = $container->getPermissionHelper($parent); | |
if (!$permissionHelper->isWritable()) { | |
trigger_error(sprintf('mkdir: %s: Permission denied', dirname($path)), E_USER_WARNING); | |
return false; | |
} | |
if ($parent instanceof Root) { | |
break; | |
} | |
} catch (NotFoundException $e) { | |
break; //will sort missing parent below | |
} | |
} | |
$container->createDir($path, $recursive, $mode); | |
} catch (FileExistsException $e) { | |
trigger_error($e->getMessage(), E_USER_WARNING); | |
return false; | |
} catch (NotFoundException $e) { | |
trigger_error(sprintf('mkdir: %s: No such file or directory', dirname($path)), E_USER_WARNING); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Called in response to chown/chgrp/touch/chmod etc. | |
* | |
* @see http://php.net/streamwrapper.stream-metadata | |
* | |
* @param string $path | |
* @param int $option | |
* @param integer $value | |
* | |
* @return bool | |
*/ | |
public function stream_metadata($path, $option, $value) | |
{ | |
$container = $this->getContainerFromContext($path); | |
$strippedPath = $this->stripScheme($path); | |
try { | |
if ($option == STREAM_META_TOUCH) { | |
if (!$container->hasNodeAt($strippedPath)) { | |
try { | |
$container->createFile($strippedPath); | |
} catch (NotFoundException $e) { | |
trigger_error( | |
sprintf('touch: %s: No such file or directory.', $strippedPath), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
} | |
$file = $container->nodeAt($strippedPath); | |
$permissionHelper = $container->getPermissionHelper($file); | |
if (!$permissionHelper->userIsOwner() && !$permissionHelper->isWritable()) { | |
trigger_error( | |
sprintf('touch: %s: Permission denied', $strippedPath), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$file->setAccessTime(time()); | |
$file->setModificationTime(time()); | |
$file->setChangeTime(time()); | |
clearstatcache(true, $path); | |
return true; | |
} | |
$node = $container->nodeAt($strippedPath); | |
$permissionHelper = $container->getPermissionHelper($node); | |
switch ($option) { | |
case STREAM_META_ACCESS: | |
if ($node instanceof Link) { | |
$node = $node->getDestination(); | |
$permissionHelper = $container->getPermissionHelper($node); | |
} | |
if (!$permissionHelper->userIsOwner()) { | |
trigger_error( | |
sprintf('chmod: %s: Permission denied', $strippedPath), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$node->chmod($value); | |
$node->setChangeTime(time()); | |
break; | |
case STREAM_META_OWNER_NAME: | |
if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { | |
trigger_error( | |
sprintf('chown: %s: Permission denied', $strippedPath), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$uid = function_exists('posix_getpwnam') ? posix_getpwnam($value)['uid'] : 0; | |
$node->chown($uid); | |
$node->setChangeTime(time()); | |
break; | |
case STREAM_META_OWNER: | |
if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { | |
trigger_error( | |
sprintf('chown: %s: Permission denied', $strippedPath), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$node->chown($value); | |
$node->setChangeTime(time()); | |
break; | |
case STREAM_META_GROUP_NAME: | |
if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { | |
trigger_error( | |
sprintf('chgrp: %s: Permission denied', $strippedPath), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$gid = function_exists('posix_getgrnam') ? posix_getgrnam($value)['gid'] : 0; | |
$node->chgrp($gid); | |
$node->setChangeTime(time()); | |
break; | |
case STREAM_META_GROUP: | |
if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { | |
trigger_error( | |
sprintf('chgrp: %s: Permission denied', $strippedPath), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$node->chgrp($value); | |
$node->setChangeTime(time()); | |
break; | |
} | |
} catch (NotFoundException $e) { | |
return false; | |
} | |
clearstatcache(true, $path); | |
return true; | |
} | |
/** | |
* Sets file pointer to specified position | |
* | |
* @param int $offset | |
* @param int $whence | |
* | |
* @return bool | |
*/ | |
public function stream_seek($offset, $whence = SEEK_SET) | |
{ | |
switch ($whence) { | |
case SEEK_SET: | |
$this->currentlyOpenedFile->position($offset); | |
break; | |
case SEEK_CUR: | |
$this->currentlyOpenedFile->offsetPosition($offset); | |
break; | |
case SEEK_END: | |
$this->currentlyOpenedFile->seekToEnd(); | |
$this->currentlyOpenedFile->offsetPosition($offset); | |
} | |
return true; | |
} | |
/** | |
* Truncates file to given size | |
* | |
* @param int $newSize | |
* | |
* @return bool | |
*/ | |
public function stream_truncate($newSize) | |
{ | |
$this->currentlyOpenedFile->truncate($newSize); | |
clearstatcache(); | |
return true; | |
} | |
/** | |
* Renames/Moves file | |
* | |
* @param string $oldname | |
* @param string $newname | |
* | |
* @return bool | |
*/ | |
public function rename($oldname, $newname) | |
{ | |
$container = $this->getContainerFromContext($newname); | |
$oldname = $this->stripScheme($oldname); | |
$newname = $this->stripScheme($newname); | |
try { | |
$container->move($oldname, $newname); | |
} catch (NotFoundException $e) { | |
trigger_error( | |
sprintf('mv: rename %s to %s: No such file or directory', $oldname, $newname), | |
E_USER_WARNING | |
); | |
return false; | |
} catch (\RuntimeException $e) { | |
trigger_error( | |
sprintf('mv: rename %s to %s: Not a directory', $oldname, $newname), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Deletes file at given path | |
* | |
* @param int $path | |
* | |
* @return bool | |
*/ | |
public function unlink($path) | |
{ | |
$container = $this->getContainerFromContext($path); | |
try { | |
$path = $this->stripScheme($path); | |
$parent = $container->nodeAt(dirname($path)); | |
$permissionHelper = $container->getPermissionHelper($parent); | |
if (!$permissionHelper->isWritable()) { | |
trigger_error( | |
sprintf('rm: %s: Permission denied', $path), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$container->remove($path = $this->stripScheme($path)); | |
} catch (NotFoundException $e) { | |
trigger_error( | |
sprintf('rm: %s: No such file or directory', $path), | |
E_USER_WARNING | |
); | |
return false; | |
} catch (\RuntimeException $e) { | |
trigger_error( | |
sprintf('rm: %s: is a directory', $path), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Removes directory | |
* | |
* @param string $path | |
* | |
* @return bool | |
*/ | |
public function rmdir($path) | |
{ | |
$container = $this->getContainerFromContext($path); | |
$path = $this->stripScheme($path); | |
try { | |
$directory = $container->nodeAt($path); | |
if ($directory instanceof File) { | |
trigger_error( | |
sprintf('Warning: rmdir(%s): Not a directory', $path), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$permissionHelper = $container->getPermissionHelper($directory); | |
if (!$permissionHelper->isReadable()) { | |
trigger_error( | |
sprintf('rmdir: %s: Permission denied', $path), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
} catch (NotFoundException $e) { | |
trigger_error( | |
sprintf('Warning: rmdir(%s): No such file or directory', $path), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
if ($directory->size()) { | |
trigger_error( | |
sprintf('Warning: rmdir(%s): Directory not empty', $path), | |
E_USER_WARNING | |
); | |
return false; | |
} | |
$container->remove($path, true); | |
return true; | |
} | |
/** | |
* Opens directory for iteration | |
* | |
* @param string $path | |
* | |
* @return bool | |
*/ | |
public function dir_opendir($path) | |
{ | |
$container = $this->getContainerFromContext($path); | |
$path = $this->stripScheme($path); | |
if (!$container->hasNodeAt($path)) { | |
trigger_error(sprintf('opendir(%s): failed to open dir: No such file or directory', $path), E_USER_WARNING); | |
return false; | |
} | |
try { | |
$dir = $container->directoryAt($path); | |
} catch (NotDirectoryException $e) { | |
trigger_error(sprintf('opendir(%s): failed to open dir: Not a directory', $path), E_USER_WARNING); | |
return false; | |
} | |
$permissionHelper = $container->getPermissionHelper($dir); | |
if (!$permissionHelper->isReadable()) { | |
trigger_error(sprintf('opendir(%s): failed to open dir: Permission denied', $path), E_USER_WARNING); | |
return false; | |
} | |
$this->currentlyOpenedDir = new DirectoryHandler(); | |
$this->currentlyOpenedDir->setDirectory($dir); | |
return true; | |
} | |
/** | |
* Closes opened dir | |
* | |
* @return bool | |
*/ | |
public function dir_closedir() | |
{ | |
if ($this->currentlyOpenedDir) { | |
$this->currentlyOpenedDir = null; | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Returns next file url in directory | |
* | |
* @return null | |
*/ | |
public function dir_readdir() | |
{ | |
$node = $this->currentlyOpenedDir->iterator()->current(); | |
if (!$node) { | |
return false; | |
} | |
$this->currentlyOpenedDir->iterator()->next(); | |
return $node->basename(); | |
} | |
/** | |
* Resets directory iterator | |
*/ | |
public function dir_rewinddir() | |
{ | |
$this->currentlyOpenedDir->iterator()->rewind(); | |
} | |
public function stream_lock($operation) | |
{ | |
return $this->currentlyOpenedFile->lock($this, $operation); | |
} | |
} |