Signed-off-by: Robin Appelman <robin@icewind.nl>tags/v22.0.0beta1
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 |
"classmap-authoritative": true | "classmap-authoritative": true | ||||
}, | }, | ||||
"require": { | "require": { | ||||
"icewind/streams": "0.7.1", | |||||
"icewind/smb": "3.2.7" | |||||
"icewind/streams": "0.7.3", | |||||
"icewind/smb": "3.4.0" | |||||
} | } | ||||
} | } |
"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", | |||||
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6", | |||||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/b5c6921f2e91229c9f71556a4713b4fac91fd394", | |||||
"reference": "b5c6921f2e91229c9f71556a4713b4fac91fd394", | |||||
"shasum": "" | "shasum": "" | ||||
}, | }, | ||||
"require": { | "require": { | ||||
"icewind/streams": ">=0.2.0", | |||||
"php": ">=7.1" | |||||
"icewind/streams": ">=0.7.3", | |||||
"php": ">=7.2" | |||||
}, | }, | ||||
"require-dev": { | "require-dev": { | ||||
"friendsofphp/php-cs-fixer": "^2.13", | |||||
"phpunit/phpunit": "^7.0" | |||||
"friendsofphp/php-cs-fixer": "^2.16", | |||||
"phpstan/phpstan": "^0.12.57", | |||||
"phpunit/phpunit": "^8.5|^9.3.8", | |||||
"psalm/phar": "^4.3" | |||||
}, | }, | ||||
"type": "library", | "type": "library", | ||||
"autoload": { | "autoload": { | ||||
} | } | ||||
], | ], | ||||
"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", | |||||
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121", | |||||
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/22ef9fc5b50d645dbc202206a656cc4dde28f95c", | |||||
"reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c", | |||||
"shasum": "" | "shasum": "" | ||||
}, | }, | ||||
"require": { | "require": { | ||||
"php": ">=5.3" | |||||
"php": ">=7.1" | |||||
}, | }, | ||||
"require-dev": { | "require-dev": { | ||||
"phpunit/phpunit": "^4.8", | |||||
"satooshi/php-coveralls": "v1.0.0" | |||||
"friendsofphp/php-cs-fixer": "^2", | |||||
"phpstan/phpstan": "^0.12", | |||||
"phpunit/phpunit": "^9" | |||||
}, | }, | ||||
"type": "library", | "type": "library", | ||||
"autoload": { | "autoload": { | ||||
"psr-4": { | "psr-4": { | ||||
"Icewind\\Streams\\Tests\\": "tests/", | |||||
"Icewind\\Streams\\": "src/" | "Icewind\\Streams\\": "src/" | ||||
} | } | ||||
}, | }, | ||||
} | } | ||||
], | ], | ||||
"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": [], | ||||
"prefer-lowest": false, | "prefer-lowest": false, | ||||
"platform": [], | "platform": [], | ||||
"platform-dev": [], | "platform-dev": [], | ||||
"plugin-api-version": "1.1.0" | |||||
"plugin-api-version": "2.0.0" | |||||
} | } |
* | * | ||||
* @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 http://www.php-fig.org/psr/psr-4/ | |||||
* @see https://www.php-fig.org/psr/psr-0/ | |||||
* @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(); | ||||
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(); | ||||
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; | |||||
} | |||||
} | } | ||||
/** | /** | ||||
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]); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
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 |
<?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; | |||||
} | |||||
} |
$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', | ||||
'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', | ||||
'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', | |||||
); | ); |
$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'), | ||||
); | ); |
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 { |
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', | ||||
); | ); | ||||
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', | ||||
'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', | ||||
'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) |
[ | |||||
{ | |||||
"name": "icewind/smb", | |||||
"version": "v3.2.7", | |||||
"version_normalized": "3.2.7.0", | |||||
"source": { | |||||
"type": "git", | |||||
"url": "https://github.com/icewind1991/SMB.git", | |||||
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6" | |||||
{ | |||||
"packages": [ | |||||
{ | |||||
"name": "icewind/smb", | |||||
"version": "v3.4.0", | |||||
"version_normalized": "3.4.0.0", | |||||
"source": { | |||||
"type": "git", | |||||
"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", | |||||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/743a7bf35317f1b76cf8e8b804e54a6c5faacad6", | |||||
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6", | |||||
"shasum": "" | |||||
}, | |||||
"require": { | |||||
"icewind/streams": ">=0.2.0", | |||||
"php": ">=7.1" | |||||
}, | |||||
"require-dev": { | |||||
"friendsofphp/php-cs-fixer": "^2.13", | |||||
"phpunit/phpunit": "^7.0" | |||||
}, | |||||
"time": "2020-09-03T13:00:22+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" | |||||
}, | |||||
{ | |||||
"name": "icewind/streams", | |||||
"version": "v0.7.1", | |||||
"version_normalized": "0.7.1.0", | |||||
"source": { | |||||
"type": "git", | |||||
"url": "https://github.com/icewind1991/Streams.git", | |||||
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121" | |||||
}, | |||||
"dist": { | |||||
"type": "zip", | |||||
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/4db3ed6c366e90b958d00e1d4c6360a9b39b2121", | |||||
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121", | |||||
"shasum": "" | |||||
}, | |||||
"require": { | |||||
"php": ">=5.3" | |||||
}, | |||||
"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" | |||||
} | |||||
] | |||||
{ | |||||
"name": "icewind/streams", | |||||
"version": "v0.7.3", | |||||
"version_normalized": "0.7.3.0", | |||||
"source": { | |||||
"type": "git", | |||||
"url": "https://github.com/icewind1991/Streams.git", | |||||
"reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c" | |||||
}, | |||||
"dist": { | |||||
"type": "zip", | |||||
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/22ef9fc5b50d645dbc202206a656cc4dde28f95c", | |||||
"reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c", | |||||
"shasum": "" | |||||
}, | |||||
"require": { | |||||
"php": ">=7.1" | |||||
}, | |||||
"require-dev": { | |||||
"friendsofphp/php-cs-fixer": "^2", | |||||
"phpstan/phpstan": "^0.12", | |||||
"phpunit/phpunit": "^9" | |||||
}, | |||||
"time": "2021-03-02T19:33:35+00:00", | |||||
"type": "library", | |||||
"installation-source": "dist", | |||||
"autoload": { | |||||
"psr-4": { | |||||
"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", | |||||
"support": { | |||||
"issues": "https://github.com/icewind1991/Streams/issues", | |||||
"source": "https://github.com/icewind1991/Streams/tree/v0.7.3" | |||||
}, | |||||
"install-path": "../icewind/streams" | |||||
} | |||||
], | |||||
"dev": true, | |||||
"dev-package-names": [] | |||||
} |
<?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', | |||||
), | |||||
), | |||||
); |
<?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 | |||||
); | |||||
} |
.php_cs.cache | .php_cs.cache | ||||
listen.php | listen.php | ||||
test.php | test.php | ||||
*.cache |
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) | |||||
[![Build Status](https://travis-ci.org/icewind1991/SMB.svg?branch=master)](https://travis-ci.org/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) | |||||
[![CI](https://github.com/icewind1991/SMB/actions/workflows/ci.yaml/badge.svg)](https://github.com/icewind1991/SMB/actions/workflows/ci.yaml) | |||||
[![codecov](https://codecov.io/gh/icewind1991/SMB/branch/master/graph/badge.svg?token=eTg0P466k6)](https://codecov.io/gh/icewind1991/SMB) | |||||
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) | ||||
``` | ``` | ||||
**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'); | ||||
$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`. | ||||
{ | { | ||||
"name" : "icewind/smb", | |||||
"description" : "php wrapper for smbclient and libsmbclient-php", | |||||
"license" : "MIT", | |||||
"authors" : [ | |||||
{ | |||||
"name" : "Robin Appelman", | |||||
"email": "icewind@owncloud.com" | |||||
} | |||||
], | |||||
"require" : { | |||||
"php": ">=7.1", | |||||
"icewind/streams": ">=0.2.0" | |||||
}, | |||||
"require-dev": { | |||||
"phpunit/phpunit": "^7.0", | |||||
"friendsofphp/php-cs-fixer": "^2.13" | |||||
}, | |||||
"autoload" : { | |||||
"psr-4": { | |||||
"Icewind\\SMB\\": "src/" | |||||
} | |||||
}, | |||||
"autoload-dev" : { | |||||
"psr-4": { | |||||
"Icewind\\SMB\\Test\\": "tests/" | |||||
} | |||||
} | |||||
"name": "icewind/smb", | |||||
"description": "php wrapper for smbclient and libsmbclient-php", | |||||
"license": "MIT", | |||||
"authors": [ | |||||
{ | |||||
"name": "Robin Appelman", | |||||
"email": "icewind@owncloud.com" | |||||
} | |||||
], | |||||
"require": { | |||||
"php": ">=7.2", | |||||
"icewind/streams": ">=0.7.3" | |||||
}, | |||||
"require-dev": { | |||||
"phpunit/phpunit": "^8.5|^9.3.8", | |||||
"friendsofphp/php-cs-fixer": "^2.16", | |||||
"phpstan/phpstan": "^0.12.57", | |||||
"psalm/phar": "^4.3" | |||||
}, | |||||
"autoload": { | |||||
"psr-4": { | |||||
"Icewind\\SMB\\": "src/" | |||||
} | |||||
}, | |||||
"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" | |||||
} | |||||
} | } |
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) { |
abstract class AbstractServer implements IServer { | abstract class AbstractServer implements IServer { | ||||
const LOCALE = 'en_US.UTF-8'; | const LOCALE = 'en_US.UTF-8'; | ||||
/** | |||||
* @var string $host | |||||
*/ | |||||
/** @var string */ | |||||
protected $host; | protected $host; | ||||
/** | |||||
* @var IAuth $user | |||||
*/ | |||||
/** @var IAuth */ | |||||
protected $auth; | protected $auth; | ||||
/** | |||||
* @var ISystem | |||||
*/ | |||||
/** @var ISystem */ | |||||
protected $system; | protected $system; | ||||
/** | |||||
* @var TimeZoneProvider | |||||
*/ | |||||
/** @var ITimeZoneProvider */ | |||||
protected $timezoneProvider; | protected $timezoneProvider; | ||||
/** @var IOptions */ | /** @var IOptions */ | ||||
* @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; | ||||
$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; | ||||
} | } | ||||
} | } |
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'); | ||||
} | } | ||||
} | } | ||||
public function setForbiddenChars(array $charList) { | |||||
/** | |||||
* @param string[] $charList | |||||
*/ | |||||
public function setForbiddenChars(array $charList): void { | |||||
$this->forbiddenCharacters = $charList; | $this->forbiddenCharacters = $charList; | ||||
} | } | ||||
} | } |
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); | ||||
} | } | ||||
} | } |
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; | ||||
/** | |||||
* BasicAuth constructor. | |||||
* | |||||
* @param string $username | |||||
* @param string $workgroup | |||||
* @param string $password | |||||
*/ | |||||
public function __construct($username, $workgroup, $password) { | |||||
public function __construct(string $username, ?string $workgroup, string $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 | ||||
} | } | ||||
} | } |
namespace Icewind\SMB; | namespace Icewind\SMB; | ||||
class Change { | class Change { | ||||
/** @var int */ | |||||
private $code; | private $code; | ||||
/** @var string */ | |||||
private $path; | private $path; | ||||
/** | |||||
* Change constructor. | |||||
* | |||||
* @param $code | |||||
* @param $path | |||||
*/ | |||||
public function __construct($code, $path) { | |||||
public function __construct(int $code, string $path) { | |||||
$this->code = $code; | $this->code = $code; | ||||
$this->path = $path; | $this->path = $path; | ||||
} | } | ||||
/** | |||||
* @return integer | |||||
*/ | |||||
public function getCode() { | |||||
public function getCode(): int { | |||||
return $this->code; | return $this->code; | ||||
} | } | ||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getPath() { | |||||
public function getPath(): string { | |||||
return $this->path; | return $this->path; | ||||
} | } | ||||
} | } |
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) { | |||||
$message = 'Unknown error (' . $error . ')'; | |||||
public function __construct(string $message = "", int $code = 0, Throwable $previous = null) { | |||||
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 mixed $error | |||||
* @param string $path | |||||
* @param array<int|string, class-string<Exception>> $exceptionMap | |||||
* @param string|int|null $error | |||||
* @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)) { |
*/ | */ | ||||
protected $path; | protected $path; | ||||
/** | |||||
* @param string $path | |||||
* @param int $code | |||||
*/ | |||||
public function __construct($path, $code = 0) { | |||||
public function __construct(string $path = "", int $code = 0, \Throwable $previous = null) { | |||||
$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; | ||||
} | } | ||||
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); | ||||
} | } | ||||
} | } |
namespace Icewind\SMB; | namespace Icewind\SMB; | ||||
interface IAuth { | interface IAuth { | ||||
/** | |||||
* @return string|null | |||||
*/ | |||||
public function getUsername(); | |||||
public function getUsername(): ?string; | |||||
/** | |||||
* @return string|null | |||||
*/ | |||||
public function getWorkgroup(); | |||||
public function getWorkgroup(): ?string; | |||||
/** | |||||
* @return string|null | |||||
*/ | |||||
public function getPassword(); | |||||
public function getPassword(): ?string; | |||||
/** | /** | ||||
* 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; | |||||
} | } |
const MODE_ARCHIVE = 0x20; | const MODE_ARCHIVE = 0x20; | ||||
const MODE_NORMAL = 0x80; | const MODE_NORMAL = 0x80; | ||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getPath(); | |||||
public function getPath(): string; | |||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getName(); | |||||
public function getName(): string; | |||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getSize(); | |||||
public function getSize(): int; | |||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getMTime(); | |||||
public function getMTime(): int; | |||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isDirectory(); | |||||
public function isDirectory(): bool; | |||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isReadOnly(); | |||||
public function isReadOnly(): bool; | |||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isHidden(); | |||||
public function isHidden(): bool; | |||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isSystem(); | |||||
public function isSystem(): bool; | |||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isArchived(); | |||||
public function isArchived(): bool; | |||||
/** | /** | ||||
* @return ACL[] | * @return ACL[] |
* | * | ||||
* @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; | |||||
} | } |
namespace Icewind\SMB; | namespace Icewind\SMB; | ||||
interface IOptions { | interface IOptions { | ||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getTimeout(); | |||||
const PROTOCOL_NT1 = 'NT1'; | |||||
const PROTOCOL_SMB2 = 'SMB2'; | |||||
const PROTOCOL_SMB2_02 = 'SMB2_02'; | |||||
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; | |||||
} | } |
namespace Icewind\SMB; | namespace Icewind\SMB; | ||||
interface IServer { | interface IServer { | ||||
/** | |||||
* @return IAuth | |||||
*/ | |||||
public function getAuth(); | |||||
public function getAuth(): IAuth; | |||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getHost(); | |||||
public function getHost(): string; | |||||
/** | /** | ||||
* @return \Icewind\SMB\IShare[] | * @return \Icewind\SMB\IShare[] | ||||
* @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; | |||||
/** | |||||
* @param string $name | |||||
* @return \Icewind\SMB\IShare | |||||
*/ | |||||
public function getShare($name); | |||||
public function getShare(string $name): IShare; | |||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getTimeZone(); | |||||
public function getTimeZone(): string; | |||||
/** | |||||
* @return ISystem | |||||
*/ | |||||
public function getSystem(); | |||||
public function getSystem(): ISystem; | |||||
/** | |||||
* @return IOptions | |||||
*/ | |||||
public function getOptions(); | |||||
public function getOptions(): IOptions; | |||||
/** | |||||
* @param ISystem $system | |||||
* @return bool | |||||
*/ | |||||
public static function available(ISystem $system); | |||||
public static function available(ISystem $system): bool; | |||||
} | } |
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 | ||||
* @param string $target local file | * @param string $target local file | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @throws InvalidTypeException | |||||
*/ | */ | ||||
public function get($source, $target); | |||||
public function get(string $source, string $target): bool; | |||||
/** | /** | ||||
* Upload a local file | * Upload a local file | ||||
* @param string $target remove file | * @param string $target remove file | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
* @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 \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
* @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 \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
* @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 \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws \Icewind\SMB\Exception\InvalidRequestException | |||||
* @throws NotFoundException | |||||
* @throws InvalidTypeException | |||||
* @throws InvalidRequestException | |||||
*/ | */ | ||||
public function append($target); | |||||
public function append(string $target); | |||||
/** | /** | ||||
* Rename a remote file | * Rename a remote file | ||||
* @param string $to | * @param string $to | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | |||||
* @return \Icewind\SMB\IFileInfo[] | |||||
* @param string $path | |||||
* @return IFileInfo[] | |||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||||
* @throws NotFoundException | |||||
* @throws AlreadyExistsException | |||||
*/ | */ | ||||
public function mkdir($path); | |||||
public function mkdir(string $path): bool; | |||||
/** | /** | ||||
* Remove a folder on the share | * Remove a folder on the share | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 |
* @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; | |||||
} | } |
* @param string $host | * @param string $host | ||||
* @return string | * @return string | ||||
*/ | */ | ||||
public function get($host); | |||||
public function get(string $host): string; | |||||
} | } |
* 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); | ||||
} | } |
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|null | |||||
*/ | |||||
/** @var array{"mode": int, "size": int, "write_time": int}|null */ | |||||
protected $attributeCache = null; | protected $attributeCache = null; | ||||
/** | |||||
* @param NativeShare $share | |||||
* @param string $path | |||||
* @param string $name | |||||
*/ | |||||
public function __construct($share, $path, $name) { | |||||
public function __construct(NativeShare $share, string $path, string $name) { | |||||
$this->share = $share; | $this->share = $share; | ||||
$this->path = $path; | $this->path = $path; | ||||
$this->name = $name; | $this->name = $name; | ||||
} | } | ||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getPath() { | |||||
public function getPath(): string { | |||||
return $this->path; | return $this->path; | ||||
} | } | ||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getName() { | |||||
public function getName(): string { | |||||
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; | ||||
} | } | ||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getSize() { | |||||
public function getSize(): int { | |||||
$stat = $this->stat(); | $stat = $this->stat(); | ||||
return $stat['size']; | return $stat['size']; | ||||
} | } | ||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getMTime() { | |||||
public function getMTime(): int { | |||||
$stat = $this->stat(); | $stat = $this->stat(); | ||||
return $stat['change_time']; | |||||
return $stat['write_time']; | |||||
} | } | ||||
/** | /** | ||||
* as false (except for `hidden` where we use the unix dotfile convention) | * as false (except for `hidden` where we use the unix dotfile convention) | ||||
*/ | */ | ||||
/** | |||||
* @return int | |||||
*/ | |||||
protected function getMode() { | |||||
protected function getMode(): int { | |||||
$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; | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isDirectory() { | |||||
public function isDirectory(): bool { | |||||
$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 | ||||
} | } | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isReadOnly() { | |||||
public function isReadOnly(): bool { | |||||
$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 | ||||
} | } | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isHidden() { | |||||
public function isHidden(): bool { | |||||
$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] === '.'; | ||||
} | } | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isSystem() { | |||||
public function isSystem(): bool { | |||||
$mode = $this->getMode(); | $mode = $this->getMode(); | ||||
if ($mode > 0x1000) { | if ($mode > 0x1000) { | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isArchived() { | |||||
public function isArchived(): bool { | |||||
$mode = $this->getMode(); | $mode = $this->getMode(); | ||||
if ($mode > 0x1000) { | if ($mode > 0x1000) { | ||||
return false; | return false; | ||||
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; |
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) { | |||||
stream_wrapper_register('nativesmb', 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 static function wrap(NativeState $state, $smbStream, string $mode, string $url) { | |||||
return parent::wrapClass($state, $smbStream, $mode, $url, NativeReadStream::class); | |||||
} | } | ||||
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) { | |||||
$remaining = $this->readBuffer; | |||||
$this->readBuffer = fopen('php://memory', 'r+'); | |||||
$this->bufferSize = 0; | |||||
stream_copy_to_stream($remaining, $this->readBuffer); | |||||
$this->bufferSize += fwrite($this->readBuffer, parent::stream_read(self::CHUNK_SIZE)); | |||||
fseek($this->readBuffer, 0); | |||||
if (!parent::stream_eof() && $this->readBuffer->remaining() < $count) { | |||||
$chunk = parent::stream_read(self::CHUNK_SIZE); | |||||
if ($chunk === false) { | |||||
return false; | |||||
} | |||||
$this->readBuffer->push($chunk); | |||||
} | } | ||||
$result = fread($this->readBuffer, $count); | |||||
$this->bufferSize -= $count; | |||||
$result = $this->readBuffer->read($count); | |||||
$read = strlen($result); | $read = strlen($result); | ||||
$this->pos += $read; | $this->pos += $read; | ||||
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->bufferSize = 0; | |||||
$this->pos = parent::stream_tell(); | |||||
$this->readBuffer->clear(); | |||||
$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() { |
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 { | ||||
/** | /** | ||||
*/ | */ | ||||
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[] | |||||
* @throws \Icewind\SMB\Exception\AuthenticationException | |||||
* @throws \Icewind\SMB\Exception\InvalidHostException | |||||
* @return IShare[] | |||||
* @throws AuthenticationException | |||||
* @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; | ||||
} | } | ||||
/** | |||||
* @param string $name | |||||
* @return \Icewind\SMB\IShare | |||||
*/ | |||||
public function getShare($name) { | |||||
public function getShare(string $name): IShare { | |||||
return new NativeShare($this, $name); | return new NativeShare($this, $name); | ||||
} | } | ||||
* @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(); | ||||
} | } | ||||
} | } |
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; | ||||
*/ | */ | ||||
private $name; | private $name; | ||||
/** | |||||
* @var NativeState $state | |||||
*/ | |||||
private $state; | |||||
/** @var NativeState|null $state */ | |||||
private $state = null; | |||||
/** | |||||
* @param IServer $server | |||||
* @param string $name | |||||
*/ | |||||
public function __construct($server, $name) { | |||||
public function __construct(IServer $server, string $name) { | |||||
parent::__construct(); | parent::__construct(); | ||||
$this->server = $server; | $this->server = $server; | ||||
$this->name = $name; | $this->name = $name; | ||||
} | } | ||||
/** | /** | ||||
* @throws \Icewind\SMB\Exception\ConnectionException | |||||
* @throws \Icewind\SMB\Exception\AuthenticationException | |||||
* @throws \Icewind\SMB\Exception\InvalidHostException | |||||
* @throws ConnectionException | |||||
* @throws AuthenticationException | |||||
* @throws InvalidHostException | |||||
*/ | */ | ||||
protected function getState() { | |||||
if ($this->state and $this->state instanceof NativeState) { | |||||
protected function getState(): NativeState { | |||||
if ($this->state) { | |||||
return $this->state; | return $this->state; | ||||
} | } | ||||
* | * | ||||
* @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) { | ||||
* 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 \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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; | ||||
} | } | ||||
} | } | ||||
$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 | ||||
* Multibyte unicode safe version of basename() | * Multibyte unicode safe version of basename() | ||||
* | * | ||||
* @param string $path | * @param string $path | ||||
* @link https://www.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)) { | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||||
* @throws NotFoundException | |||||
* @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)); | ||||
} | } | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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)); | ||||
} | } | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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)); | ||||
} | } | ||||
* @param string $to | * @param string $to | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||||
* @throws NotFoundException | |||||
* @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)); | ||||
} | } | ||||
* @param string $target remove file | * @param string $target remove file | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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); | ||||
* @param string $target local file | * @param string $target local file | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws \Icewind\SMB\Exception\InvalidPathException | |||||
* @throws \Icewind\SMB\Exception\InvalidResourceException | |||||
* @throws AuthenticationException | |||||
* @throws ConnectionException | |||||
* @throws InvalidHostException | |||||
* @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) { | ||||
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)); | ||||
* @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 \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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); | ||||
* @param string $source | * @param string $source | ||||
* @return resource a writeable stream | * @return resource a writeable stream | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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); | ||||
* @param string $source | * @param string $source | ||||
* @return resource a writeable stream | * @return resource a writeable stream | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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); | ||||
* @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); | ||||
} | } | ||||
* @param string|int $value | * @param string|int $value | ||||
* @return mixed the attribute value | * @return mixed the attribute value | ||||
*/ | */ | ||||
public function setAttribute($path, $attribute, $value) { | |||||
if ($attribute === 'system.dos_attr.mode' and is_int($value)) { | |||||
$value = '0x' . dechex($value); | |||||
public function setAttribute(string $path, string $attribute, $value) { | |||||
if (is_int($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); | ||||
* @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); | ||||
} | } | ||||
* @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())) { |
* Low level wrapper for libsmbclient-php with error handling | * Low level wrapper for libsmbclient-php with error handling | ||||
*/ | */ | ||||
class NativeState { | class NativeState { | ||||
/** | |||||
* @var resource | |||||
*/ | |||||
protected $state; | |||||
/** @var resource|null */ | |||||
protected $state = null; | |||||
/** @var bool */ | |||||
protected $handlerSet = false; | protected $handlerSet = false; | ||||
/** @var bool */ | |||||
protected $connected = false; | protected $connected = false; | ||||
// see error.h | // see error.h | ||||
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; | ||||
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); | ||||
} | } | ||||
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, ''); | ||||
* @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); | ||||
/** | /** | ||||
* @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; | ||||
} | } | ||||
* @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); | ||||
* @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); | ||||
* @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); | ||||
* @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); | ||||
/** | /** | ||||
* @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); | ||||
/** | /** | ||||
* @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; | ||||
} | } | ||||
* @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); | ||||
* @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); | ||||
/** | /** | ||||
* @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; | ||||
} | } | ||||
* @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); | ||||
* @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); | ||||
* @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); | ||||
* @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); |
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; | ||||
/** | /** | ||||
* @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) { | |||||
stream_wrapper_register('nativesmb', NativeStream::class); | |||||
protected static function wrapClass(NativeState $state, $smbStream, string $mode, string $url, string $class) { | |||||
stream_wrapper_register('nativesmb', $class); | |||||
$context = stream_context_create([ | $context = stream_context_create([ | ||||
'nativesmb' => [ | 'nativesmb' => [ | ||||
'state' => $state, | 'state' => $state, | ||||
} | } | ||||
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']; | |||||
$this->handle = $context['nativesmb']['handle']; | |||||
$this->url = $context['nativesmb']['url']; | |||||
if (!isset($context['nativesmb']) || !is_array($context['nativesmb'])) { | |||||
throw new InvalidArgumentException("context not set"); | |||||
} | |||||
$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; | ||||
} | } | ||||
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); | ||||
} | } | ||||
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) { | ||||
} | } | ||||
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) { |
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) { | |||||
$this->writeBuffer = fopen('php://memory', 'r+'); | |||||
public function __construct() { | |||||
$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) { | |||||
stream_wrapper_register('nativesmb', 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 static function wrap(NativeState $state, $smbStream, string $mode, string $url) { | |||||
return parent::wrapClass($state, $smbStream, $mode, $url, NativeWriteStream::class); | |||||
} | } | ||||
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() { | |||||
rewind($this->writeBuffer); | |||||
$this->state->write($this->handle, stream_get_contents($this->writeBuffer), $this->url); | |||||
$this->writeBuffer = fopen('php://memory', 'r+'); | |||||
$this->bufferSize = 0; | |||||
private function flushWrite(): void { | |||||
parent::stream_write($this->writeBuffer->flush()); | |||||
} | } | ||||
public function stream_write($data) { | public function stream_write($data) { | ||||
$written = fwrite($this->writeBuffer, $data); | |||||
$this->bufferSize += $written; | |||||
$written = $this->writeBuffer->push($data); | |||||
$this->pos += $written; | $this->pos += $written; | ||||
if ($this->bufferSize >= self::CHUNK_SIZE) { | |||||
if ($this->writeBuffer->remaining() >= self::CHUNK_SIZE) { | |||||
$this->flushWrite(); | $this->flushWrite(); | ||||
} | } | ||||
/** @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; | |||||
} | |||||
} | } |
Server::class | Server::class | ||||
]; | ]; | ||||
/** @var System */ | |||||
/** @var ISystem */ | |||||
private $system; | private $system; | ||||
/** @var IOptions */ | /** @var IOptions */ | ||||
/** | /** | ||||
* @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); |
<?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; | |||||
} | |||||
} |
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 = []; | ||||
/** | /** | ||||
* @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' | ||||
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]; | ||||
} | } |
$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(); |
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; | ||||
/** @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; | ||||
} | } | ||||
* | * | ||||
* @param string $input | * @param string $input | ||||
*/ | */ | ||||
public function write($input) { | |||||
parent::write($input . PHP_EOL); | |||||
public function write(string $input) { | |||||
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 = []; | ||||
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 | ||||
break; | break; | ||||
} | } | ||||
} else { | } else { | ||||
$output[] .= $line; | |||||
$output[] = $line; | |||||
} | } | ||||
$line = $this->readLine(); | $line = $this->readLine(); | ||||
} | } | ||||
return $output; | return $output; | ||||
} | } | ||||
/** | |||||
* Check | |||||
* | |||||
* @param $line | |||||
* @return bool | |||||
*/ | |||||
private function isPrompt($line) { | |||||
return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER || $line === false; | |||||
private function isPrompt(string $line): bool { | |||||
return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER; | |||||
} | } | ||||
/** | /** | ||||
* @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 | ||||
} | } | ||||
} | } | ||||
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); |
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 | |||||
*/ | |||||
/** @var callable(): ACL[] */ | |||||
protected $aclCallback; | protected $aclCallback; | ||||
/** | /** | ||||
* @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; | ||||
/** | /** | ||||
* @return string | * @return string | ||||
*/ | */ | ||||
public function getPath() { | |||||
public function getPath(): string { | |||||
return $this->path; | return $this->path; | ||||
} | } | ||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getName() { | |||||
public function getName(): string { | |||||
return $this->name; | return $this->name; | ||||
} | } | ||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getSize() { | |||||
public function getSize(): int { | |||||
return $this->size; | return $this->size; | ||||
} | } | ||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getMTime() { | |||||
public function getMTime(): int { | |||||
return $this->time; | return $this->time; | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isDirectory() { | |||||
public function isDirectory(): bool { | |||||
return (bool)($this->mode & IFileInfo::MODE_DIRECTORY); | return (bool)($this->mode & IFileInfo::MODE_DIRECTORY); | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isReadOnly() { | |||||
public function isReadOnly(): bool { | |||||
return (bool)($this->mode & IFileInfo::MODE_READONLY); | return (bool)($this->mode & IFileInfo::MODE_READONLY); | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isHidden() { | |||||
public function isHidden(): bool { | |||||
return (bool)($this->mode & IFileInfo::MODE_HIDDEN); | return (bool)($this->mode & IFileInfo::MODE_HIDDEN); | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isSystem() { | |||||
public function isSystem(): bool { | |||||
return (bool)($this->mode & IFileInfo::MODE_SYSTEM); | return (bool)($this->mode & IFileInfo::MODE_SYSTEM); | ||||
} | } | ||||
/** | |||||
* @return bool | |||||
*/ | |||||
public function isArchived() { | |||||
public function isArchived(): bool { | |||||
return (bool)($this->mode & IFileInfo::MODE_ARCHIVE); | return (bool)($this->mode & IFileInfo::MODE_ARCHIVE); | ||||
} | } | ||||
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 | ||||
* @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; | ||||
} | } | ||||
* | * | ||||
* @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))); | ||||
} | } | ||||
* | * | ||||
* 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; | ||||
} | } | ||||
} | } | ||||
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(); | ||||
} | } |
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; | ||||
*/ | */ | ||||
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, | ||||
/** | /** | ||||
* @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); | ||||
} | } | ||||
/** | /** | ||||
* 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'); | ||||
} | } | ||||
} | } | ||||
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) { | ||||
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 | ||||
$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+(.*)$/'; | ||||
$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); | ||||
}); | }); | ||||
} | } | ||||
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, '|')) { | ||||
} | } | ||||
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; | |||||
} | |||||
} | } |
* $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; | ||||
*/ | */ | ||||
private $authStream = null; | private $authStream = null; | ||||
private $connected = false; | |||||
public function __construct($command, array $env = []) { | |||||
/** | |||||
* @param string $command | |||||
* @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'); | ||||
} | } | ||||
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; | ||||
} | } | ||||
* send input to the process | * send input to the process | ||||
* | * | ||||
* @param string $input | * @param string $input | ||||
* @return int|bool | |||||
*/ | */ | ||||
public function write($input) { | |||||
fwrite($this->getInputStream(), $input); | |||||
public function write(string $input) { | |||||
$result = @fwrite($this->getInputStream(), $input); | |||||
fflush($this->getInputStream()); | fflush($this->getInputStream()); | ||||
return $result; | |||||
} | } | ||||
/** | /** | ||||
/** | /** | ||||
* 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; | ||||
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; | ||||
} | } | ||||
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(); | ||||
} | } |
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; | ||||
* @param ISystem $system | * @param ISystem $system | ||||
* @return bool | * @return bool | ||||
*/ | */ | ||||
public static function available(ISystem $system) { | |||||
return $system->getSmbclientPath(); | |||||
public static function available(ISystem $system): bool { | |||||
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 { | ||||
* @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', | |||||
$this->system->getSmbclientPath(), | |||||
'%s %s %s %s %s -L %s', | |||||
$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])) { | ||||
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); | ||||
* @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); | ||||
} | } | ||||
} | } |
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; | ||||
private $name; | private $name; | ||||
/** | /** | ||||
* @var Connection $connection | |||||
* @var Connection|null $connection | |||||
*/ | */ | ||||
public $connection; | |||||
public $connection = null; | |||||
/** | /** | ||||
* @var Parser | * @var Parser | ||||
* @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; | ||||
$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 { | ||||
} | } | ||||
} | } | ||||
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 ' : '', | |||||
$this->system->getSmbclientPath(), | |||||
$stdBuf ? $stdBuf . ' -o0 ' : '', | |||||
$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(); | ||||
} | } | ||||
/** | /** | ||||
* @throws \Icewind\SMB\Exception\ConnectionException | |||||
* @throws \Icewind\SMB\Exception\AuthenticationException | |||||
* @throws \Icewind\SMB\Exception\InvalidHostException | |||||
* @throws ConnectionException | |||||
* @throws AuthenticationException | |||||
* @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(); | |||||
if (!$this->connection->isValid()) { | |||||
throw new ConnectionException(); | |||||
/** | |||||
* @throws ConnectionException | |||||
* @throws AuthenticationException | |||||
* @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(); | |||||
} | |||||
} | } | ||||
} | } | ||||
* | * | ||||
* @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); | ||||
/** | /** | ||||
* List the content of a remote folder | * List the content of a remote folder | ||||
* | * | ||||
* @param $path | |||||
* @return \Icewind\SMB\IFileInfo[] | |||||
* @param string $path | |||||
* @return IFileInfo[] | |||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
$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 !== "/") { | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||||
* @throws NotFoundException | |||||
* @throws AlreadyExistsException | |||||
*/ | */ | ||||
public function mkdir($path) { | |||||
public function mkdir(string $path): bool { | |||||
return $this->simpleCommand('mkdir', $path); | return $this->simpleCommand('mkdir', $path); | ||||
} | } | ||||
* @param string $path | * @param string $path | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @throws InvalidTypeException | |||||
*/ | */ | ||||
public function rmdir($path) { | |||||
public function rmdir(string $path): bool { | |||||
return $this->simpleCommand('rmdir', $path); | return $this->simpleCommand('rmdir', $path); | ||||
} | } | ||||
* @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 { | ||||
* @param string $to | * @param string $to | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||||
* @throws NotFoundException | |||||
* @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); | ||||
* @param string $target remove file | * @param string $target remove file | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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); | ||||
* @param string $target local file | * @param string $target local file | ||||
* @return bool | * @return bool | ||||
* | * | ||||
* @throws \Icewind\SMB\Exception\NotFoundException | |||||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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); | ||||
* @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 \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
* @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 \Icewind\SMB\Exception\InvalidTypeException | |||||
* @throws NotFoundException | |||||
* @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 | ||||
// 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); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
* | * | ||||
* @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'); | ||||
} | } | ||||
* @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) { | ||||
* @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'); | ||||
} | } | ||||
/** | /** | ||||
* @param string $command | * @param string $command | ||||
* @return array | |||||
* @return string[] | |||||
*/ | */ | ||||
protected function execute($command) { | |||||
$this->connect(); | |||||
$this->connection->write($command . PHP_EOL); | |||||
return $this->connection->read(); | |||||
protected function execute(string $command): array { | |||||
$this->connect()->write($command . PHP_EOL); | |||||
return $this->connect()->read(); | |||||
} | } | ||||
/** | /** | ||||
* @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; | |||||
} | } | ||||
} | } | ||||
* @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); | ||||
} | } | ||||
* @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 = ''; | ||||
* @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 []; | ||||
$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(); | ||||
$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; | |||||
return $this->parser->parseACLs($rawAcls); | |||||
} | } | ||||
public function getServer(): IServer { | public function getServer(): IServer { |
.idea | .idea | ||||
vendor | vendor | ||||
composer.lock | composer.lock | ||||
build | |||||
example.php | |||||
*.cache |
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 |
# Streams # | # Streams # | ||||
[![Build Status](https://travis-ci.org/icewind1991/Streams.svg?branch=master)](https://travis-ci.org/icewind1991/Streams) | |||||
[![Coverage Status](https://img.shields.io/coveralls/icewind1991/Streams.svg)](https://coveralls.io/r/icewind1991/Streams?branch=master) | |||||
[![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) | |||||
[![CI](https://github.com/icewind1991/Streams/actions/workflows/ci.yaml/badge.svg)](https://github.com/icewind1991/Streams/actions/workflows/ci.yaml) | |||||
[![codecov](https://codecov.io/gh/icewind1991/Streams/branch/master/graph/badge.svg?token=bfPcAdGAaq)](https://codecov.io/gh/icewind1991/Streams) | |||||
Generic stream wrappers for php. | Generic stream wrappers for 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 | ||||
{ | { | ||||
"name" : "icewind/streams", | |||||
"description" : "A set of generic stream wrappers", | |||||
"license" : "MIT", | |||||
"authors" : [ | |||||
"name": "icewind/streams", | |||||
"description": "A set of generic stream wrappers", | |||||
"license": "MIT", | |||||
"authors": [ | |||||
{ | { | ||||
"name" : "Robin Appelman", | |||||
"name": "Robin Appelman", | |||||
"email": "icewind@owncloud.com" | "email": "icewind@owncloud.com" | ||||
} | } | ||||
], | ], | ||||
"require" : { | |||||
"php": ">=5.3" | |||||
"require": { | |||||
"php": ">=7.1" | |||||
}, | }, | ||||
"require-dev" : { | |||||
"satooshi/php-coveralls": "v1.0.0", | |||||
"phpunit/phpunit": "^4.8" | |||||
"require-dev": { | |||||
"phpunit/phpunit": "^9", | |||||
"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/" | |||||
} | |||||
} | } | ||||
} | } |
*/ | */ | ||||
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; | ||||
* 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 $write (optional) | |||||
* @param callable $close (optional) | |||||
* @param callable $readDir (optional) | |||||
* @return resource | |||||
* @param callable|null $read (optional) | |||||
* @param callable|null $write (optional) | |||||
* @param callable|null $close (optional) | |||||
* @param callable|null $readDir (optional) | |||||
* @param callable|null $preClose (optional) | |||||
* @return resource|bool | |||||
* | * | ||||
* @throws \BadMethodCallException | |||||
*/ | */ | ||||
public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null, $preClose = null) { | public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null, $preClose = null) { | ||||
$context = stream_context_create(array( | |||||
'callback' => array( | |||||
'source' => $source, | |||||
'read' => $read, | |||||
'write' => $write, | |||||
'close' => $close, | |||||
'readDir' => $readDir, | |||||
'preClose' => $preClose, | |||||
) | |||||
)); | |||||
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CallbackWrapper'); | |||||
$context = [ | |||||
'source' => $source, | |||||
'read' => $read, | |||||
'write' => $write, | |||||
'close' => $close, | |||||
'readDir' => $readDir, | |||||
'preClose' => $preClose, | |||||
]; | |||||
return self::wrapSource($source, $context); | |||||
} | } | ||||
protected function open() { | 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']; | ||||
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; | ||||
} | } |
* | * | ||||
* @param resource $source | * @param resource $source | ||||
* @param callable $callback | * @param callable $callback | ||||
* @return resource | |||||
* @return resource|bool | |||||
* | * | ||||
* @throws \BadMethodCallException | * @throws \BadMethodCallException | ||||
*/ | */ | ||||
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( | |||||
'count' => array( | |||||
'source' => $source, | |||||
'callback' => $callback | |||||
) | |||||
)); | |||||
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CountWrapper'); | |||||
return self::wrapSource($source, [ | |||||
'source' => $source, | |||||
'callback' => $callback | |||||
]); | |||||
} | } | ||||
protected function open() { | protected function open() { | ||||
$context = $this->loadContext('count'); | |||||
$context = $this->loadContext(); | |||||
$this->callback = $context['callback']; | $this->callback = $context['callback']; | ||||
return true; | return true; | ||||
} | } |
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(); | ||||
* @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; | ||||
} | } | ||||
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); | ||||
} | } | ||||
/** | /** | ||||
* @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( | |||||
'filter' => array( | |||||
'source' => $source, | |||||
'filter' => $filter | |||||
) | |||||
); | |||||
return self::wrapWithOptions($options, '\Icewind\Streams\DirectoryFilter'); | |||||
return self::wrapSource($source, [ | |||||
'source' => $source, | |||||
'filter' => $filter | |||||
]); | |||||
} | } | ||||
} | } |
namespace Icewind\Streams; | namespace Icewind\Streams; | ||||
class DirectoryWrapper implements Directory { | |||||
/** | |||||
* @var resource | |||||
*/ | |||||
public $context; | |||||
/** | |||||
* @var resource | |||||
*/ | |||||
protected $source; | |||||
/** | |||||
* Load the source from the stream context and return the context options | |||||
* | |||||
* @param string $name | |||||
* @return array | |||||
* @throws \Exception | |||||
*/ | |||||
protected function loadContext($name) { | |||||
$context = stream_context_get_options($this->context); | |||||
if (isset($context[$name])) { | |||||
$context = $context[$name]; | |||||
} else { | |||||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); | |||||
} | |||||
if (isset($context['source']) and is_resource($context['source'])) { | |||||
$this->source = $context['source']; | |||||
} else { | |||||
throw new \BadMethodCallException('Invalid context, source not set'); | |||||
} | |||||
return $context; | |||||
class DirectoryWrapper extends Wrapper implements Directory { | |||||
public function stream_open($path, $mode, $options, &$opened_path) { | |||||
return false; | |||||
} | } | ||||
/** | /** | ||||
* @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; | ||||
} | } | ||||
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; | |||||
} | |||||
} | } |
* @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); | ||||
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); | ||||
public function stream_truncate($size); | public function stream_truncate($size); | ||||
/** | /** | ||||
* @return array | |||||
* @return array|false | |||||
*/ | */ | ||||
public function stream_stat(); | public function stream_stat(); | ||||
<?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(); | |||||
} | |||||
} |
* | * | ||||
* 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 | ||||
*/ | */ | ||||
* | * | ||||
* @param string $name | * @param string $name | ||||
* @return array | * @return array | ||||
* @throws \Exception | |||||
* @throws \BadMethodCallException | |||||
*/ | */ | ||||
protected function loadContext($name) { | |||||
$context = stream_context_get_options($this->context); | |||||
if (isset($context[$name])) { | |||||
$context = $context[$name]; | |||||
} else { | |||||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); | |||||
} | |||||
protected function loadContext($name = null) { | |||||
$context = parent::loadContext($name); | |||||
if (isset($context['iterator'])) { | 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'); | ||||
* @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()) { | ||||
* 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( | |||||
'dir' => array( | |||||
'iterator' => $source) | |||||
)); | |||||
} else if (is_array($source)) { | |||||
$context = stream_context_create(array( | |||||
'dir' => array( | |||||
'array' => $source) | |||||
)); | |||||
$options = [ | |||||
'iterator' => $source | |||||
]; | |||||
} elseif (is_array($source)) { | |||||
$options = [ | |||||
'array' => $source | |||||
]; | |||||
} else { | } 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'); | |||||
$wrapped = opendir('iterator://', $context); | |||||
stream_wrapper_unregister('iterator'); | |||||
return $wrapped; | |||||
return self::wrapSource(self::NO_SOURCE_DIR, $options); | |||||
} | } | ||||
} | } |
* 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( | |||||
'null' => array( | |||||
'source' => $source) | |||||
)); | |||||
return Wrapper::wrapSource($source, $context, 'null', '\Icewind\Streams\NullWrapper'); | |||||
return self::wrapSource($source); | |||||
} | } | ||||
public function stream_open($path, $mode, $options, &$opened_path) { | 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; | ||||
} | } | ||||
} | } |
* @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; | ||||
} | } | ||||
*/ | */ | ||||
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); |
* @return Path|string | * @return Path|string | ||||
*/ | */ | ||||
public static function getPath($source) { | public static function getPath($source) { | ||||
return new Path(__CLASS__, [ | |||||
'null' => [ | |||||
'source' => $source | |||||
] | |||||
return new Path(NullWrapper::class, [ | |||||
NullWrapper::getProtocol() => ['source' => $source] | |||||
]); | ]); | ||||
} | } | ||||
} | } |
<?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; | |||||
} | |||||
} |
* 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( | |||||
'retry' => array( | |||||
'source' => $source | |||||
) | |||||
)); | |||||
return Wrapper::wrapSource($source, $context, 'retry', '\Icewind\Streams\RetryWrapper'); | |||||
} | |||||
protected function open() { | |||||
$this->loadContext('retry'); | |||||
return true; | |||||
return self::wrapSource($source); | |||||
} | } | ||||
public function dir_opendir($path, $options) { | public function dir_opendir($path, $options) { | ||||
} | } | ||||
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) { |
*/ | */ | ||||
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( | |||||
'callback' => array( | |||||
'source' => $source | |||||
) | |||||
)); | |||||
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\SeekableWrapper'); | |||||
return self::wrapSource($source); | |||||
} | } | ||||
public function dir_opendir($path, $options) { | public function dir_opendir($path, $options) { | ||||
} | } | ||||
public function stream_open($path, $mode, $options, &$opened_path) { | public function stream_open($path, $mode, $options, &$opened_path) { | ||||
$this->loadContext('callback'); | |||||
$this->cache = fopen('php://temp', 'w+'); | |||||
$this->loadContext(); | |||||
$cache = fopen('php://temp', 'w+'); | |||||
if ($cache === false) { | |||||
return false; | |||||
} | |||||
$this->cache = $cache; | |||||
return true; | return true; | ||||
} | } | ||||
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 { |
* @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); | ||||
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); | ||||
/** | /** | ||||
* @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); | ||||
} | } |
* @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, | |||||
$unlink = null, $stat = null) { | |||||
$options = array( | |||||
'source' => $source, | |||||
'fopen' => $fopen, | |||||
public static function wrap( | |||||
$source, | |||||
$fopen = null, | |||||
$opendir = null, | |||||
$mkdir = null, | |||||
$rename = null, | |||||
$rmdir = null, | |||||
$unlink = null, | |||||
$stat = null | |||||
) { | |||||
return new Path(static::class, [ | |||||
'source' => $source, | |||||
'fopen' => $fopen, | |||||
'opendir' => $opendir, | 'opendir' => $opendir, | ||||
'mkdir' => $mkdir, | |||||
'rename' => $rename, | |||||
'rmdir' => $rmdir, | |||||
'unlink' => $unlink, | |||||
'stat' => $stat | |||||
); | |||||
return new Path('\Icewind\Streams\UrlCallBack', $options); | |||||
'mkdir' => $mkdir, | |||||
'rename' => $rename, | |||||
'rmdir' => $rmdir, | |||||
'unlink' => $unlink, | |||||
'stat' => $stat | |||||
]); | |||||
} | } | ||||
protected function loadContext($url) { | |||||
protected function loadUrlContext($url) { | |||||
list($protocol) = explode('://', $url); | list($protocol) = explode('://', $url); | ||||
$options = stream_context_get_options($this->context); | $options = stream_context_get_options($this->context); | ||||
return $options[$protocol]; | return $options[$protocol]; | ||||
} | } | ||||
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']); | ||||
} | } |
* | * | ||||
* 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 | ||||
*/ | */ | ||||
*/ | */ | ||||
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 string $name | |||||
* @return array | |||||
* @throws \Exception | |||||
* @param resource $source | |||||
*/ | */ | ||||
protected function loadContext($name) { | |||||
$context = stream_context_get_options($this->context); | |||||
if (isset($context[$name])) { | |||||
$context = $context[$name]; | |||||
} else { | |||||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); | |||||
} | |||||
protected function setSourceStream($source) { | |||||
$this->source = $source; | |||||
} | |||||
protected function loadContext($name = null) { | |||||
$context = parent::loadContext($name); | |||||
if (isset($context['source']) and is_resource($context['source'])) { | if (isset($context['source']) and is_resource($context['source'])) { | ||||
$this->setSourceStream($context['source']); | $this->setSourceStream($context['source']); | ||||
} else { | } else { | ||||
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; | ||||
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); | |||||
break; | |||||
return stream_set_blocking($this->source, (bool)$arg1); | |||||
case STREAM_OPTION_READ_TIMEOUT: | case STREAM_OPTION_READ_TIMEOUT: | ||||
stream_set_timeout($this->source, $arg1, $arg2); | |||||
break; | |||||
return stream_set_timeout($this->source, $arg1, $arg2); | |||||
case STREAM_OPTION_WRITE_BUFFER: | 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) { |
<?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; | |||||
} | |||||
} |
<?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); | |||||
} | |||||
} |