Merge pull request #26262 from nextcloud/backport/26046/stable20

[stable20] update icewind/smb to 3.4.0
This commit is contained in:
Roeland Jago Douma 2021-03-29 21:14:39 +02:00 committed by GitHub
commit dd9890c5b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1942 additions and 1205 deletions

View File

@ -6,3 +6,6 @@ icewind/smb/Makefile
icewind/smb/.travis.yml icewind/smb/.travis.yml
icewind/smb/.scrutinizer.yml icewind/smb/.scrutinizer.yml
icewind/streams/tests icewind/streams/tests
.github
.php_cs*
psalm.xml

View File

@ -8,7 +8,7 @@
"classmap-authoritative": true "classmap-authoritative": true
}, },
"require": { "require": {
"icewind/streams": "0.7.1", "icewind/streams": "0.7.3",
"icewind/smb": "3.2.7" "icewind/smb": "3.4.0"
} }
} }

View File

@ -4,29 +4,31 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "6181c23a5c03b00fbdc659d87c1ad67d", "content-hash": "9905ed45527f669a4165a8b83b6e4141",
"packages": [ "packages": [
{ {
"name": "icewind/smb", "name": "icewind/smb",
"version": "v3.2.7", "version": "v3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/icewind1991/SMB.git", "url": "https://github.com/icewind1991/SMB.git",
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6" "reference": "b5c6921f2e91229c9f71556a4713b4fac91fd394"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/743a7bf35317f1b76cf8e8b804e54a6c5faacad6", "url": "https://api.github.com/repos/icewind1991/SMB/zipball/b5c6921f2e91229c9f71556a4713b4fac91fd394",
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6", "reference": "b5c6921f2e91229c9f71556a4713b4fac91fd394",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"icewind/streams": ">=0.2.0", "icewind/streams": ">=0.7.3",
"php": ">=7.1" "php": ">=7.2"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^2.13", "friendsofphp/php-cs-fixer": "^2.16",
"phpunit/phpunit": "^7.0" "phpstan/phpstan": "^0.12.57",
"phpunit/phpunit": "^8.5|^9.3.8",
"psalm/phar": "^4.3"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -45,33 +47,37 @@
} }
], ],
"description": "php wrapper for smbclient and libsmbclient-php", "description": "php wrapper for smbclient and libsmbclient-php",
"time": "2020-09-03T13:00:22+00:00" "support": {
"issues": "https://github.com/icewind1991/SMB/issues",
"source": "https://github.com/icewind1991/SMB/tree/v3.4.0"
},
"time": "2021-03-10T14:00:37+00:00"
}, },
{ {
"name": "icewind/streams", "name": "icewind/streams",
"version": "v0.7.1", "version": "v0.7.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/icewind1991/Streams.git", "url": "https://github.com/icewind1991/Streams.git",
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121" "reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/4db3ed6c366e90b958d00e1d4c6360a9b39b2121", "url": "https://api.github.com/repos/icewind1991/Streams/zipball/22ef9fc5b50d645dbc202206a656cc4dde28f95c",
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121", "reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3" "php": ">=7.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8", "friendsofphp/php-cs-fixer": "^2",
"satooshi/php-coveralls": "v1.0.0" "phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^9"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Icewind\\Streams\\Tests\\": "tests/",
"Icewind\\Streams\\": "src/" "Icewind\\Streams\\": "src/"
} }
}, },
@ -86,7 +92,11 @@
} }
], ],
"description": "A set of generic stream wrappers", "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.3"
},
"time": "2021-03-02T19:33:35+00:00"
} }
], ],
"packages-dev": [], "packages-dev": [],
@ -97,5 +107,5 @@
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": [],
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "1.1.0" "plugin-api-version": "2.0.0"
} }

View File

