diff options
author | Robin Appelman <robin@icewind.nl> | 2021-10-14 15:44:24 +0300 |
---|---|---|
committer | Robin Appelman <robin@icewind.nl> | 2021-10-14 15:44:24 +0300 |
commit | 2e6fdcd066bccc74969dfe788d6151ab529d53f0 (patch) | |
tree | 91004e53f260cd81a1d69a37ceaa4f9b8eec2e60 | |
parent | b9519d3f7d57b17401ccf4eee98455570c494e6a (diff) |
[20] Bump icewind/streams to 0.7.5composer/stable20/icewind/streams-0.7.5
Signed-off-by: Robin Appelman <robin@icewind.nl>
28 files changed, 533 insertions, 285 deletions
diff --git a/composer.json b/composer.json index f3a00412..880a47a6 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "doctrine/dbal": "2.10.2", "guzzlehttp/guzzle": "6.5.2", "icewind/searchdav": "^2.0.0", - "icewind/streams": "v0.7.1", + "icewind/streams": "v0.7.5", "jeremeamia/superclosure": "^2.4", "league/flysystem": "^1.0", "microsoft/azure-storage-blob": "1.5.0", diff --git a/composer.lock b/composer.lock index 40d65ee5..c19bf5f4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9c81b3768ac0e6bf0c3f4d3bc9b3aa96", + "content-hash": "12672044acefa5f05fcd287fa61748d7", "packages": [ { "name": "aws/aws-sdk-php", @@ -1657,29 +1657,29 @@ }, { "name": "icewind/streams", - "version": "v0.7.1", + "version": "v0.7.5", "source": { "type": "git", "url": "https://github.com/icewind1991/Streams.git", - "reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121" + "reference": "0c6aae16ebdadb257f0bd089c1e1e4cf5e20ddc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/Streams/zipball/4db3ed6c366e90b958d00e1d4c6360a9b39b2121", - "reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121", + "url": "https://api.github.com/repos/icewind1991/Streams/zipball/0c6aae16ebdadb257f0bd089c1e1e4cf5e20ddc2", + "reference": "0c6aae16ebdadb257f0bd089c1e1e4cf5e20ddc2", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8", - "satooshi/php-coveralls": "v1.0.0" + "friendsofphp/php-cs-fixer": "^2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9" }, "type": "library", "autoload": { "psr-4": { - "Icewind\\Streams\\Tests\\": "tests/", "Icewind\\Streams\\": "src/" } }, @@ -1694,7 +1694,11 @@ } ], "description": "A set of generic stream wrappers", - "time": "2019-02-15T12:57:29+00:00" + "support": { + "issues": "https://github.com/icewind1991/Streams/issues", + "source": "https://github.com/icewind1991/Streams/tree/v0.7.5" + }, + "time": "2021-06-14T14:02:48+00:00" }, { "name": "jeremeamia/superclosure", diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index e2bd2b42..9367604d 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -1395,14 +1395,19 @@ return array( '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_psr4.php b/composer/autoload_psr4.php index 22302727..b795e70d 100644 --- a/composer/autoload_psr4.php +++ b/composer/autoload_psr4.php @@ -60,7 +60,6 @@ return array( 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), 'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'), - 'Icewind\\Streams\\Tests\\' => array($vendorDir . '/icewind/streams/tests'), 'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'), 'ID3Parser\\' => array($vendorDir . '/christophwurst/id3parser/src'), 'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'), diff --git a/composer/autoload_static.php b/composer/autoload_static.php index 63db5a2e..58985155 100644 --- a/composer/autoload_static.php +++ b/composer/autoload_static.php @@ -220,7 +220,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 ), 'I' => array ( - 'Icewind\\Streams\\Tests\\' => 22, 'Icewind\\Streams\\' => 16, 'ID3Parser\\' => 10, ), @@ -494,10 +493,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/mtdowling/jmespath.php/src', ), - 'Icewind\\Streams\\Tests\\' => - array ( - 0 => __DIR__ . '/..' . '/icewind/streams/tests', - ), 'Icewind\\Streams\\' => array ( 0 => __DIR__ . '/..' . '/icewind/streams/src', @@ -2024,14 +2019,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.json b/composer/installed.json index c8c53a23..16a7901b 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -1726,32 +1726,32 @@ }, { "name": "icewind/streams", - "version": "v0.7.1", - "version_normalized": "0.7.1.0", + "version": "v0.7.5", + "version_normalized": "0.7.5.0", "source": { "type": "git", "url": "https://github.com/icewind1991/Streams.git", - "reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121" + "reference": "0c6aae16ebdadb257f0bd089c1e1e4cf5e20ddc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/Streams/zipball/4db3ed6c366e90b958d00e1d4c6360a9b39b2121", - "reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121", + "url": "https://api.github.com/repos/icewind1991/Streams/zipball/0c6aae16ebdadb257f0bd089c1e1e4cf5e20ddc2", + "reference": "0c6aae16ebdadb257f0bd089c1e1e4cf5e20ddc2", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8", - "satooshi/php-coveralls": "v1.0.0" + "friendsofphp/php-cs-fixer": "^2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9" }, - "time": "2019-02-15T12:57:29+00:00", + "time": "2021-06-14T14:02:48+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "Icewind\\Streams\\Tests\\": "tests/", "Icewind\\Streams\\": "src/" } }, @@ -1766,6 +1766,10 @@ } ], "description": "A set of generic stream wrappers", + "support": { + "issues": "https://github.com/icewind1991/Streams/issues", + "source": "https://github.com/icewind1991/Streams/tree/v0.7.5" + }, "install-path": "../icewind/streams" }, { diff --git a/composer/installed.php b/composer/installed.php index a3d76517..eaa76376 100644 --- a/composer/installed.php +++ b/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'd529936eea0b8174fb9acba170f4b68ba276fb00', + 'reference' => 'b9519d3f7d57b17401ccf4eee98455570c494e6a', 'name' => 'nextcloud/3rdparty', 'dev' => false, ), @@ -236,12 +236,12 @@ 'dev_requirement' => false, ), 'icewind/streams' => array( - 'pretty_version' => 'v0.7.1', - 'version' => '0.7.1.0', + 'pretty_version' => 'v0.7.5', + 'version' => '0.7.5.0', 'type' => 'library', 'install_path' => __DIR__ . '/../icewind/streams', 'aliases' => array(), - 'reference' => '4db3ed6c366e90b958d00e1d4c6360a9b39b2121', + 'reference' => '0c6aae16ebdadb257f0bd089c1e1e4cf5e20ddc2', 'dev_requirement' => false, ), 'jeremeamia/superclosure' => array( @@ -322,7 +322,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'd529936eea0b8174fb9acba170f4b68ba276fb00', + 'reference' => 'b9519d3f7d57b17401ccf4eee98455570c494e6a', 'dev_requirement' => false, ), 'nextcloud/lognormalizer' => array( diff --git a/icewind/streams/.github/workflows/ci.yaml b/icewind/streams/.github/workflows/ci.yaml new file mode 100644 index 00000000..f7422333 --- /dev/null +++ b/icewind/streams/.github/workflows/ci.yaml @@ -0,0 +1,83 @@ +on: [push, pull_request] + +name: CI + +jobs: + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@master + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + - name: PHP-CS-Fixer + uses: OskarStark/php-cs-fixer-ga@2.16.7 + with: + args: --diff --dry-run --allow-risky yes --stop-on-violation --using-cache=no --path-mode=intersection + + phpstan: + name: PHPStan Static Analysis + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + - name: Composer + run: composer install + - env: + BACKEND: smbclient + run: php ./vendor/bin/phpstan analyse --level 5 src + + phpunit: + runs-on: ubuntu-20.04 + name: Unit tests + + strategy: + matrix: + php-version: + - "7.3" + - "7.4" + - "8.0" + + steps: + - uses: actions/checkout@v2 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: pcov + - name: Composer + run: composer install + - name: PHPUnit Tests + run: php ./vendor/bin/phpunit tests -c tests/phpunit.xml --coverage-clover=coverage.xml + - uses: codecov/codecov-action@v1 + with: + files: ./coverage.xml + + phpunit-8: + runs-on: ubuntu-20.04 + name: Unit tests + + strategy: + matrix: + php-version: + - "7.1" + - "7.2" + + steps: + - uses: actions/checkout@v2 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + - name: Composer + run: | + echo '{"autoload": {"psr-4": {"Icewind\\Streams\\": "src/"}},"autoload-dev": {"psr-4": {"Icewind\\Streams\\Tests\\": "tests/"}}}' > composer.json + composer require --dev phpunit/phpunit ^6 + - name: PHPUnit Tests + run: php ./vendor/bin/phpunit tests -c tests/phpunit.xml diff --git a/icewind/streams/.gitignore b/icewind/streams/.gitignore index 4f389129..a8fa5d4a 100644 --- a/icewind/streams/.gitignore +++ b/icewind/streams/.gitignore @@ -1,3 +1,6 @@ .idea vendor composer.lock +build +example.php +*.cache diff --git a/icewind/streams/src/CallbackWrapper.php b/icewind/streams/src/CallbackWrapper.php index 67f9110d..5d78b5a3 100644 --- a/icewind/streams/src/CallbackWrapper.php +++ b/icewind/streams/src/CallbackWrapper.php @@ -25,27 +25,27 @@ namespace Icewind\Streams; */ class CallbackWrapper extends Wrapper { /** - * @var callable + * @var callable|null */ protected $readCallback; /** - * @var callable + * @var callable|null */ protected $writeCallback; /** - * @var callable + * @var callable|null */ protected $closeCallback; /** - * @var callable + * @var callable|null */ protected $readDirCallBack; /** - * @var callable + * @var callable|null */ protected $preCloseCallback; @@ -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 @@ class CallbackWrapper extends Wrapper { 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 8b86ab91..b3346209 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 @@ class CountWrapper extends Wrapper { 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 c80a8783..912be76a 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 4b869699..80b27e8b 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 @@ class DirectoryFilter extends DirectoryWrapper { 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 @@ class DirectoryFilter extends DirectoryWrapper { /** * @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 63e4805a..7f2f5c29 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 @@ class DirectoryWrapper implements Directory { * @return bool */ public function dir_opendir($path, $options) { - $this->loadContext('dir'); + $this->loadContext(); return true; } @@ -72,17 +44,4 @@ class DirectoryWrapper implements Directory { 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/File.php b/icewind/streams/src/File.php index 252b7b89..9662414a 100644 --- a/icewind/streams/src/File.php +++ b/icewind/streams/src/File.php @@ -15,7 +15,7 @@ interface File { * @param string $path * @param string $mode * @param int $options - * @param string &$opened_path + * @param string $opened_path * @return bool */ public function stream_open($path, $mode, $options, &$opened_path); @@ -28,19 +28,19 @@ interface File { public function stream_seek($offset, $whence = SEEK_SET); /** - * @return int + * @return int|false */ public function stream_tell(); /** * @param int $count - * @return string + * @return string|false */ public function stream_read($count); /** * @param string $data - * @return int + * @return int|false */ public function stream_write($data); @@ -59,7 +59,7 @@ interface File { public function stream_truncate($size); /** - * @return array + * @return array|false */ public function stream_stat(); diff --git a/icewind/streams/src/HashWrapper.php b/icewind/streams/src/HashWrapper.php new file mode 100644 index 00000000..616c2fe5 --- /dev/null +++ b/icewind/streams/src/HashWrapper.php @@ -0,0 +1,78 @@ +<?php +/** + * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Icewind\Streams; + +abstract class HashWrapper extends Wrapper { + + /** + * @var callable|null + */ + private $callback; + + /** + * @var resource|\HashContext + */ + 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(); + } +} diff --git a/icewind/streams/src/IteratorDirectory.php b/icewind/streams/src/IteratorDirectory.php index 6dfa42a8..a3872ddf 100644 --- a/icewind/streams/src/IteratorDirectory.php +++ b/icewind/streams/src/IteratorDirectory.php @@ -20,7 +20,7 @@ namespace Icewind\Streams; * * 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,18 +36,13 @@ 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'])) { + } elseif (isset($context['array'])) { $this->iterator = new \ArrayIterator($context['array']); } else { throw new \BadMethodCallException('Invalid context, iterator or array not set'); @@ -61,12 +56,12 @@ class IteratorDirectory implements Directory { * @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 @@ class IteratorDirectory implements Directory { * 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) - )); - } else if (is_array($source)) { - $context = stream_context_create(array( - 'dir' => array( - 'array' => $source) - )); + $options = [ + 'iterator' => $source + ]; + } elseif (is_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 b6c71d98..92aef2c7 100644 --- a/icewind/streams/src/NullWrapper.php +++ b/icewind/streams/src/NullWrapper.php @@ -11,29 +11,17 @@ namespace Icewind\Streams; * 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 bef9fd5f..42d74a2a 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 @@ class Path { */ 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 88af7e17..d9f3014c 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 00000000..16cf006c --- /dev/null +++ b/icewind/streams/src/ReadHashWrapper.php @@ -0,0 +1,40 @@ +<?php +/** + * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 8238f19f..d4727aa9 100644 --- a/icewind/streams/src/RetryWrapper.php +++ b/icewind/streams/src/RetryWrapper.php @@ -11,25 +11,8 @@ namespace Icewind\Streams; * 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 @@ class RetryWrapper extends Wrapper { } 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 d41fd73e..f131e753 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 @@ class SeekableWrapper extends Wrapper { } 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; } @@ -72,7 +63,7 @@ class SeekableWrapper extends Wrapper { public function stream_seek($offset, $whence = SEEK_SET) { if ($whence === SEEK_SET) { $target = $offset; - } else if ($whence === SEEK_CUR) { + } elseif ($whence === SEEK_CUR) { $current = ftell($this->cache); $target = $current + $offset; } else { diff --git a/icewind/streams/src/Url.php b/icewind/streams/src/Url.php index d6822608..38cbbdd8 100644 --- a/icewind/streams/src/Url.php +++ b/icewind/streams/src/Url.php @@ -22,7 +22,7 @@ interface Url { * @param string $path * @param string $mode * @param int $options - * @param string &$opened_path + * @param string $opened_path * @return bool */ public function stream_open($path, $mode, $options, &$opened_path); @@ -50,7 +50,7 @@ interface Url { public function rmdir($path, $options); /** - * @param string + * @param string $path * @return bool */ public function unlink($path); @@ -58,7 +58,7 @@ interface Url { /** * @param string $path * @param int $flags - * @return array + * @return array|false */ public function url_stat($path, $flags); } diff --git a/icewind/streams/src/UrlCallBack.php b/icewind/streams/src/UrlCallback.php index 580bfc6b..09ba2aef 100644 --- a/icewind/streams/src/UrlCallBack.php +++ b/icewind/streams/src/UrlCallback.php @@ -47,24 +47,30 @@ 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( - 'source' => $source, - 'fopen' => $fopen, + public static function wrap( + $source, + $fopen = null, + $opendir = null, + $mkdir = null, + $rename = null, + $rmdir = null, + $unlink = null, + $stat = null + ) { + return new Path(static::class, [ + 'source' => $source, + 'fopen' => $fopen, 'opendir' => $opendir, - 'mkdir' => $mkdir, - 'rename' => $rename, - 'rmdir' => $rmdir, - 'unlink' => $unlink, - 'stat' => $stat - ); - return new Path('\Icewind\Streams\UrlCallBack', $options); + 'mkdir' => $mkdir, + 'rename' => $rename, + 'rmdir' => $rmdir, + 'unlink' => $unlink, + 'stat' => $stat + ]); } - protected function loadContext($url) { + protected function loadUrlContext($url) { list($protocol) = explode('://', $url); $options = stream_context_get_options($this->context); return $options[$protocol]; @@ -77,40 +83,48 @@ class UrlCallback extends Wrapper implements Url { } 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 babd2c1a..03d0b202 100644 --- a/icewind/streams/src/Wrapper.php +++ b/icewind/streams/src/Wrapper.php @@ -12,7 +12,7 @@ namespace Icewind\Streams; * * 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 @@ abstract class Wrapper implements File, Directory { 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; @@ -98,14 +62,13 @@ abstract class Wrapper implements File, Directory { public function stream_set_option($option, $arg1, $arg2) { switch ($option) { case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->source, $arg1); - break; + return stream_set_blocking($this->source, (bool)$arg1); case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->source, $arg1, $arg2); - break; + return stream_set_timeout($this->source, $arg1, $arg2); case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->source, $arg1); + return stream_set_write_buffer($this->source, $arg1) === 0; } + return false; } public function stream_truncate($size) { @@ -129,7 +92,9 @@ abstract class Wrapper implements File, Directory { } public function stream_close() { - return fclose($this->source); + if (is_resource($this->source)) { + return fclose($this->source); + } } public function dir_readdir() { diff --git a/icewind/streams/src/WrapperHandler.php b/icewind/streams/src/WrapperHandler.php new file mode 100644 index 00000000..258e3ec8 --- /dev/null +++ b/icewind/streams/src/WrapperHandler.php @@ -0,0 +1,114 @@ +<?php +/** + * @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Icewind\Streams; + +class WrapperHandler { + /** @var resource $context */ + protected $context; + + 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, $mode = 'r+') { + 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 . '://', $mode, 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 00000000..279d9fd1 --- /dev/null +++ b/icewind/streams/src/WriteHashWrapper.php @@ -0,0 +1,37 @@ +<?php +/** + * @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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); + } +} |