diff --git a/composer.json b/composer.json index 05ef4bba9..e6e3d3a49 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "doctrine/dbal": "2.12.0", "guzzlehttp/guzzle": "6.5.2", "icewind/searchdav": "^2.0.0", - "icewind/streams": "v0.7.1", + "icewind/streams": "v0.7.2", "league/flysystem": "^1.0", "microsoft/azure-storage-blob": "1.5.0", "nextcloud/lognormalizer": "^1.0", diff --git a/composer/InstalledVersions.php b/composer/InstalledVersions.php index 4617d9511..b600866ca 100644 --- a/composer/InstalledVersions.php +++ b/composer/InstalledVersions.php @@ -29,7 +29,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '455f47df98504ef1336cd9e4c5b59c60903ebb22', + 'reference' => 'aea1df9dcc0ae1b774e15edf07a470198b952e45', 'name' => 'nextcloud/3rdparty', ), 'versions' => @@ -284,7 +284,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '455f47df98504ef1336cd9e4c5b59c60903ebb22', + 'reference' => 'aea1df9dcc0ae1b774e15edf07a470198b952e45', ), 'nextcloud/lognormalizer' => array ( diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index 0cff536ab..8f7d624e8 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -1320,14 +1320,19 @@ 'Icewind\\Streams\\DirectoryFilter' => $vendorDir . '/icewind/streams/src/DirectoryFilter.php', 'Icewind\\Streams\\DirectoryWrapper' => $vendorDir . '/icewind/streams/src/DirectoryWrapper.php', 'Icewind\\Streams\\File' => $vendorDir . '/icewind/streams/src/File.php', + 'Icewind\\Streams\\HashWrapper' => $vendorDir . '/icewind/streams/src/HashWrapper.php', 'Icewind\\Streams\\IteratorDirectory' => $vendorDir . '/icewind/streams/src/IteratorDirectory.php', 'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php', 'Icewind\\Streams\\Path' => $vendorDir . '/icewind/streams/src/Path.php', 'Icewind\\Streams\\PathWrapper' => $vendorDir . '/icewind/streams/src/PathWrapper.php', + 'Icewind\\Streams\\ReadHashWrapper' => $vendorDir . '/icewind/streams/src/ReadHashWrapper.php', 'Icewind\\Streams\\RetryWrapper' => $vendorDir . '/icewind/streams/src/RetryWrapper.php', 'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php', 'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php', + 'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallback.php', 'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.php', + 'Icewind\\Streams\\WrapperHandler' => $vendorDir . '/icewind/streams/src/WrapperHandler.php', + 'Icewind\\Streams\\WriteHashWrapper' => $vendorDir . '/icewind/streams/src/WriteHashWrapper.php', 'JmesPath\\AstRuntime' => $vendorDir . '/mtdowling/jmespath.php/src/AstRuntime.php', 'JmesPath\\CompilerRuntime' => $vendorDir . '/mtdowling/jmespath.php/src/CompilerRuntime.php', 'JmesPath\\DebugRuntime' => $vendorDir . '/mtdowling/jmespath.php/src/DebugRuntime.php', diff --git a/composer/autoload_static.php b/composer/autoload_static.php index 154c4692c..39aadc403 100644 --- a/composer/autoload_static.php +++ b/composer/autoload_static.php @@ -1821,14 +1821,19 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Icewind\\Streams\\DirectoryFilter' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryFilter.php', 'Icewind\\Streams\\DirectoryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryWrapper.php', 'Icewind\\Streams\\File' => __DIR__ . '/..' . '/icewind/streams/src/File.php', + 'Icewind\\Streams\\HashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/HashWrapper.php', 'Icewind\\Streams\\IteratorDirectory' => __DIR__ . '/..' . '/icewind/streams/src/IteratorDirectory.php', 'Icewind\\Streams\\NullWrapper' => __DIR__ . '/..' . '/icewind/streams/src/NullWrapper.php', 'Icewind\\Streams\\Path' => __DIR__ . '/..' . '/icewind/streams/src/Path.php', 'Icewind\\Streams\\PathWrapper' => __DIR__ . '/..' . '/icewind/streams/src/PathWrapper.php', + 'Icewind\\Streams\\ReadHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/ReadHashWrapper.php', 'Icewind\\Streams\\RetryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/RetryWrapper.php', 'Icewind\\Streams\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/src/SeekableWrapper.php', 'Icewind\\Streams\\Url' => __DIR__ . '/..' . '/icewind/streams/src/Url.php', + 'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallback.php', 'Icewind\\Streams\\Wrapper' => __DIR__ . '/..' . '/icewind/streams/src/Wrapper.php', + 'Icewind\\Streams\\WrapperHandler' => __DIR__ . '/..' . '/icewind/streams/src/WrapperHandler.php', + 'Icewind\\Streams\\WriteHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/WriteHashWrapper.php', 'JmesPath\\AstRuntime' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/AstRuntime.php', 'JmesPath\\CompilerRuntime' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/CompilerRuntime.php', 'JmesPath\\DebugRuntime' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/DebugRuntime.php', diff --git a/composer/installed.php b/composer/installed.php index d7d34c8b7..7882351b1 100644 --- a/composer/installed.php +++ b/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => '455f47df98504ef1336cd9e4c5b59c60903ebb22', + 'reference' => 'aea1df9dcc0ae1b774e15edf07a470198b952e45', 'name' => 'nextcloud/3rdparty', ), 'versions' => @@ -261,7 +261,7 @@ 'aliases' => array ( ), - 'reference' => '455f47df98504ef1336cd9e4c5b59c60903ebb22', + 'reference' => 'aea1df9dcc0ae1b774e15edf07a470198b952e45', ), 'nextcloud/lognormalizer' => array ( diff --git a/icewind/streams/.gitignore b/icewind/streams/.gitignore index 4f389129e..e2a9a5109 100644 --- a/icewind/streams/.gitignore +++ b/icewind/streams/.gitignore @@ -1,3 +1,5 @@ .idea vendor composer.lock +build +example.php diff --git a/icewind/streams/src/CallbackWrapper.php b/icewind/streams/src/CallbackWrapper.php index 67f9110d1..be62e3f66 100644 --- a/icewind/streams/src/CallbackWrapper.php +++ b/icewind/streams/src/CallbackWrapper.php @@ -53,30 +53,28 @@ class CallbackWrapper extends Wrapper { * Wraps a stream with the provided callbacks * * @param resource $source - * @param callable $read (optional) - * @param callable $write (optional) - * @param callable $close (optional) - * @param callable $readDir (optional) - * @return resource + * @param callable|null $read (optional) + * @param callable|null $write (optional) + * @param callable|null $close (optional) + * @param callable|null $readDir (optional) + * @param callable|null $preClose (optional) + * @return resource|bool * - * @throws \BadMethodCallException */ public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null, $preClose = null) { - $context = stream_context_create(array( - 'callback' => array( - 'source' => $source, - 'read' => $read, - 'write' => $write, - 'close' => $close, - 'readDir' => $readDir, - 'preClose' => $preClose, - ) - )); - return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CallbackWrapper'); + $context = [ + 'source' => $source, + 'read' => $read, + 'write' => $write, + 'close' => $close, + 'readDir' => $readDir, + 'preClose' => $preClose, + ]; + return self::wrapSource($source, $context); } protected function open() { - $context = $this->loadContext('callback'); + $context = $this->loadContext(); $this->readCallback = $context['read']; $this->writeCallback = $context['write']; @@ -112,7 +110,7 @@ public function stream_write($data) { public function stream_close() { if (is_callable($this->preCloseCallback)) { - call_user_func($this->preCloseCallback, $this->loadContext('callback')['source']); + call_user_func($this->preCloseCallback, $this->source); // prevent further calls by potential PHP 7 GC ghosts $this->preCloseCallback = null; } diff --git a/icewind/streams/src/CountWrapper.php b/icewind/streams/src/CountWrapper.php index 8b86ab918..d02434012 100644 --- a/icewind/streams/src/CountWrapper.php +++ b/icewind/streams/src/CountWrapper.php @@ -55,7 +55,7 @@ class CountWrapper extends Wrapper { * * @param resource $source * @param callable $callback - * @return resource + * @return resource|bool * * @throws \BadMethodCallException */ @@ -63,17 +63,14 @@ public static function wrap($source, $callback) { if (!is_callable($callback)) { throw new \InvalidArgumentException('Invalid or missing callback'); } - $context = stream_context_create(array( - 'count' => array( - 'source' => $source, - 'callback' => $callback - ) - )); - return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CountWrapper'); + return self::wrapSource($source, [ + 'source' => $source, + 'callback' => $callback + ]); } protected function open() { - $context = $this->loadContext('count'); + $context = $this->loadContext(); $this->callback = $context['callback']; return true; } diff --git a/icewind/streams/src/Directory.php b/icewind/streams/src/Directory.php index c80a87838..912be76ac 100644 --- a/icewind/streams/src/Directory.php +++ b/icewind/streams/src/Directory.php @@ -19,7 +19,7 @@ interface Directory { public function dir_opendir($path, $options); /** - * @return string + * @return string|bool */ public function dir_readdir(); diff --git a/icewind/streams/src/DirectoryFilter.php b/icewind/streams/src/DirectoryFilter.php index 4b8696990..80b27e8ba 100644 --- a/icewind/streams/src/DirectoryFilter.php +++ b/icewind/streams/src/DirectoryFilter.php @@ -25,7 +25,7 @@ class DirectoryFilter extends DirectoryWrapper { * @return bool */ public function dir_opendir($path, $options) { - $context = $this->loadContext('filter'); + $context = $this->loadContext(); $this->filter = $context['filter']; return true; } @@ -36,7 +36,7 @@ public function dir_opendir($path, $options) { public function dir_readdir() { $file = readdir($this->source); $filter = $this->filter; - // keep reading untill we have an accepted entry or we're at the end of the folder + // keep reading until we have an accepted entry or we're at the end of the folder while ($file !== false && $filter($file) === false) { $file = readdir($this->source); } @@ -46,15 +46,12 @@ public function dir_readdir() { /** * @param resource $source * @param callable $filter - * @return resource + * @return resource|bool */ public static function wrap($source, callable $filter) { - $options = array( - 'filter' => array( - 'source' => $source, - 'filter' => $filter - ) - ); - return self::wrapWithOptions($options, '\Icewind\Streams\DirectoryFilter'); + return self::wrapSource($source, [ + 'source' => $source, + 'filter' => $filter + ]); } } diff --git a/icewind/streams/src/DirectoryWrapper.php b/icewind/streams/src/DirectoryWrapper.php index 63e4805a8..7f2f5c291 100644 --- a/icewind/streams/src/DirectoryWrapper.php +++ b/icewind/streams/src/DirectoryWrapper.php @@ -7,37 +7,9 @@ namespace Icewind\Streams; -class DirectoryWrapper implements Directory { - /** - * @var resource - */ - public $context; - - /** - * @var resource - */ - protected $source; - - /** - * Load the source from the stream context and return the context options - * - * @param string $name - * @return array - * @throws \Exception - */ - protected function loadContext($name) { - $context = stream_context_get_options($this->context); - if (isset($context[$name])) { - $context = $context[$name]; - } else { - throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); - } - if (isset($context['source']) and is_resource($context['source'])) { - $this->source = $context['source']; - } else { - throw new \BadMethodCallException('Invalid context, source not set'); - } - return $context; +class DirectoryWrapper extends Wrapper implements Directory { + public function stream_open($path, $mode, $options, &$opened_path) { + return false; } /** @@ -46,7 +18,7 @@ protected function loadContext($name) { * @return bool */ public function dir_opendir($path, $options) { - $this->loadContext('dir'); + $this->loadContext(); return true; } @@ -72,17 +44,4 @@ public function dir_rewinddir() { rewinddir($this->source); return true; } - - /** - * @param array $options the options for the context to wrap the stream with - * @param string $class - * @return resource - */ - protected static function wrapWithOptions($options, $class) { - $context = stream_context_create($options); - stream_wrapper_register('dirwrapper', $class); - $wrapped = opendir('dirwrapper://', $context); - stream_wrapper_unregister('dirwrapper'); - return $wrapped; - } } diff --git a/icewind/streams/src/HashWrapper.php b/icewind/streams/src/HashWrapper.php new file mode 100644 index 000000000..0628d8bc7 --- /dev/null +++ b/icewind/streams/src/HashWrapper.php @@ -0,0 +1,80 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Icewind\Streams; + + +abstract class HashWrapper extends Wrapper { + + /** + * @var callable + */ + private $callback; + + /** + * @var resource + */ + private $hashContext; + + /** + * Wraps a stream to make it seekable + * + * @param resource $source + * @param string $hash + * @param callable $callback + * @return resource|bool + * + * @throws \BadMethodCallException + */ + public static function wrap($source, $hash, $callback) { + $context = [ + 'hash' => $hash, + 'callback' => $callback, + ]; + return self::wrapSource($source, $context); + } + + public function dir_opendir($path, $options) { + return false; + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $context = $this->loadContext(); + $this->callback = $context['callback']; + $this->hashContext = hash_init($context['hash']); + return true; + } + + protected function updateHash($data) { + hash_update($this->hashContext, $data); + } + + public function stream_close() { + $hash = hash_final($this->hashContext); + if ($this->hashContext !== false && is_callable($this->callback)) { + call_user_func($this->callback, $hash); + } + return parent::stream_close(); + } + +} \ No newline at end of file diff --git a/icewind/streams/src/IteratorDirectory.php b/icewind/streams/src/IteratorDirectory.php index 6dfa42a8b..057c1992f 100644 --- a/icewind/streams/src/IteratorDirectory.php +++ b/icewind/streams/src/IteratorDirectory.php @@ -20,7 +20,7 @@ * * Either 'array' or 'iterator' need to be set, if both are set, 'iterator' takes preference */ -class IteratorDirectory implements Directory { +class IteratorDirectory extends WrapperHandler implements Directory { /** * @var resource */ @@ -36,15 +36,10 @@ class IteratorDirectory implements Directory { * * @param string $name * @return array - * @throws \Exception + * @throws \BadMethodCallException */ - protected function loadContext($name) { - $context = stream_context_get_options($this->context); - if (isset($context[$name])) { - $context = $context[$name]; - } else { - throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); - } + protected function loadContext($name = null) { + $context = parent::loadContext($name); if (isset($context['iterator'])) { $this->iterator = $context['iterator']; } else if (isset($context['array'])) { @@ -61,12 +56,12 @@ protected function loadContext($name) { * @return bool */ public function dir_opendir($path, $options) { - $this->loadContext('dir'); + $this->loadContext(); return true; } /** - * @return string + * @return string|bool */ public function dir_readdir() { if ($this->iterator->valid()) { @@ -97,27 +92,22 @@ public function dir_rewinddir() { * Creates a directory handle from the provided array or iterator * * @param \Iterator | array $source - * @return resource + * @return resource|bool * * @throws \BadMethodCallException */ public static function wrap($source) { if ($source instanceof \Iterator) { - $context = stream_context_create(array( - 'dir' => array( - 'iterator' => $source) - )); + $options = [ + 'iterator' => $source + ]; } else if (is_array($source)) { - $context = stream_context_create(array( - 'dir' => array( - 'array' => $source) - )); + $options = [ + 'array' => $source + ]; } else { throw new \BadMethodCallException('$source should be an Iterator or array'); } - stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); - $wrapped = opendir('iterator://', $context); - stream_wrapper_unregister('iterator'); - return $wrapped; + return self::wrapSource(self::NO_SOURCE_DIR, $options); } } diff --git a/icewind/streams/src/NullWrapper.php b/icewind/streams/src/NullWrapper.php index b6c71d98f..92aef2c7a 100644 --- a/icewind/streams/src/NullWrapper.php +++ b/icewind/streams/src/NullWrapper.php @@ -11,29 +11,17 @@ * Stream wrapper that does nothing, used for tests */ class NullWrapper extends Wrapper { - /** - * Wraps a stream with the provided callbacks - * - * @param resource $source - * @return resource - * - * @throws \BadMethodCallException - */ public static function wrap($source) { - $context = stream_context_create(array( - 'null' => array( - 'source' => $source) - )); - return Wrapper::wrapSource($source, $context, 'null', '\Icewind\Streams\NullWrapper'); + return self::wrapSource($source); } public function stream_open($path, $mode, $options, &$opened_path) { - $this->loadContext('null'); + $this->loadContext(); return true; } public function dir_opendir($path, $options) { - $this->loadContext('null'); + $this->loadContext(); return true; } } diff --git a/icewind/streams/src/Path.php b/icewind/streams/src/Path.php index bef9fd5f6..42d74a2ac 100644 --- a/icewind/streams/src/Path.php +++ b/icewind/streams/src/Path.php @@ -38,7 +38,7 @@ class Path { * @param string $class * @param array $contextOptions */ - public function __construct($class, $contextOptions = array()) { + public function __construct($class, $contextOptions = []) { $this->class = $class; $this->contextOptions = $contextOptions; } @@ -75,7 +75,7 @@ protected function unregister() { */ protected function appendDefaultContent($values) { if (!is_array(current($values))) { - $values = array($this->getProtocol() => $values); + $values = [$this->getProtocol() => $values]; } $context = stream_context_get_default(); $defaults = stream_context_get_options($context); diff --git a/icewind/streams/src/PathWrapper.php b/icewind/streams/src/PathWrapper.php index 88af7e17b..d9f3014c3 100644 --- a/icewind/streams/src/PathWrapper.php +++ b/icewind/streams/src/PathWrapper.php @@ -16,10 +16,8 @@ class PathWrapper extends NullWrapper { * @return Path|string */ public static function getPath($source) { - return new Path(__CLASS__, [ - 'null' => [ - 'source' => $source - ] + return new Path(NullWrapper::class, [ + NullWrapper::getProtocol() => ['source' => $source] ]); } } diff --git a/icewind/streams/src/ReadHashWrapper.php b/icewind/streams/src/ReadHashWrapper.php new file mode 100644 index 000000000..16cf006cc --- /dev/null +++ b/icewind/streams/src/ReadHashWrapper.php @@ -0,0 +1,40 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Icewind\Streams; + +/** + * Wrapper that calculates the hash on the stream on read + * + * The stream and hash should be passed in when wrapping the stream. + * On close the callback will be called with the calculated checksum. + * + * For supported hashes see: http://php.net/manual/en/function.hash-algos.php + */ +class ReadHashWrapper extends HashWrapper { + public function stream_read($count) { + $data = parent::stream_read($count); + $this->updateHash($data); + return $data; + } +} diff --git a/icewind/streams/src/RetryWrapper.php b/icewind/streams/src/RetryWrapper.php index 8238f19f7..d4727aa96 100644 --- a/icewind/streams/src/RetryWrapper.php +++ b/icewind/streams/src/RetryWrapper.php @@ -11,25 +11,8 @@ * Wrapper that retries reads/writes to remote streams that dont deliver/recieve all requested data at once */ class RetryWrapper extends Wrapper { - - /** - * Wraps a stream with the provided callbacks - * - * @param resource $source - * @return resource - */ public static function wrap($source) { - $context = stream_context_create(array( - 'retry' => array( - 'source' => $source - ) - )); - return Wrapper::wrapSource($source, $context, 'retry', '\Icewind\Streams\RetryWrapper'); - } - - protected function open() { - $this->loadContext('retry'); - return true; + return self::wrapSource($source); } public function dir_opendir($path, $options) { @@ -37,7 +20,8 @@ public function dir_opendir($path, $options) { } public function stream_open($path, $mode, $options, &$opened_path) { - return $this->open(); + $this->loadContext(); + return true; } public function stream_read($count) { diff --git a/icewind/streams/src/SeekableWrapper.php b/icewind/streams/src/SeekableWrapper.php index d41fd73ec..f7abbc514 100644 --- a/icewind/streams/src/SeekableWrapper.php +++ b/icewind/streams/src/SeekableWrapper.php @@ -25,21 +25,8 @@ class SeekableWrapper extends Wrapper { */ protected $cache; - /** - * Wraps a stream to make it seekable - * - * @param resource $source - * @return resource - * - * @throws \BadMethodCallException - */ public static function wrap($source) { - $context = stream_context_create(array( - 'callback' => array( - 'source' => $source - ) - )); - return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\SeekableWrapper'); + return self::wrapSource($source); } public function dir_opendir($path, $options) { @@ -47,8 +34,12 @@ public function dir_opendir($path, $options) { } public function stream_open($path, $mode, $options, &$opened_path) { - $this->loadContext('callback'); - $this->cache = fopen('php://temp', 'w+'); + $this->loadContext(); + $cache = fopen('php://temp', 'w+'); + if ($cache === false) { + return false; + } + $this->cache = $cache; return true; } diff --git a/icewind/streams/src/UrlCallBack.php b/icewind/streams/src/UrlCallback.php similarity index 79% rename from icewind/streams/src/UrlCallBack.php rename to icewind/streams/src/UrlCallback.php index 580bfc6ba..02dca15f9 100644 --- a/icewind/streams/src/UrlCallBack.php +++ b/icewind/streams/src/UrlCallback.php @@ -47,11 +47,10 @@ class UrlCallback extends Wrapper implements Url { * @return \Icewind\Streams\Path * * @throws \BadMethodCallException - * @throws \Exception */ public static function wrap($source, $fopen = null, $opendir = null, $mkdir = null, $rename = null, $rmdir = null, $unlink = null, $stat = null) { - $options = array( + return new Path(static::class, [ 'source' => $source, 'fopen' => $fopen, 'opendir' => $opendir, @@ -60,11 +59,10 @@ public static function wrap($source, $fopen = null, $opendir = null, $mkdir = nu 'rmdir' => $rmdir, 'unlink' => $unlink, 'stat' => $stat - ); - return new Path('\Icewind\Streams\UrlCallBack', $options); + ]); } - protected function loadContext($url) { + protected function loadUrlContext($url) { list($protocol) = explode('://', $url); $options = stream_context_get_options($this->context); return $options[$protocol]; @@ -77,40 +75,48 @@ protected function callCallBack($context, $callback) { } public function stream_open($path, $mode, $options, &$opened_path) { - $context = $this->loadContext($path); + $context = $this->loadUrlContext($path); $this->callCallBack($context, 'fopen'); - $this->setSourceStream(fopen($context['source'], $mode)); + $source = fopen($context['source'], $mode); + if ($source === false) { + return false; + } + $this->setSourceStream($source); return true; } public function dir_opendir($path, $options) { - $context = $this->loadContext($path); + $context = $this->loadUrlContext($path); $this->callCallBack($context, 'opendir'); - $this->setSourceStream(opendir($context['source'])); + $source = opendir($context['source']); + if ($source === false) { + return false; + } + $this->setSourceStream($source); return true; } public function mkdir($path, $mode, $options) { - $context = $this->loadContext($path); + $context = $this->loadUrlContext($path); $this->callCallBack($context, 'mkdir'); - return mkdir($context['source'], $mode, $options & STREAM_MKDIR_RECURSIVE); + return mkdir($context['source'], $mode, ($options & STREAM_MKDIR_RECURSIVE) > 0); } public function rmdir($path, $options) { - $context = $this->loadContext($path); + $context = $this->loadUrlContext($path); $this->callCallBack($context, 'rmdir'); return rmdir($context['source']); } public function rename($source, $target) { - $context = $this->loadContext($source); + $context = $this->loadUrlContext($source); $this->callCallBack($context, 'rename'); list(, $target) = explode('://', $target); return rename($context['source'], $target); } public function unlink($path) { - $context = $this->loadContext($path); + $context = $this->loadUrlContext($path); $this->callCallBack($context, 'unlink'); return unlink($context['source']); } diff --git a/icewind/streams/src/Wrapper.php b/icewind/streams/src/Wrapper.php index babd2c1a0..abadab4dc 100644 --- a/icewind/streams/src/Wrapper.php +++ b/icewind/streams/src/Wrapper.php @@ -12,7 +12,7 @@ * * This wrapper itself doesn't implement any functionality but is just a base class for other wrappers to extend */ -abstract class Wrapper implements File, Directory { +abstract class Wrapper extends WrapperHandler implements File, Directory { /** * @var resource */ @@ -25,44 +25,15 @@ abstract class Wrapper implements File, Directory { */ protected $source; - protected static function wrapSource($source, $context, $protocol, $class) { - if (!is_resource($source)) { - throw new \BadMethodCallException(); - } - try { - stream_wrapper_register($protocol, $class); - if (self::isDirectoryHandle($source)) { - $wrapped = opendir($protocol . '://', $context); - } else { - $wrapped = fopen($protocol . '://', 'r+', false, $context); - } - } catch (\BadMethodCallException $e) { - stream_wrapper_unregister($protocol); - throw $e; - } - stream_wrapper_unregister($protocol); - return $wrapped; - } - - protected static function isDirectoryHandle($resource) { - $meta = stream_get_meta_data($resource); - return $meta['stream_type'] == 'dir'; - } - /** - * Load the source from the stream context and return the context options - * - * @param string $name - * @return array - * @throws \Exception + * @param resource $source */ - protected function loadContext($name) { - $context = stream_context_get_options($this->context); - if (isset($context[$name])) { - $context = $context[$name]; - } else { - throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); - } + protected function setSourceStream($source) { + $this->source = $source; + } + + protected function loadContext($name = null) { + $context = parent::loadContext($name); if (isset($context['source']) and is_resource($context['source'])) { $this->setSourceStream($context['source']); } else { @@ -71,13 +42,6 @@ protected function loadContext($name) { return $context; } - /** - * @param resource $source - */ - protected function setSourceStream($source) { - $this->source = $source; - } - public function stream_seek($offset, $whence = SEEK_SET) { $result = fseek($this->source, $offset, $whence); return $result == 0 ? true : false; diff --git a/icewind/streams/src/WrapperHandler.php b/icewind/streams/src/WrapperHandler.php new file mode 100644 index 000000000..72dc71485 --- /dev/null +++ b/icewind/streams/src/WrapperHandler.php @@ -0,0 +1,112 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Icewind\Streams; + + +class WrapperHandler { + const NO_SOURCE_DIR = 1; + + /** + * get the protocol name that is generated for the class + * @param string|null $class + * @return string + */ + public static function getProtocol($class = null) { + if ($class === null) { + $class = static::class; + } + + $parts = explode('\\', $class); + return strtolower(array_pop($parts)); + } + + private static function buildContext($protocol, $context, $source) { + if (is_array($context)) { + $context['source'] = $source; + return stream_context_create([$protocol => $context]); + } else { + return $context; + } + } + + /** + * @param resource|int $source + * @param resource|array $context + * @param string|null $protocol deprecated, protocol is now automatically generated + * @param string|null $class deprecated, class is now automatically generated + * @return bool|resource + */ + protected static function wrapSource($source, $context = [], $protocol = null, $class = null) { + if ($class === null) { + $class = static::class; + } + + if ($protocol === null) { + $protocol = self::getProtocol($class); + } + + $context = self::buildContext($protocol, $context, $source); + try { + stream_wrapper_register($protocol, $class); + if (self::isDirectoryHandle($source)) { + return opendir($protocol . '://', $context); + } else { + return fopen($protocol . '://', 'r+', false, $context); + } + } finally { + stream_wrapper_unregister($protocol); + } + } + + protected static function isDirectoryHandle($resource) { + if ($resource === self::NO_SOURCE_DIR) { + return true; + } + if (!is_resource($resource)) { + throw new \BadMethodCallException('Invalid stream source'); + } + $meta = stream_get_meta_data($resource); + return $meta['stream_type'] === 'dir' || $meta['stream_type'] === 'user-space-dir'; + } + + /** + * Load the source from the stream context and return the context options + * + * @param string|null $name if not set, the generated protocol name is used + * @return array + * @throws \BadMethodCallException + */ + protected function loadContext($name = null) { + if ($name === null) { + $parts = explode('\\', static::class); + $name = strtolower(array_pop($parts)); + } + + $context = stream_context_get_options($this->context); + if (isset($context[$name])) { + $context = $context[$name]; + } else { + throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); + } + return $context; + } +} diff --git a/icewind/streams/src/WriteHashWrapper.php b/icewind/streams/src/WriteHashWrapper.php new file mode 100644 index 000000000..854845ad1 --- /dev/null +++ b/icewind/streams/src/WriteHashWrapper.php @@ -0,0 +1,37 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Icewind\Streams; + +/** + * Wrapper that calculates the hash on the stream on write + * + * The stream and hash should be passed in when wrapping the stream. + * On close the callback will be called with the calculated checksum. + * + * For supported hashes see: http://php.net/manual/en/function.hash-algos.php + */ +class WriteHashWrapper extends HashWrapper { + public function stream_write($data) { + $this->updateHash($data); + return parent::stream_write($data); + } +} \ No newline at end of file