@ -37,11 +37,13 @@ namespace Composer\Autoload;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/ * @see https://www.php-fig.org/psr/psr-4/
*/ */
class ClassLoader class ClassLoader
{ {
private $vendorDir;
// PSR-4 // PSR-4
private $prefixLengthsPsr4 = array(); private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array(); private $prefixDirsPsr4 = array();
@ -57,10 +59,17 @@ class ClassLoader
private $missingClasses = array(); private $missingClasses = array();
private $apcuPrefix; private $apcuPrefix;
private static $registeredLoaders = array();
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
public function getPrefixes() public function getPrefixes()
{ {
if (!empty($this->prefixesPsr0)) { if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0); return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
} }
return array(); return array();
@ -300,6 +309,17 @@ class ClassLoader
public function register($prepend = false) public function register($prepend = false)
{ {
spl_autoload_register(array($this, 'loadClass'), true, $prepend); spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
} }
/** /**
@ -308,6 +328,10 @@ class ClassLoader
public function unregister() public function unregister()
{ {
spl_autoload_unregister(array($this, 'loadClass')); spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
} }
/** /**
@ -367,6 +391,16 @@ class ClassLoader
return $file; return $file;
} }
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
private function findFileWithExtension($class, $ext) private function findFileWithExtension($class, $ext)
{ {
// PSR-4 lookup // PSR-4 lookup

View File

@ -0,0 +1,301 @@
<?php
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
class InstalledVersions
{
private static $installed = array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
'name' => 'files_external/3rdparty',
),
'versions' =>
array (
'files_external/3rdparty' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
),
'icewind/smb' =>
array (
'pretty_version' => 'v3.4.0',
'version' => '3.4.0.0',
'aliases' =>
array (
),
'reference' => 'b5c6921f2e91229c9f71556a4713b4fac91fd394',
),
'icewind/streams' =>
array (
'pretty_version' => 'v0.7.3',
'version' => '0.7.3.0',
'aliases' =>
array (
),
'reference' => '22ef9fc5b50d645dbc202206a656cc4dde28f95c',
),
),
);
private static $canGetVendors;
private static $installedByVendor = array();
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
public static function isInstalled($packageName)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return true;
}
}
return false;
}
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
public static function getRawData()
{
return self::$installed;
}
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
}
}
}
$installed[] = self::$installed;
return $installed;
}
}

View File

@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = $vendorDir; $baseDir = $vendorDir;
return array( return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Icewind\\SMB\\ACL' => $vendorDir . '/icewind/smb/src/ACL.php', 'Icewind\\SMB\\ACL' => $vendorDir . '/icewind/smb/src/ACL.php',
'Icewind\\SMB\\AbstractServer' => $vendorDir . '/icewind/smb/src/AbstractServer.php', 'Icewind\\SMB\\AbstractServer' => $vendorDir . '/icewind/smb/src/AbstractServer.php',
'Icewind\\SMB\\AbstractShare' => $vendorDir . '/icewind/smb/src/AbstractShare.php', 'Icewind\\SMB\\AbstractShare' => $vendorDir . '/icewind/smb/src/AbstractShare.php',
@ -57,6 +58,7 @@ return array(
'Icewind\\SMB\\Native\\NativeWriteStream' => $vendorDir . '/icewind/smb/src/Native/NativeWriteStream.php', 'Icewind\\SMB\\Native\\NativeWriteStream' => $vendorDir . '/icewind/smb/src/Native/NativeWriteStream.php',
'Icewind\\SMB\\Options' => $vendorDir . '/icewind/smb/src/Options.php', 'Icewind\\SMB\\Options' => $vendorDir . '/icewind/smb/src/Options.php',
'Icewind\\SMB\\ServerFactory' => $vendorDir . '/icewind/smb/src/ServerFactory.php', 'Icewind\\SMB\\ServerFactory' => $vendorDir . '/icewind/smb/src/ServerFactory.php',
'Icewind\\SMB\\StringBuffer' => $vendorDir . '/icewind/smb/src/StringBuffer.php',
'Icewind\\SMB\\System' => $vendorDir . '/icewind/smb/src/System.php', 'Icewind\\SMB\\System' => $vendorDir . '/icewind/smb/src/System.php',
'Icewind\\SMB\\TimeZoneProvider' => $vendorDir . '/icewind/smb/src/TimeZoneProvider.php', 'Icewind\\SMB\\TimeZoneProvider' => $vendorDir . '/icewind/smb/src/TimeZoneProvider.php',
'Icewind\\SMB\\Wrapped\\Connection' => $vendorDir . '/icewind/smb/src/Wrapped/Connection.php', 'Icewind\\SMB\\Wrapped\\Connection' => $vendorDir . '/icewind/smb/src/Wrapped/Connection.php',
@ -73,13 +75,17 @@ return array(
'Icewind\\Streams\\DirectoryFilter' => $vendorDir . '/icewind/streams/src/DirectoryFilter.php', 'Icewind\\Streams\\DirectoryFilter' => $vendorDir . '/icewind/streams/src/DirectoryFilter.php',
'Icewind\\Streams\\DirectoryWrapper' => $vendorDir . '/icewind/streams/src/DirectoryWrapper.php', 'Icewind\\Streams\\DirectoryWrapper' => $vendorDir . '/icewind/streams/src/DirectoryWrapper.php',
'Icewind\\Streams\\File' => $vendorDir . '/icewind/streams/src/File.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\\IteratorDirectory' => $vendorDir . '/icewind/streams/src/IteratorDirectory.php',
'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php', 'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php',
'Icewind\\Streams\\Path' => $vendorDir . '/icewind/streams/src/Path.php', 'Icewind\\Streams\\Path' => $vendorDir . '/icewind/streams/src/Path.php',
'Icewind\\Streams\\PathWrapper' => $vendorDir . '/icewind/streams/src/PathWrapper.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\\RetryWrapper' => $vendorDir . '/icewind/streams/src/RetryWrapper.php',
'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php', 'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php',
'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php', 'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php',
'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallBack.php', 'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallback.php',
'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.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',
); );

View File

@ -6,7 +6,6 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = $vendorDir; $baseDir = $vendorDir;
return array( return array(
'Icewind\\Streams\\Tests\\' => array($vendorDir . '/icewind/streams/tests'),
'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'), 'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'),
'Icewind\\SMB\\' => array($vendorDir . '/icewind/smb/src'), 'Icewind\\SMB\\' => array($vendorDir . '/icewind/smb/src'),
); );

View File

@ -22,13 +22,15 @@ class ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3
return self::$loader; return self::$loader;
} }
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) { if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php'; require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::getInitializer($loader)); call_user_func(\Composer\Autoload\ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::getInitializer($loader));
} else { } else {

View File

@ -9,17 +9,12 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
public static $prefixLengthsPsr4 = array ( public static $prefixLengthsPsr4 = array (
'I' => 'I' =>
array ( array (
'Icewind\\Streams\\Tests\\' => 22,
'Icewind\\Streams\\' => 16, 'Icewind\\Streams\\' => 16,
'Icewind\\SMB\\' => 12, 'Icewind\\SMB\\' => 12,
), ),
); );
public static $prefixDirsPsr4 = array ( public static $prefixDirsPsr4 = array (
'Icewind\\Streams\\Tests\\' =>
array (
0 => __DIR__ . '/..' . '/icewind/streams/tests',
),
'Icewind\\Streams\\' => 'Icewind\\Streams\\' =>
array ( array (
0 => __DIR__ . '/..' . '/icewind/streams/src', 0 => __DIR__ . '/..' . '/icewind/streams/src',
@ -31,6 +26,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
); );
public static $classMap = array ( public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Icewind\\SMB\\ACL' => __DIR__ . '/..' . '/icewind/smb/src/ACL.php', 'Icewind\\SMB\\ACL' => __DIR__ . '/..' . '/icewind/smb/src/ACL.php',
'Icewind\\SMB\\AbstractServer' => __DIR__ . '/..' . '/icewind/smb/src/AbstractServer.php', 'Icewind\\SMB\\AbstractServer' => __DIR__ . '/..' . '/icewind/smb/src/AbstractServer.php',
'Icewind\\SMB\\AbstractShare' => __DIR__ . '/..' . '/icewind/smb/src/AbstractShare.php', 'Icewind\\SMB\\AbstractShare' => __DIR__ . '/..' . '/icewind/smb/src/AbstractShare.php',
@ -82,6 +78,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
'Icewind\\SMB\\Native\\NativeWriteStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeWriteStream.php', 'Icewind\\SMB\\Native\\NativeWriteStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeWriteStream.php',
'Icewind\\SMB\\Options' => __DIR__ . '/..' . '/icewind/smb/src/Options.php', 'Icewind\\SMB\\Options' => __DIR__ . '/..' . '/icewind/smb/src/Options.php',
'Icewind\\SMB\\ServerFactory' => __DIR__ . '/..' . '/icewind/smb/src/ServerFactory.php', 'Icewind\\SMB\\ServerFactory' => __DIR__ . '/..' . '/icewind/smb/src/ServerFactory.php',
'Icewind\\SMB\\StringBuffer' => __DIR__ . '/..' . '/icewind/smb/src/StringBuffer.php',
'Icewind\\SMB\\System' => __DIR__ . '/..' . '/icewind/smb/src/System.php', 'Icewind\\SMB\\System' => __DIR__ . '/..' . '/icewind/smb/src/System.php',
'Icewind\\SMB\\TimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/TimeZoneProvider.php', 'Icewind\\SMB\\TimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/TimeZoneProvider.php',
'Icewind\\SMB\\Wrapped\\Connection' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Connection.php', 'Icewind\\SMB\\Wrapped\\Connection' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Connection.php',
@ -98,15 +95,19 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
'Icewind\\Streams\\DirectoryFilter' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryFilter.php', 'Icewind\\Streams\\DirectoryFilter' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryFilter.php',
'Icewind\\Streams\\DirectoryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryWrapper.php', 'Icewind\\Streams\\DirectoryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryWrapper.php',
'Icewind\\Streams\\File' => __DIR__ . '/..' . '/icewind/streams/src/File.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\\IteratorDirectory' => __DIR__ . '/..' . '/icewind/streams/src/IteratorDirectory.php',
'Icewind\\Streams\\NullWrapper' => __DIR__ . '/..' . '/icewind/streams/src/NullWrapper.php', 'Icewind\\Streams\\NullWrapper' => __DIR__ . '/..' . '/icewind/streams/src/NullWrapper.php',
'Icewind\\Streams\\Path' => __DIR__ . '/..' . '/icewind/streams/src/Path.php', 'Icewind\\Streams\\Path' => __DIR__ . '/..' . '/icewind/streams/src/Path.php',
'Icewind\\Streams\\PathWrapper' => __DIR__ . '/..' . '/icewind/streams/src/PathWrapper.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\\RetryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/RetryWrapper.php',
'Icewind\\Streams\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/src/SeekableWrapper.php', 'Icewind\\Streams\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/src/SeekableWrapper.php',
'Icewind\\Streams\\Url' => __DIR__ . '/..' . '/icewind/streams/src/Url.php', 'Icewind\\Streams\\Url' => __DIR__ . '/..' . '/icewind/streams/src/Url.php',
'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallBack.php', 'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallback.php',
'Icewind\\Streams\\Wrapper' => __DIR__ . '/..' . '/icewind/streams/src/Wrapper.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',
); );
public static function getInitializer(ClassLoader $loader) public static function getInitializer(ClassLoader $loader)

View File

@ -1,88 +1,104 @@
[ {
{ "packages": [
"name": "icewind/smb", {
"version": "v3.2.7", "name": "icewind/smb",
"version_normalized": "3.2.7.0", "version": "v3.4.0",
"source": { "version_normalized": "3.4.0.0",
"type": "git", "source": {
"url": "https://github.com/icewind1991/SMB.git", "type": "git",
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6" "url": "https://github.com/icewind1991/SMB.git",
"reference": "b5c6921f2e91229c9f71556a4713b4fac91fd394"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/b5c6921f2e91229c9f71556a4713b4fac91fd394",
"reference": "b5c6921f2e91229c9f71556a4713b4fac91fd394",
"shasum": ""
},
"require": {
"icewind/streams": ">=0.7.3",
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
"phpstan/phpstan": "^0.12.57",
"phpunit/phpunit": "^8.5|^9.3.8",
"psalm/phar": "^4.3"
},
"time": "2021-03-10T14:00:37+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Icewind\\SMB\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"description": "php wrapper for smbclient and libsmbclient-php",
"support": {
"issues": "https://github.com/icewind1991/SMB/issues",
"source": "https://github.com/icewind1991/SMB/tree/v3.4.0"
},
"install-path": "../icewind/smb"
}, },
"dist": { {
"type": "zip", "name": "icewind/streams",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/743a7bf35317f1b76cf8e8b804e54a6c5faacad6", "version": "v0.7.3",
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6", "version_normalized": "0.7.3.0",
"shasum": "" "source": {
}, "type": "git",
"require": { "url": "https://github.com/icewind1991/Streams.git",
"icewind/streams": ">=0.2.0", "reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c"
"php": ">=7.1" },
}, "dist": {
"require-dev": { "type": "zip",
"friendsofphp/php-cs-fixer": "^2.13", "url": "https://api.github.com/repos/icewind1991/Streams/zipball/22ef9fc5b50d645dbc202206a656cc4dde28f95c",
"phpunit/phpunit": "^7.0" "reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c",
}, "shasum": ""
"time": "2020-09-03T13:00:22+00:00", },
"type": "library", "require": {
"installation-source": "dist", "php": ">=7.1"
"autoload": { },
"psr-4": { "require-dev": {
"Icewind\\SMB\\": "src/" "friendsofphp/php-cs-fixer": "^2",
} "phpstan/phpstan": "^0.12",
}, "phpunit/phpunit": "^9"
"notification-url": "https://packagist.org/downloads/", },
"license": [ "time": "2021-03-02T19:33:35+00:00",
"MIT" "type": "library",
], "installation-source": "dist",
"authors": [ "autoload": {
{ "psr-4": {
"name": "Robin Appelman", "Icewind\\Streams\\": "src/"
"email": "icewind@owncloud.com" }
} },
], "notification-url": "https://packagist.org/downloads/",
"description": "php wrapper for smbclient and libsmbclient-php" "license": [
}, "MIT"
{ ],
"name": "icewind/streams", "authors": [
"version": "v0.7.1", {
"version_normalized": "0.7.1.0", "name": "Robin Appelman",
"source": { "email": "icewind@owncloud.com"
"type": "git", }
"url": "https://github.com/icewind1991/Streams.git", ],
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121" "description": "A set of generic stream wrappers",
}, "support": {
"dist": { "issues": "https://github.com/icewind1991/Streams/issues",
"type": "zip", "source": "https://github.com/icewind1991/Streams/tree/v0.7.3"
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/4db3ed6c366e90b958d00e1d4c6360a9b39b2121", },
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121", "install-path": "../icewind/streams"
"shasum": "" }
}, ],
"require": { "dev": true,
"php": ">=5.3" "dev-package-names": []
}, }
"require-dev": {
"phpunit/phpunit": "^4.8",
"satooshi/php-coveralls": "v1.0.0"
},
"time": "2019-02-15T12:57:29+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Icewind\\Streams\\Tests\\": "tests/",
"Icewind\\Streams\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"description": "A set of generic stream wrappers"
}
]

View File

@ -0,0 +1,42 @@
<?php return array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
'name' => 'files_external/3rdparty',
),
'versions' =>
array (
'files_external/3rdparty' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
),
'icewind/smb' =>
array (
'pretty_version' => 'v3.4.0',
'version' => '3.4.0.0',
'aliases' =>
array (
),
'reference' => 'b5c6921f2e91229c9f71556a4713b4fac91fd394',
),
'icewind/streams' =>
array (
'pretty_version' => 'v0.7.3',
'version' => '0.7.3.0',
'aliases' =>
array (
),
'reference' => '22ef9fc5b50d645dbc202206a656cc4dde28f95c',
),
),
);

View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@ -4,3 +4,4 @@ composer.lock
.php_cs.cache .php_cs.cache
listen.php listen.php
test.php test.php
*.cache

View File

@ -1,9 +1,8 @@
SMB SMB
=== ===
[![Code Coverage](https://scrutinizer-ci.com/g/icewind1991/SMB/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/icewind1991/SMB/?branch=master) [![CI](https://github.com/icewind1991/SMB/actions/workflows/ci.yaml/badge.svg)](https://github.com/icewind1991/SMB/actions/workflows/ci.yaml)
[![Build Status](https://travis-ci.org/icewind1991/SMB.svg?branch=master)](https://travis-ci.org/icewind1991/SMB) [![codecov](https://codecov.io/gh/icewind1991/SMB/branch/master/graph/badge.svg?token=eTg0P466k6)](https://codecov.io/gh/icewind1991/SMB)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/icewind1991/SMB/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/icewind1991/SMB/?branch=master)
PHP wrapper for `smbclient` and [`libsmbclient-php`](https://github.com/eduardok/libsmbclient-php) PHP wrapper for `smbclient` and [`libsmbclient-php`](https://github.com/eduardok/libsmbclient-php)
@ -103,7 +102,7 @@ fclose($fh);
``` ```
**Note**: write() will truncate your file to 0bytes. You may open a writeable stream with append() which will point **Note**: write() will truncate your file to 0bytes. You may open a writeable stream with append() which will point
the cursor to the end of the file or create it if it does not exists yet. (append() is only compatible with libsmbclient-php) the cursor to the end of the file or create it if it does not exist yet. (append() is only compatible with libsmbclient-php)
```php ```php
$fh = $share->append('test.txt'); $fh = $share->append('test.txt');
fwrite($fh, 'bar'); fwrite($fh, 'bar');
@ -127,11 +126,22 @@ $options->setTimeout(5);
$serverFactory = new ServerFactory($options); $serverFactory = new ServerFactory($options);
``` ```
### Setting protocol version
```php
$options = new Options();
$options->setMinProtocol(IOptions::PROTOCOL_SMB2);
$options->setMaxProtocol(IOptions::PROTOCOL_SMB3);
$serverFactory = new ServerFactory($options);
```
Note, setting the protocol version is not supported with php-smbclient version 1.0.1 or lower.
### Customizing system integration ### Customizing system integration
The `smbclient` backend needs to get various information about the system it's running on to function The `smbclient` backend needs to get various information about the system it's running on to function
such as the paths of various binaries or the system timezone. such as the paths of various binaries or the system timezone.
While the default logic for getting this information should work on most systems, it possible to customize this behaviour. While the default logic for getting this information should work on most systems, it is possible to customize this behaviour.
In order to customize the integration you provide a custom implementation of `ITimezoneProvider` and/or `ISystem` and pass them as arguments to the `ServerFactory`. In order to customize the integration you provide a custom implementation of `ITimezoneProvider` and/or `ISystem` and pass them as arguments to the `ServerFactory`.

View File

@ -1,29 +1,37 @@
{ {
"name" : "icewind/smb", "name": "icewind/smb",
"description" : "php wrapper for smbclient and libsmbclient-php", "description": "php wrapper for smbclient and libsmbclient-php",
"license" : "MIT", "license": "MIT",
"authors" : [ "authors": [
{ {
"name" : "Robin Appelman", "name": "Robin Appelman",
"email": "icewind@owncloud.com" "email": "icewind@owncloud.com"
} }
], ],
"require" : { "require": {
"php": ">=7.1", "php": ">=7.2",
"icewind/streams": ">=0.2.0" "icewind/streams": ">=0.7.3"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^7.0", "phpunit/phpunit": "^8.5|^9.3.8",
"friendsofphp/php-cs-fixer": "^2.13" "friendsofphp/php-cs-fixer": "^2.16",
}, "phpstan/phpstan": "^0.12.57",
"autoload" : { "psalm/phar": "^4.3"
"psr-4": { },
"Icewind\\SMB\\": "src/" "autoload": {
} "psr-4": {
}, "Icewind\\SMB\\": "src/"
"autoload-dev" : { }
"psr-4": { },
"Icewind\\SMB\\Test\\": "tests/" "autoload-dev": {
} "psr-4": {
} "Icewind\\SMB\\Test\\": "tests/"
}
},
"scripts": {
"lint": "parallel-lint --exclude src --exclude vendor --exclude target --exclude build .",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm.phar"
}
} }

View File

@ -33,8 +33,11 @@ class ACL {
const FLAG_OBJECT_INHERIT = 0x1; const FLAG_OBJECT_INHERIT = 0x1;
const FLAG_CONTAINER_INHERIT = 0x2; const FLAG_CONTAINER_INHERIT = 0x2;
/** @var int */
private $type; private $type;
/** @var int */
private $flags; private $flags;
/** @var int */
private $mask; private $mask;
public function __construct(int $type, int $flags, int $mask) { public function __construct(int $type, int $flags, int $mask) {

View File

@ -24,24 +24,16 @@ namespace Icewind\SMB;
abstract class AbstractServer implements IServer { abstract class AbstractServer implements IServer {
const LOCALE = 'en_US.UTF-8'; const LOCALE = 'en_US.UTF-8';
/** /** @var string */
* @var string $host
*/
protected $host; protected $host;
/** /** @var IAuth */
* @var IAuth $user
*/
protected $auth; protected $auth;
/** /** @var ISystem */
* @var ISystem
*/
protected $system; protected $system;
/** /** @var ITimeZoneProvider */
* @var TimeZoneProvider
*/
protected $timezoneProvider; protected $timezoneProvider;
/** @var IOptions */ /** @var IOptions */
@ -51,10 +43,10 @@ abstract class AbstractServer implements IServer {
* @param string $host * @param string $host
* @param IAuth $auth * @param IAuth $auth
* @param ISystem $system * @param ISystem $system
* @param TimeZoneProvider $timeZoneProvider * @param ITimeZoneProvider $timeZoneProvider
* @param IOptions $options * @param IOptions $options
*/ */
public function __construct($host, IAuth $auth, ISystem $system, TimeZoneProvider $timeZoneProvider, IOptions $options) { public function __construct(string $host, IAuth $auth, ISystem $system, ITimeZoneProvider $timeZoneProvider, IOptions $options) {
$this->host = $host; $this->host = $host;
$this->auth = $auth; $this->auth = $auth;
$this->system = $system; $this->system = $system;
@ -62,23 +54,23 @@ abstract class AbstractServer implements IServer {
$this->options = $options; $this->options = $options;
} }
public function getAuth() { public function getAuth(): IAuth {
return $this->auth; return $this->auth;
} }
public function getHost() { public function getHost(): string {
return $this->host; return $this->host;
} }
public function getTimeZone() { public function getTimeZone(): string {
return $this->timezoneProvider->get($this->host); return $this->timezoneProvider->get($this->host);
} }
public function getSystem() { public function getSystem(): ISystem {
return $this->system; return $this->system;
} }
public function getOptions() { public function getOptions(): IOptions {
return $this->options; return $this->options;
} }
} }

View File

@ -10,13 +10,18 @@ namespace Icewind\SMB;
use Icewind\SMB\Exception\InvalidPathException; use Icewind\SMB\Exception\InvalidPathException;
abstract class AbstractShare implements IShare { abstract class AbstractShare implements IShare {
/** @var string[] */
private $forbiddenCharacters; private $forbiddenCharacters;
public function __construct() { public function __construct() {
$this->forbiddenCharacters = ['?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r"]; $this->forbiddenCharacters = ['?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r"];
} }
protected function verifyPath($path) { /**
* @param string $path
* @throws InvalidPathException
*/
protected function verifyPath(string $path): void {
foreach ($this->forbiddenCharacters as $char) { foreach ($this->forbiddenCharacters as $char) {
if (strpos($path, $char) !== false) { if (strpos($path, $char) !== false) {
throw new InvalidPathException('Invalid path, "' . $char . '" is not allowed'); throw new InvalidPathException('Invalid path, "' . $char . '" is not allowed');
@ -24,7 +29,10 @@ abstract class AbstractShare implements IShare {
} }
} }
public function setForbiddenChars(array $charList) { /**
* @param string[] $charList
*/
public function setForbiddenChars(array $charList): void {
$this->forbiddenCharacters = $charList; $this->forbiddenCharacters = $charList;
} }
} }

View File

@ -22,23 +22,23 @@
namespace Icewind\SMB; namespace Icewind\SMB;
class AnonymousAuth implements IAuth { class AnonymousAuth implements IAuth {
public function getUsername() { public function getUsername(): ?string {
return null; return null;
} }
public function getWorkgroup() { public function getWorkgroup(): ?string {
return 'dummy'; return 'dummy';
} }
public function getPassword() { public function getPassword(): ?string {
return null; return null;
} }
public function getExtraCommandLineArguments() { public function getExtraCommandLineArguments(): string {
return '-N'; return '-N';
} }
public function setExtraSmbClientOptions($smbClientState) { public function setExtraSmbClientOptions($smbClientState): void {
smbclient_option_set($smbClientState, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, true); smbclient_option_set($smbClientState, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, true);
} }
} }

View File

@ -24,41 +24,34 @@ namespace Icewind\SMB;
class BasicAuth implements IAuth { class BasicAuth implements IAuth {
/** @var string */ /** @var string */
private $username; private $username;
/** @var string */ /** @var string|null */
private $workgroup; private $workgroup;
/** @var string */ /** @var string */
private $password; private $password;
/** public function __construct(string $username, ?string $workgroup, string $password) {
* BasicAuth constructor.
*
* @param string $username
* @param string $workgroup
* @param string $password
*/
public function __construct($username, $workgroup, $password) {
$this->username = $username; $this->username = $username;
$this->workgroup = $workgroup; $this->workgroup = $workgroup;
$this->password = $password; $this->password = $password;
} }
public function getUsername() { public function getUsername(): ?string {
return $this->username; return $this->username;
} }
public function getWorkgroup() { public function getWorkgroup(): ?string {
return $this->workgroup; return $this->workgroup;
} }
public function getPassword() { public function getPassword(): ?string {
return $this->password; return $this->password;
} }
public function getExtraCommandLineArguments() { public function getExtraCommandLineArguments(): string {
return ($this->workgroup) ? '-W ' . escapeshellarg($this->workgroup) : ''; return ($this->workgroup) ? '-W ' . escapeshellarg($this->workgroup) : '';
} }
public function setExtraSmbClientOptions($smbClientState) { public function setExtraSmbClientOptions($smbClientState): void {
// noop // noop
} }
} }

View File

@ -9,32 +9,21 @@
namespace Icewind\SMB; namespace Icewind\SMB;
class Change { class Change {
/** @var int */
private $code; private $code;
/** @var string */
private $path; private $path;
/** public function __construct(int $code, string $path) {
* Change constructor.
*
* @param $code
* @param $path
*/
public function __construct($code, $path) {
$this->code = $code; $this->code = $code;
$this->path = $path; $this->path = $path;
} }
/** public function getCode(): int {
* @return integer
*/
public function getCode() {
return $this->code; return $this->code;
} }
/** public function getPath(): string {
* @return string
*/
public function getPath() {
return $this->path; return $this->path;
} }
} }

View File

@ -7,23 +7,37 @@
namespace Icewind\SMB\Exception; namespace Icewind\SMB\Exception;
use Throwable;
/**
* @psalm-consistent-constructor
*/
class Exception extends \Exception { class Exception extends \Exception {
public static function unknown($path, $error) { public function __construct(string $message = "", int $code = 0, Throwable $previous = null) {
$message = 'Unknown error (' . $error . ')'; parent::__construct($message, $code, $previous);
}
/**
* @param string|null $path
* @param string|int|null $error
* @return Exception
*/
public static function unknown(?string $path, $error): Exception {
$message = 'Unknown error (' . (string)$error . ')';
if ($path) { if ($path) {
$message .= ' for ' . $path; $message .= ' for ' . $path;
} }
return new Exception($message, is_string($error) ? 0 : $error); return new Exception($message, is_int($error) ? $error : 0);
} }
/** /**
* @param array $exceptionMap * @param array<int|string, class-string<Exception>> $exceptionMap
* @param mixed $error * @param string|int|null $error
* @param string $path * @param string|null $path
* @return Exception * @return Exception
*/ */
public static function fromMap(array $exceptionMap, $error, $path) { public static function fromMap(array $exceptionMap, $error, ?string $path): Exception {
if (isset($exceptionMap[$error])) { if (isset($exceptionMap[$error])) {
$exceptionClass = $exceptionMap[$error]; $exceptionClass = $exceptionMap[$error];
if (is_numeric($error)) { if (is_numeric($error)) {

View File

@ -13,15 +13,11 @@ class InvalidRequestException extends Exception {
*/ */
protected $path; protected $path;
/** public function __construct(string $path = "", int $code = 0, \Throwable $previous = null) {
* @param string $path
* @param int $code
*/
public function __construct($path, $code = 0) {
$class = get_class($this); $class = get_class($this);
$parts = explode('\\', $class); $parts = explode('\\', $class);
$baseName = array_pop($parts); $baseName = array_pop($parts);
parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code); parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code, $previous);
$this->path = $path; $this->path = $path;
} }

View File

@ -10,7 +10,7 @@ namespace Icewind\SMB\Exception;
use Throwable; use Throwable;
class RevisionMismatchException extends Exception { class RevisionMismatchException extends Exception {
public function __construct($message = 'Protocol version mismatch', $code = 0, Throwable $previous = null) { public function __construct(string $message = 'Protocol version mismatch', int $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
} }
} }

View File

@ -22,32 +22,23 @@
namespace Icewind\SMB; namespace Icewind\SMB;
interface IAuth { interface IAuth {
/** public function getUsername(): ?string;
* @return string|null
*/
public function getUsername();
/** public function getWorkgroup(): ?string;
* @return string|null
*/
public function getWorkgroup();
/** public function getPassword(): ?string;
* @return string|null
*/
public function getPassword();
/** /**
* Any extra command line option for smbclient that are required * Any extra command line option for smbclient that are required
* *
* @return string * @return string
*/ */
public function getExtraCommandLineArguments(); public function getExtraCommandLineArguments(): string;
/** /**
* Set any extra options for libsmbclient that are required * Set any extra options for libsmbclient that are required
* *
* @param resource $smbClientState * @param resource $smbClientState
*/ */
public function setExtraSmbClientOptions($smbClientState); public function setExtraSmbClientOptions($smbClientState): void;
} }

View File

@ -21,50 +21,23 @@ interface IFileInfo {
const MODE_ARCHIVE = 0x20; const MODE_ARCHIVE = 0x20;
const MODE_NORMAL = 0x80; const MODE_NORMAL = 0x80;
/** public function getPath(): string;
* @return string
*/
public function getPath();
/** public function getName(): string;
* @return string
*/
public function getName();
/** public function getSize(): int;
* @return int
*/
public function getSize();
/** public function getMTime(): int;
* @return int
*/
public function getMTime();
/** public function isDirectory(): bool;
* @return bool
*/
public function isDirectory();
/** public function isReadOnly(): bool;
* @return bool
*/
public function isReadOnly();
/** public function isHidden(): bool;
* @return bool
*/
public function isHidden();
/** public function isSystem(): bool;
* @return bool
*/
public function isSystem();
/** public function isArchived(): bool;
* @return bool
*/
public function isArchived();
/** /**
* @return ACL[] * @return ACL[]

View File

@ -25,21 +25,21 @@ interface INotifyHandler {
* *
* @return Change[] * @return Change[]
*/ */
public function getChanges(); public function getChanges(): array;
/** /**
* Listen actively to all incoming changes * Listen actively to all incoming changes
* *
* Note that this is a blocking process and will cause the process to block forever if not explicitly terminated * Note that this is a blocking process and will cause the process to block forever if not explicitly terminated
* *
* @param callable $callback * @param callable(Change):?bool $callback
*/ */
public function listen($callback); public function listen(callable $callback): void;
/** /**
* Stop listening for changes * Stop listening for changes
* *
* Note that any pending changes will be discarded * Note that any pending changes will be discarded
*/ */
public function stop(); public function stop(): void;
} }

View File

@ -22,8 +22,20 @@
namespace Icewind\SMB; namespace Icewind\SMB;
interface IOptions { interface IOptions {
/** const PROTOCOL_NT1 = 'NT1';
* @return int const PROTOCOL_SMB2 = 'SMB2';
*/ const PROTOCOL_SMB2_02 = 'SMB2_02';
public function getTimeout(); const PROTOCOL_SMB2_22 = 'SMB2_22';
const PROTOCOL_SMB2_24 = 'SMB2_24';
const PROTOCOL_SMB3 = 'SMB3';
const PROTOCOL_SMB3_00 = 'SMB3_00';
const PROTOCOL_SMB3_02 = 'SMB3_02';
const PROTOCOL_SMB3_10 = 'SMB3_10';
const PROTOCOL_SMB3_11 = 'SMB3_11';
public function getTimeout(): int;
public function getMinProtocol(): ?string;
public function getMaxProtocol(): ?string;
} }

View File

@ -22,15 +22,9 @@
namespace Icewind\SMB; namespace Icewind\SMB;
interface IServer { interface IServer {
/** public function getAuth(): IAuth;
* @return IAuth
*/
public function getAuth();
/** public function getHost(): string;
* @return string
*/
public function getHost();
/** /**
* @return \Icewind\SMB\IShare[] * @return \Icewind\SMB\IShare[]
@ -38,32 +32,15 @@ interface IServer {
* @throws \Icewind\SMB\Exception\AuthenticationException * @throws \Icewind\SMB\Exception\AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidHostException * @throws \Icewind\SMB\Exception\InvalidHostException
*/ */
public function listShares(); public function listShares(): array;
/** public function getShare(string $name): IShare;
* @param string $name
* @return \Icewind\SMB\IShare
*/
public function getShare($name);
/** public function getTimeZone(): string;
* @return string
*/
public function getTimeZone();
/** public function getSystem(): ISystem;
* @return ISystem
*/
public function getSystem();
/** public function getOptions(): IOptions;
* @return IOptions
*/
public function getOptions();
/** public static function available(ISystem $system): bool;
* @param ISystem $system
* @return bool
*/
public static function available(ISystem $system);
} }

View File

@ -7,13 +7,18 @@
namespace Icewind\SMB; namespace Icewind\SMB;
use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\Exception\InvalidRequestException;
use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NotFoundException;
interface IShare { interface IShare {
/** /**
* Get the name of the share * Get the name of the share
* *
* @return string * @return string
*/ */
public function getName(); public function getName(): string;
/** /**
* Download a remote file * Download a remote file
@ -22,10 +27,10 @@ interface IShare {
* @param string $target local file * @param string $target local file
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function get($source, $target); public function get(string $source, string $target): bool;
/** /**
* Upload a local file * Upload a local file
@ -34,10 +39,10 @@ interface IShare {
* @param string $target remove file * @param string $target remove file
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function put($source, $target); public function put(string $source, string $target): bool;
/** /**
* Open a readable stream top a remote file * Open a readable stream top a remote file
@ -45,10 +50,10 @@ interface IShare {
* @param string $source * @param string $source
* @return resource a read only stream with the contents of the remote file * @return resource a read only stream with the contents of the remote file
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function read($source); public function read(string $source);
/** /**
* Open a writable stream to a remote file * Open a writable stream to a remote file
@ -57,10 +62,10 @@ interface IShare {
* @param string $target * @param string $target
* @return resource a write only stream to upload a remote file * @return resource a write only stream to upload a remote file
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function write($target); public function write(string $target);
/** /**
* Open a writable stream to a remote file and set the cursor to the end of the file * Open a writable stream to a remote file and set the cursor to the end of the file
@ -68,11 +73,11 @@ interface IShare {
* @param string $target * @param string $target
* @return resource a write only stream to upload a remote file * @return resource a write only stream to upload a remote file
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
* @throws \Icewind\SMB\Exception\InvalidRequestException * @throws InvalidRequestException
*/ */
public function append($target); public function append(string $target);
/** /**
* Rename a remote file * Rename a remote file
@ -81,10 +86,10 @@ interface IShare {
* @param string $to * @param string $to
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\AlreadyExistsException * @throws AlreadyExistsException
*/ */
public function rename($from, $to); public function rename(string $from, string $to): bool;
/** /**
* Delete a file on the share * Delete a file on the share
@ -92,29 +97,29 @@ interface IShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function del($path); public function del(string $path): bool;
/** /**
* List the content of a remote folder * List the content of a remote folder
* *
* @param $path * @param string $path
* @return \Icewind\SMB\IFileInfo[] * @return IFileInfo[]
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function dir($path); public function dir(string $path): array;
/** /**
* @param string $path * @param string $path
* @return \Icewind\SMB\IFileInfo * @return IFileInfo
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
*/ */
public function stat($path); public function stat(string $path): IFileInfo;
/** /**
* Create a folder on the share * Create a folder on the share
@ -122,10 +127,10 @@ interface IShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\AlreadyExistsException * @throws AlreadyExistsException
*/ */
public function mkdir($path); public function mkdir(string $path): bool;
/** /**
* Remove a folder on the share * Remove a folder on the share
@ -133,23 +138,23 @@ interface IShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function rmdir($path); public function rmdir(string $path): bool;
/** /**
* @param string $path * @param string $path
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL * @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
* @return mixed * @return mixed
*/ */
public function setMode($path, $mode); public function setMode(string $path, int $mode);
/** /**
* @param string $path * @param string $path
* @return INotifyHandler * @return INotifyHandler
*/ */
public function notify($path); public function notify(string $path);
/** /**
* Get the IServer instance for this share * Get the IServer instance for this share

View File

@ -32,47 +32,47 @@ interface ISystem {
* @param int $num the file descriptor id * @param int $num the file descriptor id
* @return string * @return string
*/ */
public function getFD($num); public function getFD(int $num): string;
/** /**
* Get the full path to the `smbclient` binary of false if the binary is not available * Get the full path to the `smbclient` binary of null if the binary is not available
* *
* @return string|bool * @return string|null
*/ */
public function getSmbclientPath(); public function getSmbclientPath(): ?string;
/** /**
* Get the full path to the `net` binary of false if the binary is not available * Get the full path to the `net` binary of null if the binary is not available
* *
* @return string|bool * @return string|null
*/ */
public function getNetPath(); public function getNetPath(): ?string;
/** /**
* Get the full path to the `smbcacls` binary of false if the binary is not available * Get the full path to the `smbcacls` binary of null if the binary is not available
* *
* @return string|bool * @return string|null
*/ */
public function getSmbcAclsPath(); public function getSmbcAclsPath(): ?string;
/** /**
* Get the full path to the `stdbuf` binary of false if the binary is not available * Get the full path to the `stdbuf` binary of null if the binary is not available
* *
* @return string|bool * @return string|null
*/ */
public function getStdBufPath(); public function getStdBufPath(): ?string;
/** /**
* Get the full path to the `date` binary of false if the binary is not available * Get the full path to the `date` binary of null if the binary is not available
* *
* @return string|bool * @return string|null
*/ */
public function getDatePath(); public function getDatePath(): ?string;
/** /**
* Whether or not the smbclient php extension is enabled * Whether or not the smbclient php extension is enabled
* *
* @return bool * @return bool
*/ */
public function libSmbclientAvailable(); public function libSmbclientAvailable(): bool;
} }

View File

@ -28,5 +28,5 @@ interface ITimeZoneProvider {
* @param string $host * @param string $host
* @return string * @return string
*/ */
public function get($host); public function get(string $host): string;
} }

View File

@ -25,23 +25,23 @@ namespace Icewind\SMB;
* Use existing kerberos ticket to authenticate * Use existing kerberos ticket to authenticate
*/ */
class KerberosAuth implements IAuth { class KerberosAuth implements IAuth {
public function getUsername() { public function getUsername(): ?string {
return 'dummy'; return 'dummy';
} }
public function getWorkgroup() { public function getWorkgroup(): ?string {
return 'dummy'; return 'dummy';
} }
public function getPassword() { public function getPassword(): ?string {
return null; return null;
} }
public function getExtraCommandLineArguments() { public function getExtraCommandLineArguments(): string {
return '-k'; return '-k';
} }
public function setExtraSmbClientOptions($smbClientState) { public function setExtraSmbClientOptions($smbClientState): void {
smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true); smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true);
smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false); smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false);
} }

View File

@ -8,88 +8,71 @@
namespace Icewind\SMB\Native; namespace Icewind\SMB\Native;
use Icewind\SMB\ACL; use Icewind\SMB\ACL;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\IFileInfo; use Icewind\SMB\IFileInfo;
class NativeFileInfo implements IFileInfo { class NativeFileInfo implements IFileInfo {
/** /** @var string */
* @var string
*/
protected $path; protected $path;
/** @var string */
/**
* @var string
*/
protected $name; protected $name;
/** @var NativeShare */
/**
* @var NativeShare
*/
protected $share; protected $share;
/** @var array{"mode": int, "size": int, "write_time": int}|null */
/**
* @var array|null
*/
protected $attributeCache = null; protected $attributeCache = null;
/** public function __construct(NativeShare $share, string $path, string $name) {
* @param NativeShare $share
* @param string $path
* @param string $name
*/
public function __construct($share, $path, $name) {
$this->share = $share; $this->share = $share;
$this->path = $path; $this->path = $path;
$this->name = $name; $this->name = $name;
} }
/** public function getPath(): string {
* @return string
*/
public function getPath() {
return $this->path; return $this->path;
} }
/** public function getName(): string {
* @return string
*/
public function getName() {
return $this->name; return $this->name;
} }
/** /**
* @return array * @return array{"mode": int, "size": int, "write_time": int}
*/ */
protected function stat() { protected function stat(): array {
if (is_null($this->attributeCache)) { if (is_null($this->attributeCache)) {
$rawAttributes = explode(',', $this->share->getAttribute($this->path, 'system.dos_attr.*')); $rawAttributes = explode(',', $this->share->getAttribute($this->path, 'system.dos_attr.*'));
$this->attributeCache = []; $attributes = [];
foreach ($rawAttributes as $rawAttribute) { foreach ($rawAttributes as $rawAttribute) {
list($name, $value) = explode(':', $rawAttribute); list($name, $value) = explode(':', $rawAttribute);
$name = strtolower($name); $name = strtolower($name);
if ($name == 'mode') { if ($name == 'mode') {
$this->attributeCache[$name] = (int)hexdec(substr($value, 2)); $attributes[$name] = (int)hexdec(substr($value, 2));
} else { } else {
$this->attributeCache[$name] = (int)$value; $attributes[$name] = (int)$value;
} }
} }
if (!isset($attributes['mode'])) {
throw new Exception("Invalid attribute response");
}
if (!isset($attributes['size'])) {
throw new Exception("Invalid attribute response");
}
if (!isset($attributes['write_time'])) {
throw new Exception("Invalid attribute response");
}
$this->attributeCache = $attributes;
} }
return $this->attributeCache; return $this->attributeCache;
} }
/** public function getSize(): int {
* @return int
*/
public function getSize() {
$stat = $this->stat(); $stat = $this->stat();
return $stat['size']; return $stat['size'];
} }
/** public function getMTime(): int {
* @return int
*/
public function getMTime() {
$stat = $this->stat(); $stat = $this->stat();
return $stat['change_time']; return $stat['write_time'];
} }
/** /**
@ -104,22 +87,16 @@ class NativeFileInfo implements IFileInfo {
* as false (except for `hidden` where we use the unix dotfile convention) * as false (except for `hidden` where we use the unix dotfile convention)
*/ */
/** protected function getMode(): int {
* @return int
*/
protected function getMode() {
$mode = $this->stat()['mode']; $mode = $this->stat()['mode'];
// Let us ignore the ATTR_NOT_CONTENT_INDEXED for now // Let us ignore the ATTR_NOT_CONTENT_INDEXED for now
$mode &= ~0x00002000; $mode &= ~0x00002000;
return $mode; return $mode;
} }
/** public function isDirectory(): bool {
* @return bool
*/
public function isDirectory() {
$mode = $this->getMode(); $mode = $this->getMode();
if ($mode > 0x1000) { if ($mode > 0x1000) {
return (bool)($mode & 0x4000); // 0x4000: unix directory flag return (bool)($mode & 0x4000); // 0x4000: unix directory flag
@ -128,10 +105,7 @@ class NativeFileInfo implements IFileInfo {
} }
} }
/** public function isReadOnly(): bool {
* @return bool
*/
public function isReadOnly() {
$mode = $this->getMode(); $mode = $this->getMode();
if ($mode > 0x1000) { if ($mode > 0x1000) {
return !(bool)($mode & 0x80); // 0x80: owner write permissions return !(bool)($mode & 0x80); // 0x80: owner write permissions
@ -140,10 +114,7 @@ class NativeFileInfo implements IFileInfo {
} }
} }
/** public function isHidden(): bool {
* @return bool
*/
public function isHidden() {
$mode = $this->getMode(); $mode = $this->getMode();
if ($mode > 0x1000) { if ($mode > 0x1000) {
return strlen($this->name) > 0 && $this->name[0] === '.'; return strlen($this->name) > 0 && $this->name[0] === '.';
@ -152,10 +123,7 @@ class NativeFileInfo implements IFileInfo {
} }
} }
/** public function isSystem(): bool {
* @return bool
*/
public function isSystem() {
$mode = $this->getMode(); $mode = $this->getMode();
if ($mode > 0x1000) { if ($mode > 0x1000) {
return false; return false;
@ -164,10 +132,7 @@ class NativeFileInfo implements IFileInfo {
} }
} }
/** public function isArchived(): bool {
* @return bool
*/
public function isArchived() {
$mode = $this->getMode(); $mode = $this->getMode();
if ($mode > 0x1000) { if ($mode > 0x1000) {
return false; return false;
@ -185,10 +150,11 @@ class NativeFileInfo implements IFileInfo {
foreach (explode(',', $attribute) as $acl) { foreach (explode(',', $attribute) as $acl) {
list($user, $permissions) = explode(':', $acl, 2); list($user, $permissions) = explode(':', $acl, 2);
$user = trim($user, '\\');
list($type, $flags, $mask) = explode('/', $permissions); list($type, $flags, $mask) = explode('/', $permissions);
$mask = hexdec($mask); $mask = hexdec($mask);
$acls[$user] = new ACL($type, $flags, $mask); $acls[$user] = new ACL((int)$type, (int)$flags, (int)$mask);
} }
return $acls; return $acls;

View File

@ -7,64 +7,54 @@
namespace Icewind\SMB\Native; namespace Icewind\SMB\Native;
use Icewind\SMB\StringBuffer;
/** /**
* Stream optimized for read only usage * Stream optimized for read only usage
*/ */
class NativeReadStream extends NativeStream { class NativeReadStream extends NativeStream {
const CHUNK_SIZE = 1048576; // 1MB chunks const CHUNK_SIZE = 1048576; // 1MB chunks
/**
* @var resource
*/
private $readBuffer = null;
private $bufferSize = 0; /** @var StringBuffer */
private $readBuffer;
public function __construct() {
$this->readBuffer = new StringBuffer();
}
/** @var int */
private $pos = 0; private $pos = 0;
public function stream_open($path, $mode, $options, &$opened_path) { public function stream_open($path, $mode, $options, &$opened_path) {
$this->readBuffer = fopen('php://memory', 'r+');
return parent::stream_open($path, $mode, $options, $opened_path); return parent::stream_open($path, $mode, $options, $opened_path);
} }
/** /**
* Wrap a stream from libsmbclient-php into a regular php stream * Wrap a stream from libsmbclient-php into a regular php stream
* *
* @param \Icewind\SMB\NativeState $state * @param NativeState $state
* @param resource $smbStream * @param resource $smbStream
* @param string $mode * @param string $mode
* @param string $url * @param string $url
* @return resource * @return resource
*/ */
public static function wrap($state, $smbStream, $mode, $url) { public static function wrap(NativeState $state, $smbStream, string $mode, string $url) {
stream_wrapper_register('nativesmb', NativeReadStream::class); return parent::wrapClass($state, $smbStream, $mode, $url, NativeReadStream::class);
$context = stream_context_create([
'nativesmb' => [
'state' => $state,
'handle' => $smbStream,
'url' => $url
]
]);
$fh = fopen('nativesmb://', $mode, false, $context);
stream_wrapper_unregister('nativesmb');
return $fh;
} }
public function stream_read($count) { public function stream_read($count) {
// php reads 8192 bytes at once // php reads 8192 bytes at once
// however due to network latency etc, it's faster to read in larger chunks // however due to network latency etc, it's faster to read in larger chunks
// and buffer the result // and buffer the result
if (!parent::stream_eof() && $this->bufferSize < $count) { if (!parent::stream_eof() && $this->readBuffer->remaining() < $count) {
$remaining = $this->readBuffer; $chunk = parent::stream_read(self::CHUNK_SIZE);
$this->readBuffer = fopen('php://memory', 'r+'); if ($chunk === false) {
$this->bufferSize = 0; return false;
stream_copy_to_stream($remaining, $this->readBuffer); }
$this->bufferSize += fwrite($this->readBuffer, parent::stream_read(self::CHUNK_SIZE)); $this->readBuffer->push($chunk);
fseek($this->readBuffer, 0);
} }
$result = fread($this->readBuffer, $count); $result = $this->readBuffer->read($count);
$this->bufferSize -= $count;
$read = strlen($result); $read = strlen($result);
$this->pos += $read; $this->pos += $read;
@ -75,15 +65,18 @@ class NativeReadStream extends NativeStream {
public function stream_seek($offset, $whence = SEEK_SET) { public function stream_seek($offset, $whence = SEEK_SET) {
$result = parent::stream_seek($offset, $whence); $result = parent::stream_seek($offset, $whence);
if ($result) { if ($result) {
$this->readBuffer = fopen('php://memory', 'r+'); $this->readBuffer->clear();
$this->bufferSize = 0; $pos = parent::stream_tell();
$this->pos = parent::stream_tell(); if ($pos === false) {
return false;
}
$this->pos = $pos;
} }
return $result; return $result;
} }
public function stream_eof() { public function stream_eof() {
return $this->bufferSize <= 0 && parent::stream_eof(); return $this->readBuffer->remaining() <= 0 && parent::stream_eof();
} }
public function stream_tell() { public function stream_tell() {

View File

@ -8,10 +8,13 @@
namespace Icewind\SMB\Native; namespace Icewind\SMB\Native;
use Icewind\SMB\AbstractServer; use Icewind\SMB\AbstractServer;
use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\IAuth; use Icewind\SMB\IAuth;
use Icewind\SMB\IOptions; use Icewind\SMB\IOptions;
use Icewind\SMB\IShare;
use Icewind\SMB\ISystem; use Icewind\SMB\ISystem;
use Icewind\SMB\TimeZoneProvider; use Icewind\SMB\ITimeZoneProvider;
class NativeServer extends AbstractServer { class NativeServer extends AbstractServer {
/** /**
@ -19,38 +22,34 @@ class NativeServer extends AbstractServer {
*/ */
protected $state; protected $state;
public function __construct($host, IAuth $auth, ISystem $system, TimeZoneProvider $timeZoneProvider, IOptions $options) { public function __construct(string $host, IAuth $auth, ISystem $system, ITimeZoneProvider $timeZoneProvider, IOptions $options) {
parent::__construct($host, $auth, $system, $timeZoneProvider, $options); parent::__construct($host, $auth, $system, $timeZoneProvider, $options);
$this->state = new NativeState(); $this->state = new NativeState();
} }
protected function connect() { protected function connect(): void {
$this->state->init($this->getAuth(), $this->getOptions()); $this->state->init($this->getAuth(), $this->getOptions());
} }
/** /**
* @return \Icewind\SMB\IShare[] * @return IShare[]
* @throws \Icewind\SMB\Exception\AuthenticationException * @throws AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidHostException * @throws InvalidHostException
*/ */
public function listShares() { public function listShares(): array {
$this->connect(); $this->connect();
$shares = []; $shares = [];
$dh = $this->state->opendir('smb://' . $this->getHost()); $dh = $this->state->opendir('smb://' . $this->getHost());
while ($share = $this->state->readdir($dh)) { while ($share = $this->state->readdir($dh, '')) {
if ($share['type'] === 'file share') { if ($share['type'] === 'file share') {
$shares[] = $this->getShare($share['name']); $shares[] = $this->getShare($share['name']);
} }
} }
$this->state->closedir($dh); $this->state->closedir($dh, '');
return $shares; return $shares;
} }
/** public function getShare(string $name): IShare {
* @param string $name
* @return \Icewind\SMB\IShare
*/
public function getShare($name) {
return new NativeShare($this, $name); return new NativeShare($this, $name);
} }
@ -60,7 +59,7 @@ class NativeServer extends AbstractServer {
* @param ISystem $system * @param ISystem $system
* @return bool * @return bool
*/ */
public static function available(ISystem $system) { public static function available(ISystem $system): bool {
return $system->libSmbclientAvailable(); return $system->libSmbclientAvailable();
} }
} }

View File

@ -8,9 +8,16 @@
namespace Icewind\SMB\Native; namespace Icewind\SMB\Native;
use Icewind\SMB\AbstractShare; use Icewind\SMB\AbstractShare;
use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\DependencyException; use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\Exception\InvalidPathException; use Icewind\SMB\Exception\InvalidPathException;
use Icewind\SMB\Exception\InvalidResourceException; use Icewind\SMB\Exception\InvalidResourceException;
use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\IFileInfo;
use Icewind\SMB\INotifyHandler; use Icewind\SMB\INotifyHandler;
use Icewind\SMB\IServer; use Icewind\SMB\IServer;
use Icewind\SMB\Wrapped\Server; use Icewind\SMB\Wrapped\Server;
@ -27,28 +34,22 @@ class NativeShare extends AbstractShare {
*/ */
private $name; private $name;
/** /** @var NativeState|null $state */
* @var NativeState $state private $state = null;
*/
private $state;
/** public function __construct(IServer $server, string $name) {
* @param IServer $server
* @param string $name
*/
public function __construct($server, $name) {
parent::__construct(); parent::__construct();
$this->server = $server; $this->server = $server;
$this->name = $name; $this->name = $name;
} }
/** /**
* @throws \Icewind\SMB\Exception\ConnectionException * @throws ConnectionException
* @throws \Icewind\SMB\Exception\AuthenticationException * @throws AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidHostException * @throws InvalidHostException
*/ */
protected function getState() { protected function getState(): NativeState {
if ($this->state and $this->state instanceof NativeState) { if ($this->state) {
return $this->state; return $this->state;
} }
@ -62,11 +63,11 @@ class NativeShare extends AbstractShare {
* *
* @return string * @return string
*/ */
public function getName() { public function getName(): string {
return $this->name; return $this->name;
} }
private function buildUrl($path) { private function buildUrl(string $path): string {
$this->verifyPath($path); $this->verifyPath($path);
$url = sprintf('smb://%s/%s', $this->server->getHost(), $this->name); $url = sprintf('smb://%s/%s', $this->server->getHost(), $this->name);
if ($path) { if ($path) {
@ -81,16 +82,16 @@ class NativeShare extends AbstractShare {
* List the content of a remote folder * List the content of a remote folder
* *
* @param string $path * @param string $path
* @return \Icewind\SMB\IFileInfo[] * @return IFileInfo[]
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function dir($path) { public function dir(string $path): array {
$files = []; $files = [];
$dh = $this->getState()->opendir($this->buildUrl($path)); $dh = $this->getState()->opendir($this->buildUrl($path));
while ($file = $this->getState()->readdir($dh)) { while ($file = $this->getState()->readdir($dh, $path)) {
$name = $file['name']; $name = $file['name'];
if ($name !== '.' and $name !== '..') { if ($name !== '.' and $name !== '..') {
$fullPath = $path . '/' . $name; $fullPath = $path . '/' . $name;
@ -98,15 +99,15 @@ class NativeShare extends AbstractShare {
} }
} }
$this->getState()->closedir($dh); $this->getState()->closedir($dh, $path);
return $files; return $files;
} }
/** /**
* @param string $path * @param string $path
* @return \Icewind\SMB\IFileInfo * @return IFileInfo
*/ */
public function stat($path) { public function stat(string $path): IFileInfo {
$info = new NativeFileInfo($this, $path, self::mb_basename($path)); $info = new NativeFileInfo($this, $path, self::mb_basename($path));
// trigger attribute loading // trigger attribute loading
@ -122,7 +123,7 @@ class NativeShare extends AbstractShare {
* @link http://php.net/manual/en/function.basename.php#121405 * @link http://php.net/manual/en/function.basename.php#121405
* @return string * @return string
*/ */
protected static function mb_basename($path) { protected static function mb_basename(string $path): string {
if (preg_match('@^.*[\\\\/]([^\\\\/]+)$@s', $path, $matches)) { if (preg_match('@^.*[\\\\/]([^\\\\/]+)$@s', $path, $matches)) {
return $matches[1]; return $matches[1];
} elseif (preg_match('@^([^\\\\/]+)$@s', $path, $matches)) { } elseif (preg_match('@^([^\\\\/]+)$@s', $path, $matches)) {
@ -138,10 +139,10 @@ class NativeShare extends AbstractShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\AlreadyExistsException * @throws AlreadyExistsException
*/ */
public function mkdir($path) { public function mkdir(string $path): bool {
return $this->getState()->mkdir($this->buildUrl($path)); return $this->getState()->mkdir($this->buildUrl($path));
} }
@ -151,10 +152,10 @@ class NativeShare extends AbstractShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function rmdir($path) { public function rmdir(string $path): bool {
return $this->getState()->rmdir($this->buildUrl($path)); return $this->getState()->rmdir($this->buildUrl($path));
} }
@ -164,10 +165,10 @@ class NativeShare extends AbstractShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function del($path) { public function del(string $path): bool {
return $this->getState()->unlink($this->buildUrl($path)); return $this->getState()->unlink($this->buildUrl($path));
} }
@ -178,10 +179,10 @@ class NativeShare extends AbstractShare {
* @param string $to * @param string $to
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\AlreadyExistsException * @throws AlreadyExistsException
*/ */
public function rename($from, $to) { public function rename(string $from, string $to): bool {
return $this->getState()->rename($this->buildUrl($from), $this->buildUrl($to)); return $this->getState()->rename($this->buildUrl($from), $this->buildUrl($to));
} }
@ -192,10 +193,10 @@ class NativeShare extends AbstractShare {
* @param string $target remove file * @param string $target remove file
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function put($source, $target) { public function put(string $source, string $target): bool {
$sourceHandle = fopen($source, 'rb'); $sourceHandle = fopen($source, 'rb');
$targetUrl = $this->buildUrl($target); $targetUrl = $this->buildUrl($target);
@ -215,20 +216,18 @@ class NativeShare extends AbstractShare {
* @param string $target local file * @param string $target local file
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws ConnectionException
* @throws \Icewind\SMB\Exception\InvalidPathException * @throws InvalidHostException
* @throws \Icewind\SMB\Exception\InvalidResourceException * @throws InvalidPathException
* @throws InvalidResourceException
*/ */
public function get($source, $target) { public function get(string $source, string $target): bool {
if (!$target) { if (!$target) {
throw new InvalidPathException('Invalid target path: Filename cannot be empty'); throw new InvalidPathException('Invalid target path: Filename cannot be empty');
} }
$sourceHandle = $this->getState()->open($this->buildUrl($source), 'r'); $sourceHandle = $this->getState()->open($this->buildUrl($source), 'r');
if (!$sourceHandle) {
throw new InvalidResourceException('Failed opening remote file "' . $source . '" for reading');
}
$targetHandle = @fopen($target, 'wb'); $targetHandle = @fopen($target, 'wb');
if (!$targetHandle) { if (!$targetHandle) {
@ -242,7 +241,7 @@ class NativeShare extends AbstractShare {
throw new InvalidResourceException('Failed opening local file "' . $target . '" for writing: ' . $reason); throw new InvalidResourceException('Failed opening local file "' . $target . '" for writing: ' . $reason);
} }
while ($data = $this->getState()->read($sourceHandle, NativeReadStream::CHUNK_SIZE)) { while ($data = $this->getState()->read($sourceHandle, NativeReadStream::CHUNK_SIZE, $source)) {
fwrite($targetHandle, $data); fwrite($targetHandle, $data);
} }
$this->getState()->close($sourceHandle, $this->buildUrl($source)); $this->getState()->close($sourceHandle, $this->buildUrl($source));
@ -255,10 +254,10 @@ class NativeShare extends AbstractShare {
* @param string $source * @param string $source
* @return resource a read only stream with the contents of the remote file * @return resource a read only stream with the contents of the remote file
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function read($source) { public function read(string $source) {
$url = $this->buildUrl($source); $url = $this->buildUrl($source);
$handle = $this->getState()->open($url, 'r'); $handle = $this->getState()->open($url, 'r');
return NativeReadStream::wrap($this->getState(), $handle, 'r', $url); return NativeReadStream::wrap($this->getState(), $handle, 'r', $url);
@ -271,10 +270,10 @@ class NativeShare extends AbstractShare {
* @param string $source * @param string $source
* @return resource a writeable stream * @return resource a writeable stream
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function write($source) { public function write(string $source) {
$url = $this->buildUrl($source); $url = $this->buildUrl($source);
$handle = $this->getState()->create($url); $handle = $this->getState()->create($url);
return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url); return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url);
@ -286,10 +285,10 @@ class NativeShare extends AbstractShare {
* @param string $source * @param string $source
* @return resource a writeable stream * @return resource a writeable stream
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function append($source) { public function append(string $source) {
$url = $this->buildUrl($source); $url = $this->buildUrl($source);
$handle = $this->getState()->open($url, "a+"); $handle = $this->getState()->open($url, "a+");
return NativeWriteStream::wrap($this->getState(), $handle, "a", $url); return NativeWriteStream::wrap($this->getState(), $handle, "a", $url);
@ -302,7 +301,7 @@ class NativeShare extends AbstractShare {
* @param string $attribute attribute to get the info * @param string $attribute attribute to get the info
* @return string the attribute value * @return string the attribute value
*/ */
public function getAttribute($path, $attribute) { public function getAttribute(string $path, string $attribute): string {
return $this->getState()->getxattr($this->buildUrl($path), $attribute); return $this->getState()->getxattr($this->buildUrl($path), $attribute);
} }
@ -314,9 +313,13 @@ class NativeShare extends AbstractShare {
* @param string|int $value * @param string|int $value
* @return mixed the attribute value * @return mixed the attribute value
*/ */
public function setAttribute($path, $attribute, $value) { public function setAttribute(string $path, string $attribute, $value) {
if ($attribute === 'system.dos_attr.mode' and is_int($value)) { if (is_int($value)) {
$value = '0x' . dechex($value); if ($attribute === 'system.dos_attr.mode') {
$value = '0x' . dechex($value);
} else {
throw new \InvalidArgumentException("Invalid value for attribute");
}
} }
return $this->getState()->setxattr($this->buildUrl($path), $attribute, $value); return $this->getState()->setxattr($this->buildUrl($path), $attribute, $value);
@ -329,7 +332,7 @@ class NativeShare extends AbstractShare {
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL * @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
* @return mixed * @return mixed
*/ */
public function setMode($path, $mode) { public function setMode(string $path, int $mode) {
return $this->setAttribute($path, 'system.dos_attr.mode', $mode); return $this->setAttribute($path, 'system.dos_attr.mode', $mode);
} }
@ -340,7 +343,7 @@ class NativeShare extends AbstractShare {
* @param string $path * @param string $path
* @return INotifyHandler * @return INotifyHandler
*/ */
public function notify($path) { public function notify(string $path): INotifyHandler {
// php-smbclient does not support notify (https://github.com/eduardok/libsmbclient-php/issues/29) // php-smbclient does not support notify (https://github.com/eduardok/libsmbclient-php/issues/29)
// so we use the smbclient based backend for this // so we use the smbclient based backend for this
if (!Server::available($this->server->getSystem())) { if (!Server::available($this->server->getSystem())) {

View File

@ -29,13 +29,13 @@ use Icewind\SMB\IOptions;
* Low level wrapper for libsmbclient-php with error handling * Low level wrapper for libsmbclient-php with error handling
*/ */
class NativeState { class NativeState {
/** /** @var resource|null */
* @var resource protected $state = null;
*/
protected $state;
/** @var bool */
protected $handlerSet = false; protected $handlerSet = false;
/** @var bool */
protected $connected = false; protected $connected = false;
// see error.h // see error.h
@ -58,7 +58,8 @@ class NativeState {
113 => NoRouteToHostException::class 113 => NoRouteToHostException::class
]; ];
protected function handleError($path) { protected function handleError(?string $path): void {
/** @var int $error */
$error = smbclient_state_errno($this->state); $error = smbclient_state_errno($this->state);
if ($error === 0) { if ($error === 0) {
return; return;
@ -66,14 +67,19 @@ class NativeState {
throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path); throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
} }
protected function testResult($result, $uri) { /**
* @param mixed $result
* @param string|null $uri
* @throws Exception
*/
protected function testResult($result, ?string $uri): void {
if ($result === false or $result === null) { if ($result === false or $result === null) {
// smb://host/share/path // smb://host/share/path
if (is_string($uri) && count(explode('/', $uri, 5)) > 4) { if (is_string($uri) && count(explode('/', $uri, 5)) > 4) {
list(, , , , $path) = explode('/', $uri, 5); list(, , , , $path) = explode('/', $uri, 5);
$path = '/' . $path; $path = '/' . $path;
} else { } else {
$path = null; $path = $uri;
} }
$this->handleError($path); $this->handleError($path);
} }
@ -88,10 +94,21 @@ class NativeState {
if ($this->connected) { if ($this->connected) {
return true; return true;
} }
$this->state = smbclient_state_new(); /** @var resource $state */
$state = smbclient_state_new();
$this->state = $state;
smbclient_option_set($this->state, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, false); smbclient_option_set($this->state, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, false);
smbclient_option_set($this->state, SMBCLIENT_OPT_TIMEOUT, $options->getTimeout() * 1000); smbclient_option_set($this->state, SMBCLIENT_OPT_TIMEOUT, $options->getTimeout() * 1000);
if (function_exists('smbclient_client_protocols')) {
$maxProtocol = $options->getMaxProtocol();
$minProtocol = $options->getMinProtocol();
smbclient_client_protocols($this->state, $minProtocol, $maxProtocol);
}
$auth->setExtraSmbClientOptions($this->state); $auth->setExtraSmbClientOptions($this->state);
/** @var bool $result */
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword()); $result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());
$this->testResult($result, ''); $this->testResult($result, '');
@ -103,7 +120,8 @@ class NativeState {
* @param string $uri * @param string $uri
* @return resource * @return resource
*/ */
public function opendir($uri) { public function opendir(string $uri) {
/** @var resource $result */
$result = @smbclient_opendir($this->state, $uri); $result = @smbclient_opendir($this->state, $uri);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -112,23 +130,27 @@ class NativeState {
/** /**
* @param resource $dir * @param resource $dir
* @return array * @param string $path
* @return array{"type": string, "comment": string, "name": string}|false
*/ */
public function readdir($dir) { public function readdir($dir, string $path) {
/** @var array{"type": string, "comment": string, "name": string}|false $result */
$result = @smbclient_readdir($this->state, $dir); $result = @smbclient_readdir($this->state, $dir);
$this->testResult($result, $dir); $this->testResult($result, $path);
return $result; return $result;
} }
/** /**
* @param $dir * @param resource $dir
* @param string $path
* @return bool * @return bool
*/ */
public function closedir($dir) { public function closedir($dir, string $path): bool {
/** @var bool $result */
$result = smbclient_closedir($this->state, $dir); $result = smbclient_closedir($this->state, $dir);
$this->testResult($result, $dir); $this->testResult($result, $path);
return $result; return $result;
} }
@ -137,7 +159,8 @@ class NativeState {
* @param string $new * @param string $new
* @return bool * @return bool
*/ */
public function rename($old, $new) { public function rename(string $old, string $new): bool {
/** @var bool $result */
$result = @smbclient_rename($this->state, $old, $this->state, $new); $result = @smbclient_rename($this->state, $old, $this->state, $new);
$this->testResult($result, $new); $this->testResult($result, $new);
@ -148,7 +171,8 @@ class NativeState {
* @param string $uri * @param string $uri
* @return bool * @return bool
*/ */
public function unlink($uri) { public function unlink(string $uri): bool {
/** @var bool $result */
$result = @smbclient_unlink($this->state, $uri); $result = @smbclient_unlink($this->state, $uri);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -160,7 +184,8 @@ class NativeState {
* @param int $mask * @param int $mask
* @return bool * @return bool
*/ */
public function mkdir($uri, $mask = 0777) { public function mkdir(string $uri, int $mask = 0777): bool {
/** @var bool $result */
$result = @smbclient_mkdir($this->state, $uri, $mask); $result = @smbclient_mkdir($this->state, $uri, $mask);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -171,7 +196,8 @@ class NativeState {
* @param string $uri * @param string $uri
* @return bool * @return bool
*/ */
public function rmdir($uri) { public function rmdir(string $uri): bool {
/** @var bool $result */
$result = @smbclient_rmdir($this->state, $uri); $result = @smbclient_rmdir($this->state, $uri);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -180,9 +206,10 @@ class NativeState {
/** /**
* @param string $uri * @param string $uri
* @return array * @return array{"mtime": int, "size": int, "mode": int}
*/ */
public function stat($uri) { public function stat(string $uri): array {
/** @var array{"mtime": int, "size": int, "mode": int} $result */
$result = @smbclient_stat($this->state, $uri); $result = @smbclient_stat($this->state, $uri);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -191,12 +218,14 @@ class NativeState {
/** /**
* @param resource $file * @param resource $file
* @return array * @param string $path
* @return array{"mtime": int, "size": int, "mode": int}
*/ */
public function fstat($file) { public function fstat($file, string $path): array {
/** @var array{"mtime": int, "size": int, "mode": int} $result */
$result = @smbclient_fstat($this->state, $file); $result = @smbclient_fstat($this->state, $file);
$this->testResult($result, $file); $this->testResult($result, $path);
return $result; return $result;
} }
@ -206,7 +235,8 @@ class NativeState {
* @param int $mask * @param int $mask
* @return resource * @return resource
*/ */
public function open($uri, $mode, $mask = 0666) { public function open(string $uri, string $mode, int $mask = 0666) {
/** @var resource $result */
$result = @smbclient_open($this->state, $uri, $mode, $mask); $result = @smbclient_open($this->state, $uri, $mode, $mask);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -218,7 +248,8 @@ class NativeState {
* @param int $mask * @param int $mask
* @return resource * @return resource
*/ */
public function create($uri, $mask = 0666) { public function create(string $uri, int $mask = 0666) {
/** @var resource $result */
$result = @smbclient_creat($this->state, $uri, $mask); $result = @smbclient_creat($this->state, $uri, $mask);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -228,12 +259,14 @@ class NativeState {
/** /**
* @param resource $file * @param resource $file
* @param int $bytes * @param int $bytes
* @param string $path
* @return string * @return string
*/ */
public function read($file, $bytes) { public function read($file, int $bytes, string $path): string {
/** @var string $result */
$result = @smbclient_read($this->state, $file, $bytes); $result = @smbclient_read($this->state, $file, $bytes);
$this->testResult($result, $file); $this->testResult($result, $path);
return $result; return $result;
} }
@ -241,10 +274,11 @@ class NativeState {
* @param resource $file * @param resource $file
* @param string $data * @param string $data
* @param string $path * @param string $path
* @param int $length * @param int|null $length
* @return int * @return int
*/ */
public function write($file, $data, $path, $length = null) { public function write($file, string $data, string $path, ?int $length = null): int {
/** @var int $result */
$result = @smbclient_write($this->state, $file, $data, $length); $result = @smbclient_write($this->state, $file, $data, $length);
$this->testResult($result, $path); $this->testResult($result, $path);
@ -255,28 +289,38 @@ class NativeState {
* @param resource $file * @param resource $file
* @param int $offset * @param int $offset
* @param int $whence SEEK_SET | SEEK_CUR | SEEK_END * @param int $whence SEEK_SET | SEEK_CUR | SEEK_END
* @return int|bool new file offset as measured from the start of the file on success, false on failure. * @param string|null $path
* @return int|false new file offset as measured from the start of the file on success.
*/ */
public function lseek($file, $offset, $whence = SEEK_SET) { public function lseek($file, int $offset, int $whence = SEEK_SET, string $path = null) {
/** @var int|false $result */
$result = @smbclient_lseek($this->state, $file, $offset, $whence); $result = @smbclient_lseek($this->state, $file, $offset, $whence);
$this->testResult($result, $file); $this->testResult($result, $path);
return $result; return $result;
} }
/** /**
* @param resource $file * @param resource $file
* @param int $size * @param int $size
* @param string $path
* @return bool * @return bool
*/ */
public function ftruncate($file, $size) { public function ftruncate($file, int $size, string $path): bool {
/** @var bool $result */
$result = @smbclient_ftruncate($this->state, $file, $size); $result = @smbclient_ftruncate($this->state, $file, $size);
$this->testResult($result, $file); $this->testResult($result, $path);
return $result; return $result;
} }
public function close($file, $path) { /**
* @param resource $file
* @param string $path
* @return bool
*/
public function close($file, string $path): bool {
/** @var bool $result */
$result = @smbclient_close($this->state, $file); $result = @smbclient_close($this->state, $file);
$this->testResult($result, $path); $this->testResult($result, $path);
@ -288,7 +332,8 @@ class NativeState {
* @param string $key * @param string $key
* @return string * @return string
*/ */
public function getxattr($uri, $key) { public function getxattr(string $uri, string $key) {
/** @var string $result */
$result = @smbclient_getxattr($this->state, $uri, $key); $result = @smbclient_getxattr($this->state, $uri, $key);
$this->testResult($result, $uri); $this->testResult($result, $uri);
@ -300,9 +345,10 @@ class NativeState {
* @param string $key * @param string $key
* @param string $value * @param string $value
* @param int $flags * @param int $flags
* @return mixed * @return bool
*/ */
public function setxattr($uri, $key, $value, $flags = 0) { public function setxattr(string $uri, string $key, string $value, int $flags = 0) {
/** @var bool $result */
$result = @smbclient_setxattr($this->state, $uri, $key, $value, $flags); $result = @smbclient_setxattr($this->state, $uri, $key, $value, $flags);
$this->testResult($result, $uri); $this->testResult($result, $uri);

View File

@ -10,20 +10,24 @@ namespace Icewind\SMB\Native;
use Icewind\SMB\Exception\Exception; use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\InvalidRequestException; use Icewind\SMB\Exception\InvalidRequestException;
use Icewind\Streams\File; use Icewind\Streams\File;
use InvalidArgumentException;
class NativeStream implements File { abstract class NativeStream implements File {
/** /**
* @var resource * @var resource
* @psalm-suppress PropertyNotSetInConstructor
*/ */
public $context; public $context;
/** /**
* @var NativeState * @var NativeState
* @psalm-suppress PropertyNotSetInConstructor
*/ */
protected $state; protected $state;
/** /**
* @var resource * @var resource
* @psalm-suppress PropertyNotSetInConstructor
*/ */
protected $handle; protected $handle;
@ -35,19 +39,20 @@ class NativeStream implements File {
/** /**
* @var string * @var string
*/ */
protected $url; protected $url = '';
/** /**
* Wrap a stream from libsmbclient-php into a regular php stream * Wrap a stream from libsmbclient-php into a regular php stream
* *
* @param \Icewind\SMB\NativeState $state * @param NativeState $state
* @param resource $smbStream * @param resource $smbStream
* @param string $mode * @param string $mode
* @param string $url * @param string $url
* @param class-string<NativeStream> $class
* @return resource * @return resource
*/ */
public static function wrap($state, $smbStream, $mode, $url) { protected static function wrapClass(NativeState $state, $smbStream, string $mode, string $url, string $class) {
stream_wrapper_register('nativesmb', NativeStream::class); stream_wrapper_register('nativesmb', $class);
$context = stream_context_create([ $context = stream_context_create([
'nativesmb' => [ 'nativesmb' => [
'state' => $state, 'state' => $state,
@ -73,19 +78,35 @@ class NativeStream implements File {
} }
public function stream_flush() { public function stream_flush() {
return false;
} }
public function stream_open($path, $mode, $options, &$opened_path) { public function stream_open($path, $mode, $options, &$opened_path) {
$context = stream_context_get_options($this->context); $context = stream_context_get_options($this->context);
$this->state = $context['nativesmb']['state']; if (!isset($context['nativesmb']) || !is_array($context['nativesmb'])) {
$this->handle = $context['nativesmb']['handle']; throw new InvalidArgumentException("context not set");
$this->url = $context['nativesmb']['url']; }
$state = $context['nativesmb']['state'];
if (!$state instanceof NativeState) {
throw new InvalidArgumentException("invalid context set");
}
$this->state = $state;
$handle = $context['nativesmb']['handle'];
if (!is_resource($handle)) {
throw new InvalidArgumentException("invalid context set");
}
$this->handle = $handle;
$url = $context['nativesmb']['url'];
if (!is_string($url)) {
throw new InvalidArgumentException("invalid context set");
}
$this->url = $url;
return true; return true;
} }
public function stream_read($count) { public function stream_read($count) {
$result = $this->state->read($this->handle, $count); $result = $this->state->read($this->handle, $count, $this->url);
if (strlen($result) < $count) { if (strlen($result) < $count) {
$this->eof = true; $this->eof = true;
} }
@ -95,12 +116,15 @@ class NativeStream implements File {
public function stream_seek($offset, $whence = SEEK_SET) { public function stream_seek($offset, $whence = SEEK_SET) {
$this->eof = false; $this->eof = false;
try { try {
return $this->state->lseek($this->handle, $offset, $whence) !== false; return $this->state->lseek($this->handle, $offset, $whence, $this->url) !== false;
} catch (InvalidRequestException $e) { } catch (InvalidRequestException $e) {
return false; return false;
} }
} }
/**
* @return array{"mtime": int, "size": int, "mode": int}|false
*/
public function stream_stat() { public function stream_stat() {
try { try {
return $this->state->stat($this->url); return $this->state->stat($this->url);
@ -110,7 +134,7 @@ class NativeStream implements File {
} }
public function stream_tell() { public function stream_tell() {
return $this->state->lseek($this->handle, 0, SEEK_CUR); return $this->state->lseek($this->handle, 0, SEEK_CUR, $this->url);
} }
public function stream_write($data) { public function stream_write($data) {
@ -118,7 +142,7 @@ class NativeStream implements File {
} }
public function stream_truncate($size) { public function stream_truncate($size) {
return $this->state->ftruncate($this->handle, $size); return $this->state->ftruncate($this->handle, $size, $this->url);
} }
public function stream_set_option($option, $arg1, $arg2) { public function stream_set_option($option, $arg1, $arg2) {

View File

@ -7,71 +7,63 @@
namespace Icewind\SMB\Native; namespace Icewind\SMB\Native;
use Icewind\SMB\StringBuffer;
/** /**
* Stream optimized for write only usage * Stream optimized for write only usage
*/ */
class NativeWriteStream extends NativeStream { class NativeWriteStream extends NativeStream {
const CHUNK_SIZE = 1048576; // 1MB chunks const CHUNK_SIZE = 1048576; // 1MB chunks
/**
* @var resource
*/
private $writeBuffer = null;
private $bufferSize = 0; /** @var StringBuffer */
private $writeBuffer;
/** @var int */
private $pos = 0; private $pos = 0;
public function stream_open($path, $mode, $options, &$opened_path) { public function __construct() {
$this->writeBuffer = fopen('php://memory', 'r+'); $this->writeBuffer = new StringBuffer();
}
public function stream_open($path, $mode, $options, &$opened_path): bool {
return parent::stream_open($path, $mode, $options, $opened_path); return parent::stream_open($path, $mode, $options, $opened_path);
} }
/** /**
* Wrap a stream from libsmbclient-php into a regular php stream * Wrap a stream from libsmbclient-php into a regular php stream
* *
* @param \Icewind\SMB\NativeState $state * @param NativeState $state
* @param resource $smbStream * @param resource $smbStream
* @param string $mode * @param string $mode
* @param string $url * @param string $url
* @return resource * @return resource
*/ */
public static function wrap($state, $smbStream, $mode, $url) { public static function wrap(NativeState $state, $smbStream, string $mode, string $url) {
stream_wrapper_register('nativesmb', NativeWriteStream::class); return parent::wrapClass($state, $smbStream, $mode, $url, NativeWriteStream::class);
$context = stream_context_create([
'nativesmb' => [
'state' => $state,
'handle' => $smbStream,
'url' => $url
]
]);
$fh = fopen('nativesmb://', $mode, false, $context);
stream_wrapper_unregister('nativesmb');
return $fh;
} }
public function stream_seek($offset, $whence = SEEK_SET) { public function stream_seek($offset, $whence = SEEK_SET) {
$this->flushWrite(); $this->flushWrite();
$result = parent::stream_seek($offset, $whence); $result = parent::stream_seek($offset, $whence);
if ($result) { if ($result) {
$this->pos = parent::stream_tell(); $pos = parent::stream_tell();
if ($pos === false) {
return false;
}
$this->pos = $pos;
} }
return $result; return $result;
} }
private function flushWrite() { private function flushWrite(): void {
rewind($this->writeBuffer); parent::stream_write($this->writeBuffer->flush());
$this->state->write($this->handle, stream_get_contents($this->writeBuffer), $this->url);
$this->writeBuffer = fopen('php://memory', 'r+');
$this->bufferSize = 0;
} }
public function stream_write($data) { public function stream_write($data) {
$written = fwrite($this->writeBuffer, $data); $written = $this->writeBuffer->push($data);
$this->bufferSize += $written;
$this->pos += $written; $this->pos += $written;
if ($this->bufferSize >= self::CHUNK_SIZE) { if ($this->writeBuffer->remaining() >= self::CHUNK_SIZE) {
$this->flushWrite(); $this->flushWrite();
} }

View File

@ -25,11 +25,32 @@ class Options implements IOptions {
/** @var int */ /** @var int */
private $timeout = 20; private $timeout = 20;
public function getTimeout() { /** @var string|null */
private $minProtocol;
/** @var string|null */
private $maxProtocol;
public function getTimeout(): int {
return $this->timeout; return $this->timeout;
} }
public function setTimeout($timeout) { public function setTimeout(int $timeout): void {
$this->timeout = $timeout; $this->timeout = $timeout;
} }
public function getMinProtocol(): ?string {
return $this->minProtocol;
}
public function setMinProtocol(?string $minProtocol): void {
$this->minProtocol = $minProtocol;
}
public function getMaxProtocol(): ?string {
return $this->maxProtocol;
}
public function setMaxProtocol(?string $maxProtocol): void {
$this->maxProtocol = $maxProtocol;
}
} }

View File

@ -31,7 +31,7 @@ class ServerFactory {
Server::class Server::class
]; ];
/** @var System */ /** @var ISystem */
private $system; private $system;
/** @var IOptions */ /** @var IOptions */
@ -68,12 +68,12 @@ class ServerFactory {
/** /**
* @param $host * @param string $host
* @param IAuth $credentials * @param IAuth $credentials
* @return IServer * @return IServer
* @throws DependencyException * @throws DependencyException
*/ */
public function createServer($host, IAuth $credentials) { public function createServer(string $host, IAuth $credentials): IServer {
foreach (self::BACKENDS as $backend) { foreach (self::BACKENDS as $backend) {
if (call_user_func("$backend::available", $this->system)) { if (call_user_func("$backend::available", $this->system)) {
return new $backend($host, $credentials, $this->system, $this->timeZoneProvider, $this->options); return new $backend($host, $credentials, $this->system, $this->timeZoneProvider, $this->options);

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 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\SMB;
class StringBuffer {
/** @var string */
private $buffer = "";
/** @var int */
private $pos = 0;
public function clear(): void {
$this->buffer = "";
$this->pos = 0;
}
public function push(string $data): int {
$this->buffer = $this->flush() . $data;
return strlen($data);
}
public function remaining(): int {
return strlen($this->buffer) - $this->pos;
}
public function read(int $count): string {
$chunk = substr($this->buffer, $this->pos, $this->pos + $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function flush(): string {
if ($this->pos === 0) {
$remaining = $this->buffer;
} else {
$remaining = substr($this->buffer, $this->pos);
}
$this->clear();
return $remaining;
}
}

View File

@ -10,7 +10,7 @@ namespace Icewind\SMB;
use Icewind\SMB\Exception\Exception; use Icewind\SMB\Exception\Exception;
class System implements ISystem { class System implements ISystem {
/** @var (string|bool)[] */ /** @var (string|null)[] */
private $paths = []; private $paths = [];
/** /**
@ -20,7 +20,7 @@ class System implements ISystem {
* @return string * @return string
* @throws Exception * @throws Exception
*/ */
public function getFD($num) { public function getFD(int $num): string {
$folders = [ $folders = [
'/proc/self/fd', '/proc/self/fd',
'/dev/fd' '/dev/fd'
@ -33,36 +33,36 @@ class System implements ISystem {
throw new Exception('Cant find file descriptor path'); throw new Exception('Cant find file descriptor path');
} }
public function getSmbclientPath() { public function getSmbclientPath(): ?string {
return $this->getBinaryPath('smbclient'); return $this->getBinaryPath('smbclient');
} }
public function getNetPath() { public function getNetPath(): ?string {
return $this->getBinaryPath('net'); return $this->getBinaryPath('net');
} }
public function getSmbcAclsPath() { public function getSmbcAclsPath(): ?string {
return $this->getBinaryPath('smbcacls'); return $this->getBinaryPath('smbcacls');
} }
public function getStdBufPath() { public function getStdBufPath(): ?string {
return $this->getBinaryPath('stdbuf'); return $this->getBinaryPath('stdbuf');
} }
public function getDatePath() { public function getDatePath(): ?string {
return $this->getBinaryPath('date'); return $this->getBinaryPath('date');
} }
public function libSmbclientAvailable() { public function libSmbclientAvailable(): bool {
return function_exists('smbclient_state_new'); return function_exists('smbclient_state_new');
} }
protected function getBinaryPath($binary) { protected function getBinaryPath(string $binary): ?string {
if (!isset($this->paths[$binary])) { if (!isset($this->paths[$binary])) {
$result = null; $result = null;
$output = []; $output = [];
exec("which $binary 2>&1", $output, $result); exec("which $binary 2>&1", $output, $result);
$this->paths[$binary] = $result === 0 ? trim(implode('', $output)) : false; $this->paths[$binary] = $result === 0 ? trim(implode('', $output)) : null;
} }
return $this->paths[$binary]; return $this->paths[$binary];
} }

View File

@ -25,7 +25,7 @@ class TimeZoneProvider implements ITimeZoneProvider {
$this->system = $system; $this->system = $system;
} }
public function get($host) { public function get(string $host): string {
if (!isset($this->timeZones[$host])) { if (!isset($this->timeZones[$host])) {
$timeZone = null; $timeZone = null;
$net = $this->system->getNetPath(); $net = $this->system->getNetPath();

View File

@ -7,9 +7,11 @@
namespace Icewind\SMB\Wrapped; namespace Icewind\SMB\Wrapped;
use Icewind\SMB\Exception\AccessDeniedException;
use Icewind\SMB\Exception\AuthenticationException; use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\ConnectException; use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException; use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\ConnectionRefusedException;
use Icewind\SMB\Exception\InvalidHostException; use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\Exception\NoLoginServerException; use Icewind\SMB\Exception\NoLoginServerException;
@ -20,7 +22,12 @@ class Connection extends RawConnection {
/** @var Parser */ /** @var Parser */
private $parser; private $parser;
public function __construct($command, Parser $parser, $env = []) { /**
* @param string $command
* @param Parser $parser
* @param array<string, string> $env
*/
public function __construct(string $command, Parser $parser, array $env = []) {
parent::__construct($command, $env); parent::__construct($command, $env);
$this->parser = $parser; $this->parser = $parser;
} }
@ -30,39 +37,48 @@ class Connection extends RawConnection {
* *
* @param string $input * @param string $input
*/ */
public function write($input) { public function write(string $input) {
parent::write($input . PHP_EOL); return parent::write($input . PHP_EOL);
} }
/** /**
* @throws ConnectException * @throws ConnectException
*/ */
public function clearTillPrompt() { public function clearTillPrompt(): void {
$this->write(''); $this->write('');
do { do {
$promptLine = $this->readLine(); $promptLine = $this->readLine();
if ($promptLine === false) {
break;
}
$this->parser->checkConnectionError($promptLine); $this->parser->checkConnectionError($promptLine);
} while (!$this->isPrompt($promptLine)); } while (!$this->isPrompt($promptLine));
$this->write(''); if ($this->write('') === false) {
throw new ConnectionRefusedException();
}
$this->readLine(); $this->readLine();
} }
/** /**
* get all unprocessed output from smbclient until the next prompt * get all unprocessed output from smbclient until the next prompt
* *
* @param callable $callback (optional) callback to call for every line read * @param (callable(string):bool)|null $callback (optional) callback to call for every line read
* @return string[] * @return string[]
* @throws AuthenticationException * @throws AuthenticationException
* @throws ConnectException * @throws ConnectException
* @throws ConnectionException * @throws ConnectionException
* @throws InvalidHostException * @throws InvalidHostException
* @throws NoLoginServerException * @throws NoLoginServerException
* @throws AccessDeniedException
*/ */
public function read(callable $callback = null) { public function read(callable $callback = null): array {
if (!$this->isValid()) { if (!$this->isValid()) {
throw new ConnectionException('Connection not valid'); throw new ConnectionException('Connection not valid');
} }
$promptLine = $this->readLine(); //first line is prompt $promptLine = $this->readLine(); //first line is prompt
if ($promptLine === false) {
$this->unknownError($promptLine);
}
$this->parser->checkConnectionError($promptLine); $this->parser->checkConnectionError($promptLine);
$output = []; $output = [];
@ -74,7 +90,7 @@ class Connection extends RawConnection {
if ($line === false) { if ($line === false) {
$this->unknownError($promptLine); $this->unknownError($promptLine);
} }
while (!$this->isPrompt($line)) { //next prompt functions as delimiter while ($line !== false && !$this->isPrompt($line)) { //next prompt functions as delimiter
if (is_callable($callback)) { if (is_callable($callback)) {
$result = $callback($line); $result = $callback($line);
if ($result === false) { // allow the callback to close the connection for infinite running commands if ($result === false) { // allow the callback to close the connection for infinite running commands
@ -82,26 +98,21 @@ class Connection extends RawConnection {
break; break;
} }
} else { } else {
$output[] .= $line; $output[] = $line;
} }
$line = $this->readLine(); $line = $this->readLine();
} }
return $output; return $output;
} }
/** private function isPrompt(string $line): bool {
* Check return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER;
*
* @param $line
* @return bool
*/
private function isPrompt($line) {
return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER || $line === false;
} }
/** /**
* @param string $promptLine (optional) prompt line that might contain some info about the error * @param string|bool $promptLine (optional) prompt line that might contain some info about the error
* @throws ConnectException * @throws ConnectException
* @return no-return
*/ */
private function unknownError($promptLine = '') { private function unknownError($promptLine = '') {
if ($promptLine) { //maybe we have some error we missed on the previous line if ($promptLine) { //maybe we have some error we missed on the previous line
@ -116,7 +127,7 @@ class Connection extends RawConnection {
} }
} }
public function close($terminate = true) { public function close(bool $terminate = true): void {
if (get_resource_type($this->getInputStream()) === 'stream') { if (get_resource_type($this->getInputStream()) === 'stream') {
// ignore any errors while trying to send the close command, the process might already be dead // ignore any errors while trying to send the close command, the process might already be dead
@$this->write('close' . PHP_EOL); @$this->write('close' . PHP_EOL);

View File

@ -11,34 +11,17 @@ use Icewind\SMB\ACL;
use Icewind\SMB\IFileInfo; use Icewind\SMB\IFileInfo;
class FileInfo implements IFileInfo { class FileInfo implements IFileInfo {
/** /** @var string */
* @var string
*/
protected $path; protected $path;
/** @var string */
/**
* @var string
*/
protected $name; protected $name;
/** @var int */
/**
* @var int
*/
protected $size; protected $size;
/** @var int */
/**
* @var int
*/
protected $time; protected $time;
/** @var int */
/**
* @var int
*/
protected $mode; protected $mode;
/** @var callable(): ACL[] */
/**
* @var callable
*/
protected $aclCallback; protected $aclCallback;
/** /**
@ -47,9 +30,9 @@ class FileInfo implements IFileInfo {
* @param int $size * @param int $size
* @param int $time * @param int $time
* @param int $mode * @param int $mode
* @param callable $aclCallback * @param callable(): ACL[] $aclCallback
*/ */
public function __construct($path, $name, $size, $time, $mode, callable $aclCallback) { public function __construct(string $path, string $name, int $size, int $time, int $mode, callable $aclCallback) {
$this->path = $path; $this->path = $path;
$this->name = $name; $this->name = $name;
$this->size = $size; $this->size = $size;
@ -61,63 +44,39 @@ class FileInfo implements IFileInfo {
/** /**
* @return string * @return string
*/ */
public function getPath() { public function getPath(): string {
return $this->path; return $this->path;
} }
/** public function getName(): string {
* @return string
*/
public function getName() {
return $this->name; return $this->name;
} }
/** public function getSize(): int {
* @return int
*/
public function getSize() {
return $this->size; return $this->size;
} }
/** public function getMTime(): int {
* @return int
*/
public function getMTime() {
return $this->time; return $this->time;
} }
/** public function isDirectory(): bool {
* @return bool
*/
public function isDirectory() {
return (bool)($this->mode & IFileInfo::MODE_DIRECTORY); return (bool)($this->mode & IFileInfo::MODE_DIRECTORY);
} }
/** public function isReadOnly(): bool {
* @return bool
*/
public function isReadOnly() {
return (bool)($this->mode & IFileInfo::MODE_READONLY); return (bool)($this->mode & IFileInfo::MODE_READONLY);
} }
/** public function isHidden(): bool {
* @return bool
*/
public function isHidden() {
return (bool)($this->mode & IFileInfo::MODE_HIDDEN); return (bool)($this->mode & IFileInfo::MODE_HIDDEN);
} }
/** public function isSystem(): bool {
* @return bool
*/
public function isSystem() {
return (bool)($this->mode & IFileInfo::MODE_SYSTEM); return (bool)($this->mode & IFileInfo::MODE_SYSTEM);
} }
/** public function isArchived(): bool {
* @return bool
*/
public function isArchived() {
return (bool)($this->mode & IFileInfo::MODE_ARCHIVE); return (bool)($this->mode & IFileInfo::MODE_ARCHIVE);
} }

View File

@ -14,16 +14,13 @@ use Icewind\SMB\Exception\RevisionMismatchException;
use Icewind\SMB\INotifyHandler; use Icewind\SMB\INotifyHandler;
class NotifyHandler implements INotifyHandler { class NotifyHandler implements INotifyHandler {
/** /** @var Connection */
* @var Connection
*/
private $connection; private $connection;
/** /** @var string */
* @var string
*/
private $path; private $path;
/** @var bool */
private $listening = true; private $listening = true;
// see error.h // see error.h
@ -35,7 +32,7 @@ class NotifyHandler implements INotifyHandler {
* @param Connection $connection * @param Connection $connection
* @param string $path * @param string $path
*/ */
public function __construct(Connection $connection, $path) { public function __construct(Connection $connection, string $path) {
$this->connection = $connection; $this->connection = $connection;
$this->path = $path; $this->path = $path;
} }
@ -45,17 +42,17 @@ class NotifyHandler implements INotifyHandler {
* *
* @return Change[] * @return Change[]
*/ */
public function getChanges() { public function getChanges(): array {
if (!$this->listening) { if (!$this->listening) {
return []; return [];
} }
stream_set_blocking($this->connection->getOutputStream(), 0); stream_set_blocking($this->connection->getOutputStream(), false);
$lines = []; $lines = [];
while (($line = $this->connection->readLine())) { while (($line = $this->connection->readLine())) {
$this->checkForError($line); $this->checkForError($line);
$lines[] = $line; $lines[] = $line;
} }
stream_set_blocking($this->connection->getOutputStream(), 1); stream_set_blocking($this->connection->getOutputStream(), true);
return array_values(array_filter(array_map([$this, 'parseChangeLine'], $lines))); return array_values(array_filter(array_map([$this, 'parseChangeLine'], $lines)));
} }
@ -64,21 +61,24 @@ class NotifyHandler implements INotifyHandler {
* *
* Note that this is a blocking process and will cause the process to block forever if not explicitly terminated * Note that this is a blocking process and will cause the process to block forever if not explicitly terminated
* *
* @param callable $callback * @param callable(Change):?bool $callback
*/ */
public function listen($callback) { public function listen(callable $callback): void {
if ($this->listening) { if ($this->listening) {
$this->connection->read(function ($line) use ($callback) { $this->connection->read(function (string $line) use ($callback): bool {
$this->checkForError($line); $this->checkForError($line);
$change = $this->parseChangeLine($line); $change = $this->parseChangeLine($line);
if ($change) { if ($change) {
return $callback($change); $result = $callback($change);
return $result === false ? false : true;
} else {
return true;
} }
}); });
} }
} }
private function parseChangeLine($line) { private function parseChangeLine(string $line): ?Change {
$code = (int)substr($line, 0, 4); $code = (int)substr($line, 0, 4);
if ($code === 0) { if ($code === 0) {
return null; return null;
@ -91,14 +91,14 @@ class NotifyHandler implements INotifyHandler {
} }
} }
private function checkForError($line) { private function checkForError(string $line): void {
if (substr($line, 0, 16) === 'notify returned ') { if (substr($line, 0, 16) === 'notify returned ') {
$error = substr($line, 16); $error = substr($line, 16);
throw Exception::fromMap(array_merge(self::EXCEPTION_MAP, Parser::EXCEPTION_MAP), $error, 'Notify is not supported with the used smb version'); throw Exception::fromMap(array_merge(self::EXCEPTION_MAP, Parser::EXCEPTION_MAP), $error, 'Notify is not supported with the used smb version');
} }
} }
public function stop() { public function stop(): void {
$this->listening = false; $this->listening = false;
$this->connection->close(); $this->connection->close();
} }

View File

@ -7,6 +7,7 @@
namespace Icewind\SMB\Wrapped; namespace Icewind\SMB\Wrapped;
use Icewind\SMB\ACL;
use Icewind\SMB\Exception\AccessDeniedException; use Icewind\SMB\Exception\AccessDeniedException;
use Icewind\SMB\Exception\AlreadyExistsException; use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\Exception\AuthenticationException; use Icewind\SMB\Exception\AuthenticationException;
@ -28,11 +29,6 @@ class Parser {
*/ */
protected $timeZone; protected $timeZone;
/**
* @var string
*/
private $host;
// see error.h // see error.h
const EXCEPTION_MAP = [ const EXCEPTION_MAP = [
ErrorCodes::LogonFailure => AuthenticationException::class, ErrorCodes::LogonFailure => AuthenticationException::class,
@ -60,21 +56,29 @@ class Parser {
/** /**
* @param string $timeZone * @param string $timeZone
*/ */
public function __construct($timeZone) { public function __construct(string $timeZone) {
$this->timeZone = $timeZone; $this->timeZone = $timeZone;
} }
private function getErrorCode($line) { private function getErrorCode(string $line): ?string {
$parts = explode(' ', $line); $parts = explode(' ', $line);
foreach ($parts as $part) { foreach ($parts as $part) {
if (substr($part, 0, 9) === 'NT_STATUS') { if (substr($part, 0, 9) === 'NT_STATUS') {
return $part; return $part;
} }
} }
return false; return null;
} }
public function checkForError($output, $path) { /**
* @param string[] $output
* @param string $path
* @return no-return
* @throws Exception
* @throws InvalidResourceException
* @throws NotFoundException
*/
public function checkForError(array $output, string $path): void {
if (strpos($output[0], 'does not exist')) { if (strpos($output[0], 'does not exist')) {
throw new NotFoundException($path); throw new NotFoundException($path);
} }
@ -91,13 +95,13 @@ class Parser {
/** /**
* check if the first line holds a connection failure * check if the first line holds a connection failure
* *
* @param $line * @param string $line
* @throws AuthenticationException * @throws AuthenticationException
* @throws InvalidHostException * @throws InvalidHostException
* @throws NoLoginServerException * @throws NoLoginServerException
* @throws AccessDeniedException * @throws AccessDeniedException
*/ */
public function checkConnectionError($line) { public function checkConnectionError(string $line): void {
$line = rtrim($line, ')'); $line = rtrim($line, ')');
if (substr($line, -23) === ErrorCodes::LogonFailure) { if (substr($line, -23) === ErrorCodes::LogonFailure) {
throw new AuthenticationException('Invalid login'); throw new AuthenticationException('Invalid login');
@ -119,7 +123,7 @@ class Parser {
} }
} }
public function parseMode($mode) { public function parseMode(string $mode): int {
$result = 0; $result = 0;
foreach (self::MODE_STRINGS as $char => $val) { foreach (self::MODE_STRINGS as $char => $val) {
if (strpos($mode, $char) !== false) { if (strpos($mode, $char) !== false) {
@ -129,7 +133,12 @@ class Parser {
return $result; return $result;
} }
public function parseStat($output) { /**
* @param string[] $output
* @return array{"mtime": int, "mode": int, "size": int}
* @throws Exception
*/
public function parseStat(array $output): array {
$data = []; $data = [];
foreach ($output as $line) { foreach ($output as $line) {
// A line = explode statement may not fill all array elements // A line = explode statement may not fill all array elements
@ -143,14 +152,24 @@ class Parser {
$data[$name] = $value; $data[$name] = $value;
} }
} }
$attributeStart = strpos($data['attributes'], '(');
if ($attributeStart === false) {
throw new Exception("Malformed state response from server");
}
return [ return [
'mtime' => strtotime($data['write_time']), 'mtime' => strtotime($data['write_time']),
'mode' => hexdec(substr($data['attributes'], strpos($data['attributes'], '(') + 1, -1)), 'mode' => hexdec(substr($data['attributes'], $attributeStart + 1, -1)),
'size' => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0 'size' => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0
]; ];
} }
public function parseDir($output, $basePath, callable $aclCallback) { /**
* @param string[] $output
* @param string $basePath
* @param callable(string):ACL[] $aclCallback
* @return FileInfo[]
*/
public function parseDir(array $output, string $basePath, callable $aclCallback): array {
//last line is used space //last line is used space
array_pop($output); array_pop($output);
$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/'; $regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
@ -163,7 +182,7 @@ class Parser {
$mode = $this->parseMode($mode); $mode = $this->parseMode($mode);
$time = strtotime($time . ' ' . $this->timeZone); $time = strtotime($time . ' ' . $this->timeZone);
$path = $basePath . '/' . $name; $path = $basePath . '/' . $name;
$content[] = new FileInfo($path, $name, $size, $time, $mode, function () use ($aclCallback, $path) { $content[] = new FileInfo($path, $name, (int)$size, $time, $mode, function () use ($aclCallback, $path): array {
return $aclCallback($path); return $aclCallback($path);
}); });
} }
@ -172,7 +191,11 @@ class Parser {
return $content; return $content;
} }
public function parseListShares($output) { /**
* @param string[] $output
* @return array<string, string>
*/
public function parseListShares(array $output): array {
$shareNames = []; $shareNames = [];
foreach ($output as $line) { foreach ($output as $line) {
if (strpos($line, '|')) { if (strpos($line, '|')) {
@ -188,4 +211,67 @@ class Parser {
} }
return $shareNames; return $shareNames;
} }
/**
* @param string[] $rawAcls
* @return ACL[]
*/
public function parseACLs(array $rawAcls): array {
$acls = [];
foreach ($rawAcls as $acl) {
if (strpos($acl, ':') === false) {
continue;
}
[$type, $acl] = explode(':', $acl, 2);
if ($type !== 'ACL') {
continue;
}
[$user, $permissions] = explode(':', $acl, 2);
[$type, $flags, $mask] = explode('/', $permissions);
$type = $type === 'ALLOWED' ? ACL::TYPE_ALLOW : ACL::TYPE_DENY;
$flagsInt = 0;
foreach (explode('|', $flags) as $flagString) {
if ($flagString === 'OI') {
$flagsInt += ACL::FLAG_OBJECT_INHERIT;
} elseif ($flagString === 'CI') {
$flagsInt += ACL::FLAG_CONTAINER_INHERIT;
}
}
if (substr($mask, 0, 2) === '0x') {
$maskInt = hexdec($mask);
} else {
$maskInt = 0;
foreach (explode('|', $mask) as $maskString) {
if ($maskString === 'R') {
$maskInt += ACL::MASK_READ;
} elseif ($maskString === 'W') {
$maskInt += ACL::MASK_WRITE;
} elseif ($maskString === 'X') {
$maskInt += ACL::MASK_EXECUTE;
} elseif ($maskString === 'D') {
$maskInt += ACL::MASK_DELETE;
} elseif ($maskString === 'READ') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE;
} elseif ($maskString === 'CHANGE') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
} elseif ($maskString === 'FULL') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
}
}
}
if (isset($acls[$user])) {
$existing = $acls[$user];
$maskInt += $existing->getMask();
}
$acls[$user] = new ACL($type, $flagsInt, $maskInt);
}
ksort($acls);
return $acls;
}
} }

View File

@ -30,10 +30,10 @@ class RawConnection {
* $pipes[4] holds the stream for writing files * $pipes[4] holds the stream for writing files
* $pipes[5] holds the stream for reading files * $pipes[5] holds the stream for reading files
*/ */
private $pipes; private $pipes = [];
/** /**
* @var resource $process * @var resource|null $process
*/ */
private $process; private $process;
@ -42,17 +42,20 @@ class RawConnection {
*/ */
private $authStream = null; private $authStream = null;
private $connected = false; /**
* @param string $command
public function __construct($command, array $env = []) { * @param array<string, string> $env
*/
public function __construct(string $command, array $env = []) {
$this->command = $command; $this->command = $command;
$this->env = $env; $this->env = $env;
} }
/** /**
* @throws ConnectException * @throws ConnectException
* @psalm-assert resource $this->process
*/ */
public function connect() { public function connect(): void {
if (is_null($this->getAuthStream())) { if (is_null($this->getAuthStream())) {
throw new ConnectException('Authentication not set before connecting'); throw new ConnectException('Authentication not set before connecting');
} }
@ -77,18 +80,18 @@ class RawConnection {
if (!$this->isValid()) { if (!$this->isValid()) {
throw new ConnectionException(); throw new ConnectionException();
} }
$this->connected = true;
} }
/** /**
* check if the connection is still active * check if the connection is still active
* *
* @return bool * @return bool
* @psalm-assert-if-true resource $this->process
*/ */
public function isValid() { public function isValid(): bool {
if (is_resource($this->process)) { if (is_resource($this->process)) {
$status = proc_get_status($this->process); $status = proc_get_status($this->process);
return $status['running']; return (bool)$status['running'];
} else { } else {
return false; return false;
} }
@ -98,10 +101,12 @@ class RawConnection {
* send input to the process * send input to the process
* *
* @param string $input * @param string $input
* @return int|bool
*/ */
public function write($input) { public function write(string $input) {
fwrite($this->getInputStream(), $input); $result = @fwrite($this->getInputStream(), $input);
fflush($this->getInputStream()); fflush($this->getInputStream());
return $result;
} }
/** /**
@ -116,18 +121,19 @@ class RawConnection {
/** /**
* read a line of output * read a line of output
* *
* @return string * @return string|false
*/ */
public function readError() { public function readError() {
return trim(stream_get_line($this->getErrorStream(), 4086)); $line = stream_get_line($this->getErrorStream(), 4086);
return $line !== false ? trim($line) : false;
} }
/** /**
* get all output until the process closes * get all output until the process closes
* *
* @return array * @return string[]
*/ */
public function readAll() { public function readAll(): array {
$output = []; $output = [];
while ($line = $this->readLine()) { while ($line = $this->readLine()) {
$output[] = $line; $output[] = $line;
@ -135,40 +141,67 @@ class RawConnection {
return $output; return $output;
} }
/**
* @return resource
*/
public function getInputStream() { public function getInputStream() {
return $this->pipes[0]; return $this->pipes[0];
} }
/**
* @return resource
*/
public function getOutputStream() { public function getOutputStream() {
return $this->pipes[1]; return $this->pipes[1];
} }
/**
* @return resource
*/
public function getErrorStream() { public function getErrorStream() {
return $this->pipes[2]; return $this->pipes[2];
} }
/**
* @return resource|null
*/
public function getAuthStream() { public function getAuthStream() {
return $this->authStream; return $this->authStream;
} }
/**
* @return resource
*/
public function getFileInputStream() { public function getFileInputStream() {
return $this->pipes[4]; return $this->pipes[4];
} }
/**
* @return resource
*/
public function getFileOutputStream() { public function getFileOutputStream() {
return $this->pipes[5]; return $this->pipes[5];
} }
public function writeAuthentication($user, $password) { /**
$auth = ($password === false) * @param string|null $user
* @param string|null $password
* @psalm-assert resource $this->authStream
*/
public function writeAuthentication(?string $user, ?string $password): void {
$auth = ($password === null)
? "username=$user" ? "username=$user"
: "username=$user\npassword=$password\n"; : "username=$user\npassword=$password\n";
$this->authStream = fopen('php://temp', 'w+'); $this->authStream = fopen('php://temp', 'w+');
fwrite($this->getAuthStream(), $auth); fwrite($this->authStream, $auth);
} }
public function close($terminate = true) { /**
* @param bool $terminate
* @psalm-assert null $this->process
*/
public function close(bool $terminate = true): void {
if (!is_resource($this->process)) { if (!is_resource($this->process)) {
return; return;
} }
@ -176,9 +209,10 @@ class RawConnection {
proc_terminate($this->process); proc_terminate($this->process);
} }
proc_close($this->process); proc_close($this->process);
$this->process = null;
} }
public function reconnect() { public function reconnect(): void {
$this->close(); $this->close();
$this->connect(); $this->connect();
} }

View File

@ -11,6 +11,8 @@ use Icewind\SMB\AbstractServer;
use Icewind\SMB\Exception\AuthenticationException; use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\ConnectException; use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException; use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\ConnectionRefusedException;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\InvalidHostException; use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\IShare; use Icewind\SMB\IShare;
use Icewind\SMB\ISystem; use Icewind\SMB\ISystem;
@ -22,11 +24,11 @@ class Server extends AbstractServer {
* @param ISystem $system * @param ISystem $system
* @return bool * @return bool
*/ */
public static function available(ISystem $system) { public static function available(ISystem $system): bool {
return $system->getSmbclientPath(); return $system->getSmbclientPath() !== null;
} }
private function getAuthFileArgument() { private function getAuthFileArgument(): string {
if ($this->getAuth()->getUsername()) { if ($this->getAuth()->getUsername()) {
return '--authentication-file=' . $this->system->getFD(3); return '--authentication-file=' . $this->system->getFD(3);
} else { } else {
@ -41,22 +43,30 @@ class Server extends AbstractServer {
* @throws InvalidHostException * @throws InvalidHostException
* @throws ConnectException * @throws ConnectException
*/ */
public function listShares() { public function listShares(): array {
$maxProtocol = $this->options->getMaxProtocol();
$minProtocol = $this->options->getMinProtocol();
$smbClient = $this->system->getSmbclientPath();
if ($smbClient === null) {
throw new Exception("Backend not available");
}
$command = sprintf( $command = sprintf(
'%s %s %s -L %s', '%s %s %s %s %s -L %s',
$this->system->getSmbclientPath(), $smbClient,
$this->getAuthFileArgument(), $this->getAuthFileArgument(),
$this->getAuth()->getExtraCommandLineArguments(), $this->getAuth()->getExtraCommandLineArguments(),
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "",
$minProtocol ? "--option='client min protocol=" . $minProtocol . "'" : "",
escapeshellarg('//' . $this->getHost()) escapeshellarg('//' . $this->getHost())
); );
$connection = new RawConnection($command); $connection = new RawConnection($command);
$connection->writeAuthentication($this->getAuth()->getUsername(), $this->getAuth()->getPassword()); $connection->writeAuthentication($this->getAuth()->getUsername(), $this->getAuth()->getPassword());
$connection->connect(); $connection->connect();
if (!$connection->isValid()) { if (!$connection->isValid()) {
throw new ConnectionException($connection->readLine()); throw new ConnectionException((string)$connection->readLine());
} }
$parser = new Parser($this->timezoneProvider); $parser = new Parser($this->timezoneProvider->get($this->host));
$output = $connection->readAll(); $output = $connection->readAll();
if (isset($output[0])) { if (isset($output[0])) {
@ -71,6 +81,9 @@ class Server extends AbstractServer {
if (isset($output[0])) { if (isset($output[0])) {
$parser->checkConnectionError($output[0]); $parser->checkConnectionError($output[0]);
} }
if (count($output) === 0) {
throw new ConnectionRefusedException();
}
$shareNames = $parser->parseListShares($output); $shareNames = $parser->parseListShares($output);
@ -85,7 +98,7 @@ class Server extends AbstractServer {
* @param string $name * @param string $name
* @return IShare * @return IShare
*/ */
public function getShare($name) { public function getShare(string $name): IShare {
return new Share($this, $name, $this->system); return new Share($this, $name, $this->system);
} }
} }

View File

@ -9,9 +9,14 @@ namespace Icewind\SMB\Wrapped;
use Icewind\SMB\AbstractShare; use Icewind\SMB\AbstractShare;
use Icewind\SMB\ACL; use Icewind\SMB\ACL;
use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException; use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\DependencyException; use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\FileInUseException; use Icewind\SMB\Exception\FileInUseException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\Exception\InvalidTypeException; use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NotFoundException; use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\Exception\InvalidRequestException; use Icewind\SMB\Exception\InvalidRequestException;
@ -35,9 +40,9 @@ class Share extends AbstractShare {
private $name; private $name;
/** /**
* @var Connection $connection * @var Connection|null $connection
*/ */
public $connection; public $connection = null;
/** /**
* @var Parser * @var Parser
@ -63,7 +68,7 @@ class Share extends AbstractShare {
* @param string $name * @param string $name
* @param ISystem $system * @param ISystem $system
*/ */
public function __construct(IServer $server, $name, ISystem $system) { public function __construct(IServer $server, string $name, ISystem $system) {
parent::__construct(); parent::__construct();
$this->server = $server; $this->server = $server;
$this->name = $name; $this->name = $name;
@ -71,7 +76,7 @@ class Share extends AbstractShare {
$this->parser = new Parser($server->getTimeZone()); $this->parser = new Parser($server->getTimeZone());
} }
private function getAuthFileArgument() { private function getAuthFileArgument(): string {
if ($this->server->getAuth()->getUsername()) { if ($this->server->getAuth()->getUsername()) {
return '--authentication-file=' . $this->system->getFD(3); return '--authentication-file=' . $this->system->getFD(3);
} else { } else {
@ -79,22 +84,31 @@ class Share extends AbstractShare {
} }
} }
protected function getConnection() { protected function getConnection(): Connection {
$maxProtocol = $this->server->getOptions()->getMaxProtocol();
$minProtocol = $this->server->getOptions()->getMinProtocol();
$smbClient = $this->system->getSmbclientPath();
$stdBuf = $this->system->getStdBufPath();
if ($smbClient === null) {
throw new Exception("Backend not available");
}
$command = sprintf( $command = sprintf(
'%s %s%s -t %s %s %s %s', '%s %s%s -t %s %s %s %s %s %s',
self::EXEC_CMD, self::EXEC_CMD,
$this->system->getStdBufPath() ? $this->system->getStdBufPath() . ' -o0 ' : '', $stdBuf ? $stdBuf . ' -o0 ' : '',
$this->system->getSmbclientPath(), $smbClient,
$this->server->getOptions()->getTimeout(), $this->server->getOptions()->getTimeout(),
$this->getAuthFileArgument(), $this->getAuthFileArgument(),
$this->server->getAuth()->getExtraCommandLineArguments(), $this->server->getAuth()->getExtraCommandLineArguments(),
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "",
$minProtocol ? "--option='client min protocol=" . $minProtocol . "'" : "",
escapeshellarg('//' . $this->server->getHost() . '/' . $this->name) escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
); );
$connection = new Connection($command, $this->parser); $connection = new Connection($command, $this->parser);
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword()); $connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
$connection->connect(); $connection->connect();
if (!$connection->isValid()) { if (!$connection->isValid()) {
throw new ConnectionException($connection->readLine()); throw new ConnectionException((string)$connection->readLine());
} }
// some versions of smbclient add a help message in first of the first prompt // some versions of smbclient add a help message in first of the first prompt
$connection->clearTillPrompt(); $connection->clearTillPrompt();
@ -102,21 +116,33 @@ class Share extends AbstractShare {
} }
/** /**
* @throws \Icewind\SMB\Exception\ConnectionException * @throws ConnectionException
* @throws \Icewind\SMB\Exception\AuthenticationException * @throws AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidHostException * @throws InvalidHostException
* @psalm-assert Connection $this->connection
*/ */
protected function connect() { protected function connect(): Connection {
if ($this->connection and $this->connection->isValid()) { if ($this->connection and $this->connection->isValid()) {
return; return $this->connection;
} }
$this->connection = $this->getConnection(); $this->connection = $this->getConnection();
return $this->connection;
} }
protected function reconnect() { /**
$this->connection->reconnect(); * @throws ConnectionException
if (!$this->connection->isValid()) { * @throws AuthenticationException
throw new ConnectionException(); * @throws InvalidHostException
* @psalm-assert Connection $this->connection
*/
protected function reconnect(): void {
if ($this->connection === null) {
$this->connect();
} else {
$this->connection->reconnect();
if (!$this->connection->isValid()) {
throw new ConnectionException();
}
} }
} }
@ -125,11 +151,11 @@ class Share extends AbstractShare {
* *
* @return string * @return string
*/ */
public function getName() { public function getName(): string {
return $this->name; return $this->name;
} }
protected function simpleCommand($command, $path) { protected function simpleCommand(string $command, string $path): bool {
$escapedPath = $this->escapePath($path); $escapedPath = $this->escapePath($path);
$cmd = $command . ' ' . $escapedPath; $cmd = $command . ' ' . $escapedPath;
$output = $this->execute($cmd); $output = $this->execute($cmd);
@ -139,13 +165,13 @@ class Share extends AbstractShare {
/** /**
* List the content of a remote folder * List the content of a remote folder
* *
* @param $path * @param string $path
* @return \Icewind\SMB\IFileInfo[] * @return IFileInfo[]
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function dir($path) { public function dir(string $path): array {
$escapedPath = $this->escapePath($path); $escapedPath = $this->escapePath($path);
$output = $this->execute('cd ' . $escapedPath); $output = $this->execute('cd ' . $escapedPath);
//check output for errors //check output for errors
@ -154,16 +180,16 @@ class Share extends AbstractShare {
$this->execute('cd /'); $this->execute('cd /');
return $this->parser->parseDir($output, $path, function ($path) { return $this->parser->parseDir($output, $path, function (string $path) {
return $this->getAcls($path); return $this->getAcls($path);
}); });
} }
/** /**
* @param string $path * @param string $path
* @return \Icewind\SMB\IFileInfo * @return IFileInfo
*/ */
public function stat($path) { public function stat(string $path): IFileInfo {
// some windows server setups don't seem to like the allinfo command // some windows server setups don't seem to like the allinfo command
// use the dir command instead to get the file info where possible // use the dir command instead to get the file info where possible
if ($path !== "" && $path !== "/") { if ($path !== "" && $path !== "/") {
@ -200,10 +226,10 @@ class Share extends AbstractShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\AlreadyExistsException * @throws AlreadyExistsException
*/ */
public function mkdir($path) { public function mkdir(string $path): bool {
return $this->simpleCommand('mkdir', $path); return $this->simpleCommand('mkdir', $path);
} }
@ -213,10 +239,10 @@ class Share extends AbstractShare {
* @param string $path * @param string $path
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function rmdir($path) { public function rmdir(string $path): bool {
return $this->simpleCommand('rmdir', $path); return $this->simpleCommand('rmdir', $path);
} }
@ -230,7 +256,7 @@ class Share extends AbstractShare {
* @throws NotFoundException * @throws NotFoundException
* @throws \Exception * @throws \Exception
*/ */
public function del($path, $secondTry = false) { public function del(string $path, bool $secondTry = false): bool {
//del return a file not found error when trying to delete a folder //del return a file not found error when trying to delete a folder
//we catch it so we can check if $path doesn't exist or is of invalid type //we catch it so we can check if $path doesn't exist or is of invalid type
try { try {
@ -261,10 +287,10 @@ class Share extends AbstractShare {
* @param string $to * @param string $to
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\AlreadyExistsException * @throws AlreadyExistsException
*/ */
public function rename($from, $to) { public function rename(string $from, string $to): bool {
$path1 = $this->escapePath($from); $path1 = $this->escapePath($from);
$path2 = $this->escapePath($to); $path2 = $this->escapePath($to);
$output = $this->execute('rename ' . $path1 . ' ' . $path2); $output = $this->execute('rename ' . $path1 . ' ' . $path2);
@ -278,10 +304,10 @@ class Share extends AbstractShare {
* @param string $target remove file * @param string $target remove file
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function put($source, $target) { public function put(string $source, string $target): bool {
$path1 = $this->escapeLocalPath($source); //first path is local, needs different escaping $path1 = $this->escapeLocalPath($source); //first path is local, needs different escaping
$path2 = $this->escapePath($target); $path2 = $this->escapePath($target);
$output = $this->execute('put ' . $path1 . ' ' . $path2); $output = $this->execute('put ' . $path1 . ' ' . $path2);
@ -295,10 +321,10 @@ class Share extends AbstractShare {
* @param string $target local file * @param string $target local file
* @return bool * @return bool
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function get($source, $target) { public function get(string $source, string $target): bool {
$path1 = $this->escapePath($source); $path1 = $this->escapePath($source);
$path2 = $this->escapeLocalPath($target); //second path is local, needs different escaping $path2 = $this->escapeLocalPath($target); //second path is local, needs different escaping
$output = $this->execute('get ' . $path1 . ' ' . $path2); $output = $this->execute('get ' . $path1 . ' ' . $path2);
@ -311,10 +337,10 @@ class Share extends AbstractShare {
* @param string $source * @param string $source
* @return resource a read only stream with the contents of the remote file * @return resource a read only stream with the contents of the remote file
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function read($source) { public function read(string $source) {
$source = $this->escapePath($source); $source = $this->escapePath($source);
// since returned stream is closed by the caller we need to create a new instance // since returned stream is closed by the caller we need to create a new instance
// since we can't re-use the same file descriptor over multiple calls // since we can't re-use the same file descriptor over multiple calls
@ -333,10 +359,10 @@ class Share extends AbstractShare {
* @param string $target * @param string $target
* @return resource a write only stream to upload a remote file * @return resource a write only stream to upload a remote file
* *
* @throws \Icewind\SMB\Exception\NotFoundException * @throws NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
*/ */
public function write($target) { public function write(string $target) {
$target = $this->escapePath($target); $target = $this->escapePath($target);
// since returned stream is closed by the caller we need to create a new instance // since returned stream is closed by the caller we need to create a new instance
// since we can't re-use the same file descriptor over multiple calls // since we can't re-use the same file descriptor over multiple calls
@ -348,9 +374,14 @@ class Share extends AbstractShare {
// use a close callback to ensure the upload is finished before continuing // use a close callback to ensure the upload is finished before continuing
// this also serves as a way to keep the connection in scope // this also serves as a way to keep the connection in scope
return CallbackWrapper::wrap($fh, null, null, function () use ($connection, $target) { $stream = CallbackWrapper::wrap($fh, null, null, function () use ($connection) {
$connection->close(false); // dont terminate, give the upload some time $connection->close(false); // dont terminate, give the upload some time
}); });
if (is_resource($stream)) {
return $stream;
} else {
throw new InvalidRequestException($target);
}
} }
/** /**
@ -359,9 +390,9 @@ class Share extends AbstractShare {
* *
* @param string $target * @param string $target
* *
* @throws \Icewind\SMB\Exception\DependencyException * @throws DependencyException
*/ */
public function append($target) { public function append(string $target) {
throw new DependencyException('php-libsmbclient is required for append'); throw new DependencyException('php-libsmbclient is required for append');
} }
@ -370,7 +401,7 @@ class Share extends AbstractShare {
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL * @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
* @return mixed * @return mixed
*/ */
public function setMode($path, $mode) { public function setMode(string $path, int $mode) {
$modeString = ''; $modeString = '';
foreach (self::MODE_MAP as $modeByte => $string) { foreach (self::MODE_MAP as $modeByte => $string) {
if ($mode & $modeByte) { if ($mode & $modeByte) {
@ -400,7 +431,7 @@ class Share extends AbstractShare {
* @throws ConnectionException * @throws ConnectionException
* @throws DependencyException * @throws DependencyException
*/ */
public function notify($path) { public function notify(string $path): INotifyHandler {
if (!$this->system->getStdBufPath()) { //stdbuf is required to disable smbclient's output buffering if (!$this->system->getStdBufPath()) { //stdbuf is required to disable smbclient's output buffering
throw new DependencyException('stdbuf is required for usage of the notify command'); throw new DependencyException('stdbuf is required for usage of the notify command');
} }
@ -412,12 +443,11 @@ class Share extends AbstractShare {
/** /**
* @param string $command * @param string $command
* @return array * @return string[]
*/ */
protected function execute($command) { protected function execute(string $command): array {
$this->connect(); $this->connect()->write($command . PHP_EOL);
$this->connection->write($command . PHP_EOL); return $this->connect()->read();
return $this->connection->read();
} }
/** /**
@ -427,19 +457,18 @@ class Share extends AbstractShare {
* @param string $path * @param string $path
* *
* @return bool * @return bool
* @throws \Icewind\SMB\Exception\AlreadyExistsException * @throws AlreadyExistsException
* @throws \Icewind\SMB\Exception\AccessDeniedException * @throws \Icewind\SMB\Exception\AccessDeniedException
* @throws \Icewind\SMB\Exception\NotEmptyException * @throws \Icewind\SMB\Exception\NotEmptyException
* @throws \Icewind\SMB\Exception\InvalidTypeException * @throws InvalidTypeException
* @throws \Icewind\SMB\Exception\Exception * @throws \Icewind\SMB\Exception\Exception
* @throws NotFoundException * @throws NotFoundException
*/ */
protected function parseOutput($lines, $path = '') { protected function parseOutput(array $lines, string $path = ''): bool {
if (count($lines) === 0) { if (count($lines) === 0) {
return true; return true;
} else { } else {
$this->parser->checkForError($lines, $path); $this->parser->checkForError($lines, $path);
return false;
} }
} }
@ -447,7 +476,7 @@ class Share extends AbstractShare {
* @param string $string * @param string $string
* @return string * @return string
*/ */
protected function escape($string) { protected function escape(string $string): string {
return escapeshellarg($string); return escapeshellarg($string);
} }
@ -455,7 +484,7 @@ class Share extends AbstractShare {
* @param string $path * @param string $path
* @return string * @return string
*/ */
protected function escapePath($path) { protected function escapePath(string $path): string {
$this->verifyPath($path); $this->verifyPath($path);
if ($path === '/') { if ($path === '/') {
$path = ''; $path = '';
@ -470,12 +499,18 @@ class Share extends AbstractShare {
* @param string $path * @param string $path
* @return string * @return string
*/ */
protected function escapeLocalPath($path) { protected function escapeLocalPath(string $path): string {
$path = str_replace('"', '\"', $path); $path = str_replace('"', '\"', $path);
return '"' . $path . '"'; return '"' . $path . '"';
} }
protected function getAcls($path) { /**
* @param string $path
* @return ACL[]
* @throws ConnectionException
* @throws ConnectException
*/
protected function getAcls(string $path): array {
$commandPath = $this->system->getSmbcAclsPath(); $commandPath = $this->system->getSmbcAclsPath();
if (!$commandPath) { if (!$commandPath) {
return []; return [];
@ -494,62 +529,11 @@ class Share extends AbstractShare {
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword()); $connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
$connection->connect(); $connection->connect();
if (!$connection->isValid()) { if (!$connection->isValid()) {
throw new ConnectionException($connection->readLine()); throw new ConnectionException((string)$connection->readLine());
} }
$rawAcls = $connection->readAll(); $rawAcls = $connection->readAll();
return $this->parser->parseACLs($rawAcls);
$acls = [];
foreach ($rawAcls as $acl) {
[$type, $acl] = explode(':', $acl, 2);
if ($type !== 'ACL') {
continue;
}
[$user, $permissions] = explode(':', $acl, 2);
[$type, $flags, $mask] = explode('/', $permissions);
$type = $type === 'ALLOWED' ? ACL::TYPE_ALLOW : ACL::TYPE_DENY;
$flagsInt = 0;
foreach (explode('|', $flags) as $flagString) {
if ($flagString === 'OI') {
$flagsInt += ACL::FLAG_OBJECT_INHERIT;
} elseif ($flagString === 'CI') {
$flagsInt += ACL::FLAG_CONTAINER_INHERIT;
}
}
if (substr($mask, 0, 2) === '0x') {
$maskInt = hexdec($mask);
} else {
$maskInt = 0;
foreach (explode('|', $mask) as $maskString) {
if ($maskString === 'R') {
$maskInt += ACL::MASK_READ;
} elseif ($maskString === 'W') {
$maskInt += ACL::MASK_WRITE;
} elseif ($maskString === 'X') {
$maskInt += ACL::MASK_EXECUTE;
} elseif ($maskString === 'D') {
$maskInt += ACL::MASK_DELETE;
} elseif ($maskString === 'READ') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE;
} elseif ($maskString === 'CHANGE') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
} elseif ($maskString === 'FULL') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
}
}
}
if (isset($acls[$user])) {
$existing = $acls[$user];
$maskInt += $existing->getMask();
}
$acls[$user] = new ACL($type, $flagsInt, $maskInt);
}
return $acls;
} }
public function getServer(): IServer { public function getServer(): IServer {

View File

@ -1,3 +1,6 @@
.idea .idea
vendor vendor
composer.lock composer.lock
build
example.php
*.cache

View File

@ -1,24 +0,0 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
env:
global:
- CURRENT_DIR=`pwd`
install:
- composer install --dev --no-interaction
script:
- mkdir -p build/logs
- cd tests
- phpunit --coverage-clover ../build/logs/clover.xml --configuration phpunit.xml
after_script:
- cd $CURRENT_DIR
- php vendor/bin/coveralls -v

View File

@ -1,8 +1,7 @@
# Streams # # Streams #
[![Build Status](https://travis-ci.org/icewind1991/Streams.svg?branch=master)](https://travis-ci.org/icewind1991/Streams) [![CI](https://github.com/icewind1991/Streams/actions/workflows/ci.yaml/badge.svg)](https://github.com/icewind1991/Streams/actions/workflows/ci.yaml)
[![Coverage Status](https://img.shields.io/coveralls/icewind1991/Streams.svg)](https://coveralls.io/r/icewind1991/Streams?branch=master) [![codecov](https://codecov.io/gh/icewind1991/Streams/branch/master/graph/badge.svg?token=bfPcAdGAaq)](https://codecov.io/gh/icewind1991/Streams)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/icewind1991/Streams/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/icewind1991/Streams/?branch=master)
Generic stream wrappers for php. Generic stream wrappers for php.
@ -14,7 +13,7 @@ it wraps an existing stream and can thus be used for any stream in php
The callbacks are passed in the stream context along with the source stream The callbacks are passed in the stream context along with the source stream
and can be any valid [php callable](http://php.net/manual/en/language.types.callable.php) and can be any valid [php callable](http://php.net/manual/en/language.types.callable.php)
###Example### ### Example ###
```php ```php
<?php <?php

View File

@ -1,24 +1,29 @@
{ {
"name" : "icewind/streams", "name": "icewind/streams",
"description" : "A set of generic stream wrappers", "description": "A set of generic stream wrappers",
"license" : "MIT", "license": "MIT",
"authors" : [ "authors": [
{ {
"name" : "Robin Appelman", "name": "Robin Appelman",
"email": "icewind@owncloud.com" "email": "icewind@owncloud.com"
} }
], ],
"require" : { "require": {
"php": ">=5.3" "php": ">=7.1"
}, },
"require-dev" : { "require-dev": {
"satooshi/php-coveralls": "v1.0.0", "phpunit/phpunit": "^9",
"phpunit/phpunit": "^4.8" "friendsofphp/php-cs-fixer": "^2",
"phpstan/phpstan": "^0.12"
}, },
"autoload" : { "autoload": {
"psr-4": { "psr-4": {
"Icewind\\Streams\\Tests\\": "tests/",
"Icewind\\Streams\\": "src/" "Icewind\\Streams\\": "src/"
} }
},
"autoload-dev": {
"psr-4": {
"Icewind\\Streams\\Tests\\": "tests/"
}
} }
} }

View File

@ -25,27 +25,27 @@ namespace Icewind\Streams;
*/ */
class CallbackWrapper extends Wrapper { class CallbackWrapper extends Wrapper {
/** /**
* @var callable * @var callable|null
*/ */
protected $readCallback; protected $readCallback;
/** /**
* @var callable * @var callable|null
*/ */
protected $writeCallback; protected $writeCallback;
/** /**
* @var callable * @var callable|null
*/ */
protected $closeCallback; protected $closeCallback;
/** /**
* @var callable * @var callable|null
*/ */
protected $readDirCallBack; protected $readDirCallBack;
/** /**
* @var callable * @var callable|null
*/ */
protected $preCloseCallback; protected $preCloseCallback;
@ -53,30 +53,28 @@ class CallbackWrapper extends Wrapper {
* Wraps a stream with the provided callbacks * Wraps a stream with the provided callbacks
* *
* @param resource $source * @param resource $source
* @param callable $read (optional) * @param callable|null $read (optional)
* @param callable $write (optional) * @param callable|null $write (optional)
* @param callable $close (optional) * @param callable|null $close (optional)
* @param callable $readDir (optional) * @param callable|null $readDir (optional)
* @return resource * @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) { public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null, $preClose = null) {
$context = stream_context_create(array( $context = [
'callback' => array( 'source' => $source,
'source' => $source, 'read' => $read,
'read' => $read, 'write' => $write,
'write' => $write, 'close' => $close,
'close' => $close, 'readDir' => $readDir,
'readDir' => $readDir, 'preClose' => $preClose,
'preClose' => $preClose, ];
) return self::wrapSource($source, $context);
));
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CallbackWrapper');
} }
protected function open() { protected function open() {
$context = $this->loadContext('callback'); $context = $this->loadContext();
$this->readCallback = $context['read']; $this->readCallback = $context['read'];
$this->writeCallback = $context['write']; $this->writeCallback = $context['write'];
@ -112,7 +110,7 @@ class CallbackWrapper extends Wrapper {
public function stream_close() { public function stream_close() {
if (is_callable($this->preCloseCallback)) { 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 // prevent further calls by potential PHP 7 GC ghosts
$this->preCloseCallback = null; $this->preCloseCallback = null;
} }

View File

@ -55,7 +55,7 @@ class CountWrapper extends Wrapper {
* *
* @param resource $source * @param resource $source
* @param callable $callback * @param callable $callback
* @return resource * @return resource|bool
* *
* @throws \BadMethodCallException * @throws \BadMethodCallException
*/ */
@ -63,17 +63,14 @@ class CountWrapper extends Wrapper {
if (!is_callable($callback)) { if (!is_callable($callback)) {
throw new \InvalidArgumentException('Invalid or missing callback'); throw new \InvalidArgumentException('Invalid or missing callback');
} }
$context = stream_context_create(array( return self::wrapSource($source, [
'count' => array( 'source' => $source,
'source' => $source, 'callback' => $callback
'callback' => $callback ]);
)
));
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CountWrapper');
} }
protected function open() { protected function open() {
$context = $this->loadContext('count'); $context = $this->loadContext();
$this->callback = $context['callback']; $this->callback = $context['callback'];
return true; return true;
} }

View File

@ -19,7 +19,7 @@ interface Directory {
public function dir_opendir($path, $options); public function dir_opendir($path, $options);
/** /**
* @return string * @return string|bool
*/ */
public function dir_readdir(); public function dir_readdir();

View File

@ -25,7 +25,7 @@ class DirectoryFilter extends DirectoryWrapper {
* @return bool * @return bool
*/ */
public function dir_opendir($path, $options) { public function dir_opendir($path, $options) {
$context = $this->loadContext('filter'); $context = $this->loadContext();
$this->filter = $context['filter']; $this->filter = $context['filter'];
return true; return true;
} }
@ -36,7 +36,7 @@ class DirectoryFilter extends DirectoryWrapper {
public function dir_readdir() { public function dir_readdir() {
$file = readdir($this->source); $file = readdir($this->source);
$filter = $this->filter; $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) { while ($file !== false && $filter($file) === false) {
$file = readdir($this->source); $file = readdir($this->source);
} }
@ -46,15 +46,12 @@ class DirectoryFilter extends DirectoryWrapper {
/** /**
* @param resource $source * @param resource $source
* @param callable $filter * @param callable $filter
* @return resource * @return resource|bool
*/ */
public static function wrap($source, callable $filter) { public static function wrap($source, callable $filter) {
$options = array( return self::wrapSource($source, [
'filter' => array( 'source' => $source,
'source' => $source, 'filter' => $filter
'filter' => $filter ]);
)
);
return self::wrapWithOptions($options, '\Icewind\Streams\DirectoryFilter');
} }
} }

View File

@ -7,37 +7,9 @@
namespace Icewind\Streams; namespace Icewind\Streams;
class DirectoryWrapper implements Directory { class DirectoryWrapper extends Wrapper implements Directory {
/** public function stream_open($path, $mode, $options, &$opened_path) {
* @var resource return false;
*/
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;
} }
/** /**
@ -46,7 +18,7 @@ class DirectoryWrapper implements Directory {
* @return bool * @return bool
*/ */
public function dir_opendir($path, $options) { public function dir_opendir($path, $options) {
$this->loadContext('dir'); $this->loadContext();
return true; return true;
} }
@ -72,17 +44,4 @@ class DirectoryWrapper implements Directory {
rewinddir($this->source); rewinddir($this->source);
return true; 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;
}
} }

View File

@ -15,7 +15,7 @@ interface File {
* @param string $path * @param string $path
* @param string $mode * @param string $mode
* @param int $options * @param int $options
* @param string &$opened_path * @param string $opened_path
* @return bool * @return bool
*/ */
public function stream_open($path, $mode, $options, &$opened_path); public function stream_open($path, $mode, $options, &$opened_path);
@ -28,19 +28,19 @@ interface File {
public function stream_seek($offset, $whence = SEEK_SET); public function stream_seek($offset, $whence = SEEK_SET);
/** /**
* @return int * @return int|false
*/ */
public function stream_tell(); public function stream_tell();
/** /**
* @param int $count * @param int $count
* @return string * @return string|false
*/ */
public function stream_read($count); public function stream_read($count);
/** /**
* @param string $data * @param string $data
* @return int * @return int|false
*/ */
public function stream_write($data); public function stream_write($data);
@ -59,7 +59,7 @@ interface File {
public function stream_truncate($size); public function stream_truncate($size);
/** /**
* @return array * @return array|false
*/ */
public function stream_stat(); public function stream_stat();

View File

@ -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();
}
}

View File

@ -20,7 +20,7 @@ namespace Icewind\Streams;
* *
* Either 'array' or 'iterator' need to be set, if both are set, 'iterator' takes preference * 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 * @var resource
*/ */
@ -36,18 +36,13 @@ class IteratorDirectory implements Directory {
* *
* @param string $name * @param string $name
* @return array * @return array
* @throws \Exception * @throws \BadMethodCallException
*/ */
protected function loadContext($name) { protected function loadContext($name = null) {
$context = stream_context_get_options($this->context); $context = parent::loadContext($name);
if (isset($context[$name])) {
$context = $context[$name];
} else {
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
}
if (isset($context['iterator'])) { if (isset($context['iterator'])) {
$this->iterator = $context['iterator']; $this->iterator = $context['iterator'];
} else if (isset($context['array'])) { } elseif (isset($context['array'])) {
$this->iterator = new \ArrayIterator($context['array']); $this->iterator = new \ArrayIterator($context['array']);
} else { } else {
throw new \BadMethodCallException('Invalid context, iterator or array not set'); throw new \BadMethodCallException('Invalid context, iterator or array not set');
@ -61,12 +56,12 @@ class IteratorDirectory implements Directory {
* @return bool * @return bool
*/ */
public function dir_opendir($path, $options) { public function dir_opendir($path, $options) {
$this->loadContext('dir'); $this->loadContext();
return true; return true;
} }
/** /**
* @return string * @return string|bool
*/ */
public function dir_readdir() { public function dir_readdir() {
if ($this->iterator->valid()) { if ($this->iterator->valid()) {
@ -97,27 +92,22 @@ class IteratorDirectory implements Directory {
* Creates a directory handle from the provided array or iterator * Creates a directory handle from the provided array or iterator
* *
* @param \Iterator | array $source * @param \Iterator | array $source
* @return resource * @return resource|bool
* *
* @throws \BadMethodCallException * @throws \BadMethodCallException
*/ */
public static function wrap($source) { public static function wrap($source) {
if ($source instanceof \Iterator) { if ($source instanceof \Iterator) {
$context = stream_context_create(array( $options = [
'dir' => array( 'iterator' => $source
'iterator' => $source) ];
)); } elseif (is_array($source)) {
} else if (is_array($source)) { $options = [
$context = stream_context_create(array( 'array' => $source
'dir' => array( ];
'array' => $source)
));
} else { } else {
throw new \BadMethodCallException('$source should be an Iterator or array'); throw new \BadMethodCallException('$source should be an Iterator or array');
} }
stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); return self::wrapSource(self::NO_SOURCE_DIR, $options);
$wrapped = opendir('iterator://', $context);
stream_wrapper_unregister('iterator');
return $wrapped;
} }
} }

View File

@ -11,29 +11,17 @@ namespace Icewind\Streams;
* Stream wrapper that does nothing, used for tests * Stream wrapper that does nothing, used for tests
*/ */
class NullWrapper extends Wrapper { class NullWrapper extends Wrapper {
/**
* Wraps a stream with the provided callbacks
*
* @param resource $source
* @return resource
*
* @throws \BadMethodCallException
*/
public static function wrap($source) { public static function wrap($source) {
$context = stream_context_create(array( return self::wrapSource($source);
'null' => array(
'source' => $source)
));
return Wrapper::wrapSource($source, $context, 'null', '\Icewind\Streams\NullWrapper');
} }
public function stream_open($path, $mode, $options, &$opened_path) { public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext('null'); $this->loadContext();
return true; return true;
} }
public function dir_opendir($path, $options) { public function dir_opendir($path, $options) {
$this->loadContext('null'); $this->loadContext();
return true; return true;
} }
} }

View File

@ -38,7 +38,7 @@ class Path {
* @param string $class * @param string $class
* @param array $contextOptions * @param array $contextOptions
*/ */
public function __construct($class, $contextOptions = array()) { public function __construct($class, $contextOptions = []) {
$this->class = $class; $this->class = $class;
$this->contextOptions = $contextOptions; $this->contextOptions = $contextOptions;
} }
@ -75,7 +75,7 @@ class Path {
*/ */
protected function appendDefaultContent($values) { protected function appendDefaultContent($values) {
if (!is_array(current($values))) { if (!is_array(current($values))) {
$values = array($this->getProtocol() => $values); $values = [$this->getProtocol() => $values];
} }
$context = stream_context_get_default(); $context = stream_context_get_default();
$defaults = stream_context_get_options($context); $defaults = stream_context_get_options($context);

View File

@ -16,10 +16,8 @@ class PathWrapper extends NullWrapper {
* @return Path|string * @return Path|string
*/ */
public static function getPath($source) { public static function getPath($source) {
return new Path(__CLASS__, [ return new Path(NullWrapper::class, [
'null' => [ NullWrapper::getProtocol() => ['source' => $source]
'source' => $source
]
]); ]);
} }
} }

View File

@ -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;
}
}

View File

@ -11,25 +11,8 @@ namespace Icewind\Streams;
* Wrapper that retries reads/writes to remote streams that dont deliver/recieve all requested data at once * Wrapper that retries reads/writes to remote streams that dont deliver/recieve all requested data at once
*/ */
class RetryWrapper extends Wrapper { class RetryWrapper extends Wrapper {
/**
* Wraps a stream with the provided callbacks
*
* @param resource $source
* @return resource
*/
public static function wrap($source) { public static function wrap($source) {
$context = stream_context_create(array( return self::wrapSource($source);
'retry' => array(
'source' => $source
)
));
return Wrapper::wrapSource($source, $context, 'retry', '\Icewind\Streams\RetryWrapper');
}
protected function open() {
$this->loadContext('retry');
return true;
} }
public function dir_opendir($path, $options) { public function dir_opendir($path, $options) {
@ -37,7 +20,8 @@ class RetryWrapper extends Wrapper {
} }
public function stream_open($path, $mode, $options, &$opened_path) { public function stream_open($path, $mode, $options, &$opened_path) {
return $this->open(); $this->loadContext();
return true;
} }
public function stream_read($count) { public function stream_read($count) {

View File

@ -25,21 +25,8 @@ class SeekableWrapper extends Wrapper {
*/ */
protected $cache; protected $cache;
/**
* Wraps a stream to make it seekable
*
* @param resource $source
* @return resource
*
* @throws \BadMethodCallException
*/
public static function wrap($source) { public static function wrap($source) {
$context = stream_context_create(array( return self::wrapSource($source);
'callback' => array(
'source' => $source
)
));
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\SeekableWrapper');
} }
public function dir_opendir($path, $options) { public function dir_opendir($path, $options) {
@ -47,8 +34,12 @@ class SeekableWrapper extends Wrapper {
} }
public function stream_open($path, $mode, $options, &$opened_path) { public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext('callback'); $this->loadContext();
$this->cache = fopen('php://temp', 'w+'); $cache = fopen('php://temp', 'w+');
if ($cache === false) {
return false;
}
$this->cache = $cache;
return true; return true;
} }
@ -72,7 +63,7 @@ class SeekableWrapper extends Wrapper {
public function stream_seek($offset, $whence = SEEK_SET) { public function stream_seek($offset, $whence = SEEK_SET) {
if ($whence === SEEK_SET) { if ($whence === SEEK_SET) {
$target = $offset; $target = $offset;
} else if ($whence === SEEK_CUR) { } elseif ($whence === SEEK_CUR) {
$current = ftell($this->cache); $current = ftell($this->cache);
$target = $current + $offset; $target = $current + $offset;
} else { } else {

View File

@ -22,7 +22,7 @@ interface Url {
* @param string $path * @param string $path
* @param string $mode * @param string $mode
* @param int $options * @param int $options
* @param string &$opened_path * @param string $opened_path
* @return bool * @return bool
*/ */
public function stream_open($path, $mode, $options, &$opened_path); public function stream_open($path, $mode, $options, &$opened_path);
@ -50,7 +50,7 @@ interface Url {
public function rmdir($path, $options); public function rmdir($path, $options);
/** /**
* @param string * @param string $path
* @return bool * @return bool
*/ */
public function unlink($path); public function unlink($path);
@ -58,7 +58,7 @@ interface Url {
/** /**
* @param string $path * @param string $path
* @param int $flags * @param int $flags
* @return array * @return array|false
*/ */
public function url_stat($path, $flags); public function url_stat($path, $flags);
} }

View File

@ -47,24 +47,30 @@ class UrlCallback extends Wrapper implements Url {
* @return \Icewind\Streams\Path * @return \Icewind\Streams\Path
* *
* @throws \BadMethodCallException * @throws \BadMethodCallException
* @throws \Exception
*/ */
public static function wrap($source, $fopen = null, $opendir = null, $mkdir = null, $rename = null, $rmdir = null, public static function wrap(
$unlink = null, $stat = null) { $source,
$options = array( $fopen = null,
'source' => $source, $opendir = null,
'fopen' => $fopen, $mkdir = null,
$rename = null,
$rmdir = null,
$unlink = null,
$stat = null
) {
return new Path(static::class, [
'source' => $source,
'fopen' => $fopen,
'opendir' => $opendir, 'opendir' => $opendir,
'mkdir' => $mkdir, 'mkdir' => $mkdir,
'rename' => $rename, 'rename' => $rename,
'rmdir' => $rmdir, 'rmdir' => $rmdir,
'unlink' => $unlink, 'unlink' => $unlink,
'stat' => $stat 'stat' => $stat
); ]);
return new Path('\Icewind\Streams\UrlCallBack', $options);
} }
protected function loadContext($url) { protected function loadUrlContext($url) {
list($protocol) = explode('://', $url); list($protocol) = explode('://', $url);
$options = stream_context_get_options($this->context); $options = stream_context_get_options($this->context);
return $options[$protocol]; return $options[$protocol];
@ -77,40 +83,48 @@ class UrlCallback extends Wrapper implements Url {
} }
public function stream_open($path, $mode, $options, &$opened_path) { public function stream_open($path, $mode, $options, &$opened_path) {
$context = $this->loadContext($path); $context = $this->loadUrlContext($path);
$this->callCallBack($context, 'fopen'); $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; return true;
} }
public function dir_opendir($path, $options) { public function dir_opendir($path, $options) {
$context = $this->loadContext($path); $context = $this->loadUrlContext($path);
$this->callCallBack($context, 'opendir'); $this->callCallBack($context, 'opendir');
$this->setSourceStream(opendir($context['source'])); $source = opendir($context['source']);
if ($source === false) {
return false;
}
$this->setSourceStream($source);
return true; return true;
} }
public function mkdir($path, $mode, $options) { public function mkdir($path, $mode, $options) {
$context = $this->loadContext($path); $context = $this->loadUrlContext($path);
$this->callCallBack($context, 'mkdir'); $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) { public function rmdir($path, $options) {
$context = $this->loadContext($path); $context = $this->loadUrlContext($path);
$this->callCallBack($context, 'rmdir'); $this->callCallBack($context, 'rmdir');
return rmdir($context['source']); return rmdir($context['source']);
} }
public function rename($source, $target) { public function rename($source, $target) {
$context = $this->loadContext($source); $context = $this->loadUrlContext($source);
$this->callCallBack($context, 'rename'); $this->callCallBack($context, 'rename');
list(, $target) = explode('://', $target); list(, $target) = explode('://', $target);
return rename($context['source'], $target); return rename($context['source'], $target);
} }
public function unlink($path) { public function unlink($path) {
$context = $this->loadContext($path); $context = $this->loadUrlContext($path);
$this->callCallBack($context, 'unlink'); $this->callCallBack($context, 'unlink');
return unlink($context['source']); return unlink($context['source']);
} }

View File

@ -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 * 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 * @var resource
*/ */
@ -25,44 +25,15 @@ abstract class Wrapper implements File, Directory {
*/ */
protected $source; 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 resource $source
*
* @param string $name
* @return array
* @throws \Exception
*/ */
protected function loadContext($name) { protected function setSourceStream($source) {
$context = stream_context_get_options($this->context); $this->source = $source;
if (isset($context[$name])) { }
$context = $context[$name];
} else { protected function loadContext($name = null) {
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); $context = parent::loadContext($name);
}
if (isset($context['source']) and is_resource($context['source'])) { if (isset($context['source']) and is_resource($context['source'])) {
$this->setSourceStream($context['source']); $this->setSourceStream($context['source']);
} else { } else {
@ -71,13 +42,6 @@ abstract class Wrapper implements File, Directory {
return $context; return $context;
} }
/**
* @param resource $source
*/
protected function setSourceStream($source) {
$this->source = $source;
}
public function stream_seek($offset, $whence = SEEK_SET) { public function stream_seek($offset, $whence = SEEK_SET) {
$result = fseek($this->source, $offset, $whence); $result = fseek($this->source, $offset, $whence);
return $result == 0 ? true : false; return $result == 0 ? true : false;
@ -98,14 +62,13 @@ abstract class Wrapper implements File, Directory {
public function stream_set_option($option, $arg1, $arg2) { public function stream_set_option($option, $arg1, $arg2) {
switch ($option) { switch ($option) {
case STREAM_OPTION_BLOCKING: case STREAM_OPTION_BLOCKING:
stream_set_blocking($this->source, $arg1); return stream_set_blocking($this->source, (bool)$arg1);
break;
case STREAM_OPTION_READ_TIMEOUT: case STREAM_OPTION_READ_TIMEOUT:
stream_set_timeout($this->source, $arg1, $arg2); return stream_set_timeout($this->source, $arg1, $arg2);
break;
case STREAM_OPTION_WRITE_BUFFER: 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) { public function stream_truncate($size) {

View File

@ -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) {
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;
}
}

View File

@ -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);
}
}