Merge pull request #26046 from nextcloud/smb-3.4.0
update icewind/smb to 3.4.0
This commit is contained in:
commit
9e9b014e31
|
@ -6,3 +6,6 @@ icewind/smb/Makefile
|
|||
icewind/smb/.travis.yml
|
||||
icewind/smb/.scrutinizer.yml
|
||||
icewind/streams/tests
|
||||
.github
|
||||
.php_cs*
|
||||
psalm.xml
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"classmap-authoritative": true
|
||||
},
|
||||
"require": {
|
||||
"icewind/streams": "0.7.1",
|
||||
"icewind/smb": "3.2.7"
|
||||
"icewind/streams": "0.7.3",
|
||||
"icewind/smb": "3.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,29 +4,31 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6181c23a5c03b00fbdc659d87c1ad67d",
|
||||
"content-hash": "9905ed45527f669a4165a8b83b6e4141",
|
||||
"packages": [
|
||||
{
|
||||
"name": "icewind/smb",
|
||||
"version": "v3.2.7",
|
||||
"version": "v3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/icewind1991/SMB.git",
|
||||
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6"
|
||||
"reference": "b5c6921f2e91229c9f71556a4713b4fac91fd394"
|
||||
},
|
||||
"dist": {
|
||||
"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": ""
|
||||
},
|
||||
"require": {
|
||||
"icewind/streams": ">=0.2.0",
|
||||
"php": ">=7.1"
|
||||
"icewind/streams": ">=0.7.3",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"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",
|
||||
"autoload": {
|
||||
|
@ -45,33 +47,37 @@
|
|||
}
|
||||
],
|
||||
"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",
|
||||
"version": "v0.7.1",
|
||||
"version": "v0.7.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/icewind1991/Streams.git",
|
||||
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121"
|
||||
"reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c"
|
||||
},
|
||||
"dist": {
|
||||
"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": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8",
|
||||
"satooshi/php-coveralls": "v1.0.0"
|
||||
"friendsofphp/php-cs-fixer": "^2",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^9"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Icewind\\Streams\\Tests\\": "tests/",
|
||||
"Icewind\\Streams\\": "src/"
|
||||
}
|
||||
},
|
||||
|
@ -86,7 +92,11 @@
|
|||
}
|
||||
],
|
||||
"description": "A set of generic stream wrappers",
|
||||
"time": "2019-02-15T12:57:29+00:00"
|
||||
"support": {
|
||||
"issues": "https://github.com/icewind1991/Streams/issues",
|
||||
"source": "https://github.com/icewind1991/Streams/tree/v0.7.3"
|
||||
},
|
||||
"time": "2021-03-02T19:33:35+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
@ -97,5 +107,5 @@
|
|||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -37,11 +37,13 @@ namespace Composer\Autoload;
|
|||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @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
|
||||
{
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
|
@ -57,10 +59,17 @@ class ClassLoader
|
|||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
}
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
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();
|
||||
|
@ -300,6 +309,17 @@ class ClassLoader
|
|||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,6 +328,10 @@ class ClassLoader
|
|||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -367,6 +391,16 @@ class ClassLoader
|
|||
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)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class InstalledVersions
|
||||
{
|
||||
private static $installed = array (
|
||||
'root' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
|
||||
'name' => 'files_external/3rdparty',
|
||||
),
|
||||
'versions' =>
|
||||
array (
|
||||
'files_external/3rdparty' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
|
||||
),
|
||||
'icewind/smb' =>
|
||||
array (
|
||||
'pretty_version' => 'v3.4.0',
|
||||
'version' => '3.4.0.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => 'b5c6921f2e91229c9f71556a4713b4fac91fd394',
|
||||
),
|
||||
'icewind/streams' =>
|
||||
array (
|
||||
'pretty_version' => 'v0.7.3',
|
||||
'version' => '0.7.3.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '22ef9fc5b50d645dbc202206a656cc4dde28f95c',
|
||||
),
|
||||
),
|
||||
);
|
||||
private static $canGetVendors;
|
||||
private static $installedByVendor = array();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function isInstalled($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints($constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getRawData()
|
||||
{
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$installed[] = self::$installed;
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
|
|||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'Icewind\\SMB\\ACL' => $vendorDir . '/icewind/smb/src/ACL.php',
|
||||
'Icewind\\SMB\\AbstractServer' => $vendorDir . '/icewind/smb/src/AbstractServer.php',
|
||||
'Icewind\\SMB\\AbstractShare' => $vendorDir . '/icewind/smb/src/AbstractShare.php',
|
||||
|
@ -57,6 +58,7 @@ return array(
|
|||
'Icewind\\SMB\\Native\\NativeWriteStream' => $vendorDir . '/icewind/smb/src/Native/NativeWriteStream.php',
|
||||
'Icewind\\SMB\\Options' => $vendorDir . '/icewind/smb/src/Options.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\\TimeZoneProvider' => $vendorDir . '/icewind/smb/src/TimeZoneProvider.php',
|
||||
'Icewind\\SMB\\Wrapped\\Connection' => $vendorDir . '/icewind/smb/src/Wrapped/Connection.php',
|
||||
|
@ -73,13 +75,17 @@ return array(
|
|||
'Icewind\\Streams\\DirectoryFilter' => $vendorDir . '/icewind/streams/src/DirectoryFilter.php',
|
||||
'Icewind\\Streams\\DirectoryWrapper' => $vendorDir . '/icewind/streams/src/DirectoryWrapper.php',
|
||||
'Icewind\\Streams\\File' => $vendorDir . '/icewind/streams/src/File.php',
|
||||
'Icewind\\Streams\\HashWrapper' => $vendorDir . '/icewind/streams/src/HashWrapper.php',
|
||||
'Icewind\\Streams\\IteratorDirectory' => $vendorDir . '/icewind/streams/src/IteratorDirectory.php',
|
||||
'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php',
|
||||
'Icewind\\Streams\\Path' => $vendorDir . '/icewind/streams/src/Path.php',
|
||||
'Icewind\\Streams\\PathWrapper' => $vendorDir . '/icewind/streams/src/PathWrapper.php',
|
||||
'Icewind\\Streams\\ReadHashWrapper' => $vendorDir . '/icewind/streams/src/ReadHashWrapper.php',
|
||||
'Icewind\\Streams\\RetryWrapper' => $vendorDir . '/icewind/streams/src/RetryWrapper.php',
|
||||
'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php',
|
||||
'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php',
|
||||
'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallBack.php',
|
||||
'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallback.php',
|
||||
'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.php',
|
||||
'Icewind\\Streams\\WrapperHandler' => $vendorDir . '/icewind/streams/src/WrapperHandler.php',
|
||||
'Icewind\\Streams\\WriteHashWrapper' => $vendorDir . '/icewind/streams/src/WriteHashWrapper.php',
|
||||
);
|
||||
|
|
|
@ -6,7 +6,6 @@ $vendorDir = dirname(dirname(__FILE__));
|
|||
$baseDir = $vendorDir;
|
||||
|
||||
return array(
|
||||
'Icewind\\Streams\\Tests\\' => array($vendorDir . '/icewind/streams/tests'),
|
||||
'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'),
|
||||
'Icewind\\SMB\\' => array($vendorDir . '/icewind/smb/src'),
|
||||
);
|
||||
|
|
|
@ -22,13 +22,15 @@ class ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3
|
|||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
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'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::getInitializer($loader));
|
||||
} else {
|
||||
|
|
|
@ -9,17 +9,12 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
|
|||
public static $prefixLengthsPsr4 = array (
|
||||
'I' =>
|
||||
array (
|
||||
'Icewind\\Streams\\Tests\\' => 22,
|
||||
'Icewind\\Streams\\' => 16,
|
||||
'Icewind\\SMB\\' => 12,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Icewind\\Streams\\Tests\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/icewind/streams/tests',
|
||||
),
|
||||
'Icewind\\Streams\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/icewind/streams/src',
|
||||
|
@ -31,6 +26,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
|
|||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'Icewind\\SMB\\ACL' => __DIR__ . '/..' . '/icewind/smb/src/ACL.php',
|
||||
'Icewind\\SMB\\AbstractServer' => __DIR__ . '/..' . '/icewind/smb/src/AbstractServer.php',
|
||||
'Icewind\\SMB\\AbstractShare' => __DIR__ . '/..' . '/icewind/smb/src/AbstractShare.php',
|
||||
|
@ -82,6 +78,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
|
|||
'Icewind\\SMB\\Native\\NativeWriteStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeWriteStream.php',
|
||||
'Icewind\\SMB\\Options' => __DIR__ . '/..' . '/icewind/smb/src/Options.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\\TimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/TimeZoneProvider.php',
|
||||
'Icewind\\SMB\\Wrapped\\Connection' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Connection.php',
|
||||
|
@ -98,15 +95,19 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
|
|||
'Icewind\\Streams\\DirectoryFilter' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryFilter.php',
|
||||
'Icewind\\Streams\\DirectoryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryWrapper.php',
|
||||
'Icewind\\Streams\\File' => __DIR__ . '/..' . '/icewind/streams/src/File.php',
|
||||
'Icewind\\Streams\\HashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/HashWrapper.php',
|
||||
'Icewind\\Streams\\IteratorDirectory' => __DIR__ . '/..' . '/icewind/streams/src/IteratorDirectory.php',
|
||||
'Icewind\\Streams\\NullWrapper' => __DIR__ . '/..' . '/icewind/streams/src/NullWrapper.php',
|
||||
'Icewind\\Streams\\Path' => __DIR__ . '/..' . '/icewind/streams/src/Path.php',
|
||||
'Icewind\\Streams\\PathWrapper' => __DIR__ . '/..' . '/icewind/streams/src/PathWrapper.php',
|
||||
'Icewind\\Streams\\ReadHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/ReadHashWrapper.php',
|
||||
'Icewind\\Streams\\RetryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/RetryWrapper.php',
|
||||
'Icewind\\Streams\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/src/SeekableWrapper.php',
|
||||
'Icewind\\Streams\\Url' => __DIR__ . '/..' . '/icewind/streams/src/Url.php',
|
||||
'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallBack.php',
|
||||
'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallback.php',
|
||||
'Icewind\\Streams\\Wrapper' => __DIR__ . '/..' . '/icewind/streams/src/Wrapper.php',
|
||||
'Icewind\\Streams\\WrapperHandler' => __DIR__ . '/..' . '/icewind/streams/src/WrapperHandler.php',
|
||||
'Icewind\\Streams\\WriteHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/WriteHashWrapper.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
|
|
|
@ -1,28 +1,31 @@
|
|||
[
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "icewind/smb",
|
||||
"version": "v3.2.7",
|
||||
"version_normalized": "3.2.7.0",
|
||||
"version": "v3.4.0",
|
||||
"version_normalized": "3.4.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/icewind1991/SMB.git",
|
||||
"reference": "743a7bf35317f1b76cf8e8b804e54a6c5faacad6"
|
||||
"reference": "b5c6921f2e91229c9f71556a4713b4fac91fd394"
|
||||
},
|
||||
"dist": {
|
||||
"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": ""
|
||||
},
|
||||
"require": {
|
||||
"icewind/streams": ">=0.2.0",
|
||||
"php": ">=7.1"
|
||||
"icewind/streams": ">=0.7.3",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"time": "2020-09-03T13:00:22+00:00",
|
||||
"time": "2021-03-10T14:00:37+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
|
@ -40,36 +43,41 @@
|
|||
"email": "icewind@owncloud.com"
|
||||
}
|
||||
],
|
||||
"description": "php wrapper for smbclient and libsmbclient-php"
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"name": "icewind/streams",
|
||||
"version": "v0.7.1",
|
||||
"version_normalized": "0.7.1.0",
|
||||
"version": "v0.7.3",
|
||||
"version_normalized": "0.7.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/icewind1991/Streams.git",
|
||||
"reference": "4db3ed6c366e90b958d00e1d4c6360a9b39b2121"
|
||||
"reference": "22ef9fc5b50d645dbc202206a656cc4dde28f95c"
|
||||
},
|
||||
"dist": {
|
||||
"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": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8",
|
||||
"satooshi/php-coveralls": "v1.0.0"
|
||||
"friendsofphp/php-cs-fixer": "^2",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^9"
|
||||
},
|
||||
"time": "2019-02-15T12:57:29+00:00",
|
||||
"time": "2021-03-02T19:33:35+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Icewind\\Streams\\Tests\\": "tests/",
|
||||
"Icewind\\Streams\\": "src/"
|
||||
}
|
||||
},
|
||||
|
@ -83,6 +91,14 @@
|
|||
"email": "icewind@owncloud.com"
|
||||
}
|
||||
],
|
||||
"description": "A set of generic stream wrappers"
|
||||
"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": []
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php return array (
|
||||
'root' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
|
||||
'name' => 'files_external/3rdparty',
|
||||
),
|
||||
'versions' =>
|
||||
array (
|
||||
'files_external/3rdparty' =>
|
||||
array (
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '62929cc646134fbd409cfb4eacb7039d15763b96',
|
||||
),
|
||||
'icewind/smb' =>
|
||||
array (
|
||||
'pretty_version' => 'v3.4.0',
|
||||
'version' => '3.4.0.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => 'b5c6921f2e91229c9f71556a4713b4fac91fd394',
|
||||
),
|
||||
'icewind/streams' =>
|
||||
array (
|
||||
'pretty_version' => 'v0.7.3',
|
||||
'version' => '0.7.3.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '22ef9fc5b50d645dbc202206a656cc4dde28f95c',
|
||||
),
|
||||
),
|
||||
);
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 70200)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
|
@ -4,3 +4,4 @@ composer.lock
|
|||
.php_cs.cache
|
||||
listen.php
|
||||
test.php
|
||||
*.cache
|
|
@ -1,9 +1,8 @@
|
|||
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)
|
||||
|
||||
|
@ -103,7 +102,7 @@ fclose($fh);
|
|||
```
|
||||
|
||||
**Note**: write() will truncate your file to 0bytes. You may open a writeable stream with append() which will point
|
||||
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
|
||||
$fh = $share->append('test.txt');
|
||||
fwrite($fh, 'bar');
|
||||
|
@ -127,11 +126,22 @@ $options->setTimeout(5);
|
|||
$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
|
||||
|
||||
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.
|
||||
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`.
|
||||
|
||||
|
|
|
@ -1,29 +1,37 @@
|
|||
{
|
||||
"name" : "icewind/smb",
|
||||
"description" : "php wrapper for smbclient and libsmbclient-php",
|
||||
"license" : "MIT",
|
||||
"authors" : [
|
||||
"name": "icewind/smb",
|
||||
"description": "php wrapper for smbclient and libsmbclient-php",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name" : "Robin Appelman",
|
||||
"name": "Robin Appelman",
|
||||
"email": "icewind@owncloud.com"
|
||||
}
|
||||
],
|
||||
"require" : {
|
||||
"php": ">=7.1",
|
||||
"icewind/streams": ">=0.2.0"
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"icewind/streams": ">=0.7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.0",
|
||||
"friendsofphp/php-cs-fixer": "^2.13"
|
||||
"phpunit/phpunit": "^8.5|^9.3.8",
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"phpstan/phpstan": "^0.12.57",
|
||||
"psalm/phar": "^4.3"
|
||||
},
|
||||
"autoload" : {
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Icewind\\SMB\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev" : {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,11 @@ class ACL {
|
|||
const FLAG_OBJECT_INHERIT = 0x1;
|
||||
const FLAG_CONTAINER_INHERIT = 0x2;
|
||||
|
||||
/** @var int */
|
||||
private $type;
|
||||
/** @var int */
|
||||
private $flags;
|
||||
/** @var int */
|
||||
private $mask;
|
||||
|
||||
public function __construct(int $type, int $flags, int $mask) {
|
||||
|
|
|
@ -24,24 +24,16 @@ namespace Icewind\SMB;
|
|||
abstract class AbstractServer implements IServer {
|
||||
const LOCALE = 'en_US.UTF-8';
|
||||
|
||||
/**
|
||||
* @var string $host
|
||||
*/
|
||||
/** @var string */
|
||||
protected $host;
|
||||
|
||||
/**
|
||||
* @var IAuth $user
|
||||
*/
|
||||
/** @var IAuth */
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* @var ISystem
|
||||
*/
|
||||
/** @var ISystem */
|
||||
protected $system;
|
||||
|
||||
/**
|
||||
* @var TimeZoneProvider
|
||||
*/
|
||||
/** @var ITimeZoneProvider */
|
||||
protected $timezoneProvider;
|
||||
|
||||
/** @var IOptions */
|
||||
|
@ -51,10 +43,10 @@ abstract class AbstractServer implements IServer {
|
|||
* @param string $host
|
||||
* @param IAuth $auth
|
||||
* @param ISystem $system
|
||||
* @param TimeZoneProvider $timeZoneProvider
|
||||
* @param ITimeZoneProvider $timeZoneProvider
|
||||
* @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->auth = $auth;
|
||||
$this->system = $system;
|
||||
|
@ -62,23 +54,23 @@ abstract class AbstractServer implements IServer {
|
|||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function getAuth() {
|
||||
public function getAuth(): IAuth {
|
||||
return $this->auth;
|
||||
}
|
||||
|
||||
public function getHost() {
|
||||
public function getHost(): string {
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function getTimeZone() {
|
||||
public function getTimeZone(): string {
|
||||
return $this->timezoneProvider->get($this->host);
|
||||
}
|
||||
|
||||
public function getSystem() {
|
||||
public function getSystem(): ISystem {
|
||||
return $this->system;
|
||||
}
|
||||
|
||||
public function getOptions() {
|
||||
public function getOptions(): IOptions {
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,18 @@ namespace Icewind\SMB;
|
|||
use Icewind\SMB\Exception\InvalidPathException;
|
||||
|
||||
abstract class AbstractShare implements IShare {
|
||||
/** @var string[] */
|
||||
private $forbiddenCharacters;
|
||||
|
||||
public function __construct() {
|
||||
$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) {
|
||||
if (strpos($path, $char) !== false) {
|
||||
throw new InvalidPathException('Invalid path, "' . $char . '" is not allowed');
|
||||
|
@ -24,7 +29,10 @@ abstract class AbstractShare implements IShare {
|
|||
}
|
||||
}
|
||||
|
||||
public function setForbiddenChars(array $charList) {
|
||||
/**
|
||||
* @param string[] $charList
|
||||
*/
|
||||
public function setForbiddenChars(array $charList): void {
|
||||
$this->forbiddenCharacters = $charList;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,23 +22,23 @@
|
|||
namespace Icewind\SMB;
|
||||
|
||||
class AnonymousAuth implements IAuth {
|
||||
public function getUsername() {
|
||||
public function getUsername(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getWorkgroup() {
|
||||
public function getWorkgroup(): ?string {
|
||||
return 'dummy';
|
||||
}
|
||||
|
||||
public function getPassword() {
|
||||
public function getPassword(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getExtraCommandLineArguments() {
|
||||
public function getExtraCommandLineArguments(): string {
|
||||
return '-N';
|
||||
}
|
||||
|
||||
public function setExtraSmbClientOptions($smbClientState) {
|
||||
public function setExtraSmbClientOptions($smbClientState): void {
|
||||
smbclient_option_set($smbClientState, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,41 +24,34 @@ namespace Icewind\SMB;
|
|||
class BasicAuth implements IAuth {
|
||||
/** @var string */
|
||||
private $username;
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
private $workgroup;
|
||||
/** @var string */
|
||||
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->workgroup = $workgroup;
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
public function getUsername() {
|
||||
public function getUsername(): ?string {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getWorkgroup() {
|
||||
public function getWorkgroup(): ?string {
|
||||
return $this->workgroup;
|
||||
}
|
||||
|
||||
public function getPassword() {
|
||||
public function getPassword(): ?string {
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getExtraCommandLineArguments() {
|
||||
public function getExtraCommandLineArguments(): string {
|
||||
return ($this->workgroup) ? '-W ' . escapeshellarg($this->workgroup) : '';
|
||||
}
|
||||
|
||||
public function setExtraSmbClientOptions($smbClientState) {
|
||||
public function setExtraSmbClientOptions($smbClientState): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,32 +9,21 @@
|
|||
namespace Icewind\SMB;
|
||||
|
||||
class Change {
|
||||
/** @var int */
|
||||
private $code;
|
||||
|
||||
/** @var string */
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Change constructor.
|
||||
*
|
||||
* @param $code
|
||||
* @param $path
|
||||
*/
|
||||
public function __construct($code, $path) {
|
||||
public function __construct(int $code, string $path) {
|
||||
$this->code = $code;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getCode() {
|
||||
public function getCode(): int {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() {
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,37 @@
|
|||
|
||||
namespace Icewind\SMB\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @psalm-consistent-constructor
|
||||
*/
|
||||
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) {
|
||||
$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
|
||||
*/
|
||||
public static function fromMap(array $exceptionMap, $error, $path) {
|
||||
public static function fromMap(array $exceptionMap, $error, ?string $path): Exception {
|
||||
if (isset($exceptionMap[$error])) {
|
||||
$exceptionClass = $exceptionMap[$error];
|
||||
if (is_numeric($error)) {
|
||||
|
|
|
@ -13,15 +13,11 @@ class InvalidRequestException extends Exception {
|
|||
*/
|
||||
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);
|
||||
$parts = explode('\\', $class);
|
||||
$baseName = array_pop($parts);
|
||||
parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code);
|
||||
parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code, $previous);
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Icewind\SMB\Exception;
|
|||
use Throwable;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,32 +22,23 @@
|
|||
namespace Icewind\SMB;
|
||||
|
||||
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
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraCommandLineArguments();
|
||||
public function getExtraCommandLineArguments(): string;
|
||||
|
||||
/**
|
||||
* Set any extra options for libsmbclient that are required
|
||||
*
|
||||
* @param resource $smbClientState
|
||||
*/
|
||||
public function setExtraSmbClientOptions($smbClientState);
|
||||
public function setExtraSmbClientOptions($smbClientState): void;
|
||||
}
|
||||
|
|
|
@ -21,50 +21,23 @@ interface IFileInfo {
|
|||
const MODE_ARCHIVE = 0x20;
|
||||
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[]
|
||||
|
|
|
@ -25,21 +25,21 @@ interface INotifyHandler {
|
|||
*
|
||||
* @return Change[]
|
||||
*/
|
||||
public function getChanges();
|
||||
public function getChanges(): array;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param callable(Change):?bool $callback
|
||||
*/
|
||||
public function listen($callback);
|
||||
public function listen(callable $callback): void;
|
||||
|
||||
/**
|
||||
* Stop listening for changes
|
||||
*
|
||||
* Note that any pending changes will be discarded
|
||||
*/
|
||||
public function stop();
|
||||
public function stop(): void;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,20 @@
|
|||
namespace Icewind\SMB;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -22,15 +22,9 @@
|
|||
namespace Icewind\SMB;
|
||||
|
||||
interface IServer {
|
||||
/**
|
||||
* @return IAuth
|
||||
*/
|
||||
public function getAuth();
|
||||
public function getAuth(): IAuth;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHost();
|
||||
public function getHost(): string;
|
||||
|
||||
/**
|
||||
* @return \Icewind\SMB\IShare[]
|
||||
|
@ -38,32 +32,15 @@ interface IServer {
|
|||
* @throws \Icewind\SMB\Exception\AuthenticationException
|
||||
* @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;
|
||||
}
|
||||
|
|
|
@ -7,13 +7,18 @@
|
|||
|
||||
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 {
|
||||
/**
|
||||
* Get the name of the share
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Download a remote file
|
||||
|
@ -22,10 +27,10 @@ interface IShare {
|
|||
* @param string $target local file
|
||||
* @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
|
||||
|
@ -34,10 +39,10 @@ interface IShare {
|
|||
* @param string $target remove file
|
||||
* @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
|
||||
|
@ -45,10 +50,10 @@ interface IShare {
|
|||
* @param string $source
|
||||
* @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
|
||||
|
@ -57,10 +62,10 @@ interface IShare {
|
|||
* @param string $target
|
||||
* @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
|
||||
|
@ -68,11 +73,11 @@ interface IShare {
|
|||
* @param string $target
|
||||
* @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
|
||||
|
@ -81,10 +86,10 @@ interface IShare {
|
|||
* @param string $to
|
||||
* @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
|
||||
|
@ -92,29 +97,29 @@ interface IShare {
|
|||
* @param string $path
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
* @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
|
||||
|
@ -122,10 +127,10 @@ interface IShare {
|
|||
* @param string $path
|
||||
* @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
|
||||
|
@ -133,23 +138,23 @@ interface IShare {
|
|||
* @param string $path
|
||||
* @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 int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
|
||||
* @return mixed
|
||||
*/
|
||||
public function setMode($path, $mode);
|
||||
public function setMode(string $path, int $mode);
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return INotifyHandler
|
||||
*/
|
||||
public function notify($path);
|
||||
public function notify(string $path);
|
||||
|
||||
/**
|
||||
* Get the IServer instance for this share
|
||||
|
|
|
@ -32,47 +32,47 @@ interface ISystem {
|
|||
* @param int $num the file descriptor id
|
||||
* @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
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function libSmbclientAvailable();
|
||||
public function libSmbclientAvailable(): bool;
|
||||
}
|
||||
|
|
|
@ -28,5 +28,5 @@ interface ITimeZoneProvider {
|
|||
* @param string $host
|
||||
* @return string
|
||||
*/
|
||||
public function get($host);
|
||||
public function get(string $host): string;
|
||||
}
|
||||
|
|
|
@ -25,23 +25,23 @@ namespace Icewind\SMB;
|
|||
* Use existing kerberos ticket to authenticate
|
||||
*/
|
||||
class KerberosAuth implements IAuth {
|
||||
public function getUsername() {
|
||||
public function getUsername(): ?string {
|
||||
return 'dummy';
|
||||
}
|
||||
|
||||
public function getWorkgroup() {
|
||||
public function getWorkgroup(): ?string {
|
||||
return 'dummy';
|
||||
}
|
||||
|
||||
public function getPassword() {
|
||||
public function getPassword(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getExtraCommandLineArguments() {
|
||||
public function getExtraCommandLineArguments(): string {
|
||||
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_FALLBACK_AFTER_KERBEROS, false);
|
||||
}
|
||||
|
|
|
@ -8,88 +8,71 @@
|
|||
namespace Icewind\SMB\Native;
|
||||
|
||||
use Icewind\SMB\ACL;
|
||||
use Icewind\SMB\Exception\Exception;
|
||||
use Icewind\SMB\IFileInfo;
|
||||
|
||||
class NativeFileInfo implements IFileInfo {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var NativeShare
|
||||
*/
|
||||
/** @var NativeShare */
|
||||
protected $share;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
/** @var array{"mode": int, "size": int, "write_time": int}|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->path = $path;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() {
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
public function getName(): string {
|
||||
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)) {
|
||||
$rawAttributes = explode(',', $this->share->getAttribute($this->path, 'system.dos_attr.*'));
|
||||
$this->attributeCache = [];
|
||||
$attributes = [];
|
||||
foreach ($rawAttributes as $rawAttribute) {
|
||||
list($name, $value) = explode(':', $rawAttribute);
|
||||
$name = strtolower($name);
|
||||
if ($name == 'mode') {
|
||||
$this->attributeCache[$name] = (int)hexdec(substr($value, 2));
|
||||
$attributes[$name] = (int)hexdec(substr($value, 2));
|
||||
} 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 int
|
||||
*/
|
||||
public function getSize() {
|
||||
public function getSize(): int {
|
||||
$stat = $this->stat();
|
||||
return $stat['size'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMTime() {
|
||||
public function getMTime(): int {
|
||||
$stat = $this->stat();
|
||||
return $stat['change_time'];
|
||||
return $stat['write_time'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,10 +87,7 @@ class NativeFileInfo implements IFileInfo {
|
|||
* 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'];
|
||||
|
||||
// Let us ignore the ATTR_NOT_CONTENT_INDEXED for now
|
||||
|
@ -116,10 +96,7 @@ class NativeFileInfo implements IFileInfo {
|
|||
return $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory() {
|
||||
public function isDirectory(): bool {
|
||||
$mode = $this->getMode();
|
||||
if ($mode > 0x1000) {
|
||||
return (bool)($mode & 0x4000); // 0x4000: unix directory flag
|
||||
|
@ -128,10 +105,7 @@ class NativeFileInfo implements IFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadOnly() {
|
||||
public function isReadOnly(): bool {
|
||||
$mode = $this->getMode();
|
||||
if ($mode > 0x1000) {
|
||||
return !(bool)($mode & 0x80); // 0x80: owner write permissions
|
||||
|
@ -140,10 +114,7 @@ class NativeFileInfo implements IFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isHidden() {
|
||||
public function isHidden(): bool {
|
||||
$mode = $this->getMode();
|
||||
if ($mode > 0x1000) {
|
||||
return strlen($this->name) > 0 && $this->name[0] === '.';
|
||||
|
@ -152,10 +123,7 @@ class NativeFileInfo implements IFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSystem() {
|
||||
public function isSystem(): bool {
|
||||
$mode = $this->getMode();
|
||||
if ($mode > 0x1000) {
|
||||
return false;
|
||||
|
@ -164,10 +132,7 @@ class NativeFileInfo implements IFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isArchived() {
|
||||
public function isArchived(): bool {
|
||||
$mode = $this->getMode();
|
||||
if ($mode > 0x1000) {
|
||||
return false;
|
||||
|
@ -185,10 +150,11 @@ class NativeFileInfo implements IFileInfo {
|
|||
|
||||
foreach (explode(',', $attribute) as $acl) {
|
||||
list($user, $permissions) = explode(':', $acl, 2);
|
||||
$user = trim($user, '\\');
|
||||
list($type, $flags, $mask) = explode('/', $permissions);
|
||||
$mask = hexdec($mask);
|
||||
|
||||
$acls[$user] = new ACL($type, $flags, $mask);
|
||||
$acls[$user] = new ACL((int)$type, (int)$flags, (int)$mask);
|
||||
}
|
||||
|
||||
return $acls;
|
||||
|
|
|
@ -7,64 +7,54 @@
|
|||
|
||||
namespace Icewind\SMB\Native;
|
||||
|
||||
use Icewind\SMB\StringBuffer;
|
||||
|
||||
/**
|
||||
* Stream optimized for read only usage
|
||||
*/
|
||||
class NativeReadStream extends NativeStream {
|
||||
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;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
$this->readBuffer = fopen('php://memory', 'r+');
|
||||
|
||||
return parent::stream_open($path, $mode, $options, $opened_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a stream from libsmbclient-php into a regular php stream
|
||||
*
|
||||
* @param \Icewind\SMB\NativeState $state
|
||||
* @param NativeState $state
|
||||
* @param resource $smbStream
|
||||
* @param string $mode
|
||||
* @param string $url
|
||||
* @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) {
|
||||
// php reads 8192 bytes at once
|
||||
// however due to network latency etc, it's faster to read in larger chunks
|
||||
// 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);
|
||||
$this->pos += $read;
|
||||
|
@ -75,15 +65,18 @@ class NativeReadStream extends NativeStream {
|
|||
public function stream_seek($offset, $whence = SEEK_SET) {
|
||||
$result = parent::stream_seek($offset, $whence);
|
||||
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;
|
||||
}
|
||||
|
||||
public function stream_eof() {
|
||||
return $this->bufferSize <= 0 && parent::stream_eof();
|
||||
return $this->readBuffer->remaining() <= 0 && parent::stream_eof();
|
||||
}
|
||||
|
||||
public function stream_tell() {
|
||||
|
|
|
@ -8,10 +8,13 @@
|
|||
namespace Icewind\SMB\Native;
|
||||
|
||||
use Icewind\SMB\AbstractServer;
|
||||
use Icewind\SMB\Exception\AuthenticationException;
|
||||
use Icewind\SMB\Exception\InvalidHostException;
|
||||
use Icewind\SMB\IAuth;
|
||||
use Icewind\SMB\IOptions;
|
||||
use Icewind\SMB\IShare;
|
||||
use Icewind\SMB\ISystem;
|
||||
use Icewind\SMB\TimeZoneProvider;
|
||||
use Icewind\SMB\ITimeZoneProvider;
|
||||
|
||||
class NativeServer extends AbstractServer {
|
||||
/**
|
||||
|
@ -19,38 +22,34 @@ class NativeServer extends AbstractServer {
|
|||
*/
|
||||
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);
|
||||
$this->state = new NativeState();
|
||||
}
|
||||
|
||||
protected function connect() {
|
||||
protected function connect(): void {
|
||||
$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();
|
||||
$shares = [];
|
||||
$dh = $this->state->opendir('smb://' . $this->getHost());
|
||||
while ($share = $this->state->readdir($dh)) {
|
||||
while ($share = $this->state->readdir($dh, '')) {
|
||||
if ($share['type'] === 'file share') {
|
||||
$shares[] = $this->getShare($share['name']);
|
||||
}
|
||||
}
|
||||
$this->state->closedir($dh);
|
||||
$this->state->closedir($dh, '');
|
||||
return $shares;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return \Icewind\SMB\IShare
|
||||
*/
|
||||
public function getShare($name) {
|
||||
public function getShare(string $name): IShare {
|
||||
return new NativeShare($this, $name);
|
||||
}
|
||||
|
||||
|
@ -60,7 +59,7 @@ class NativeServer extends AbstractServer {
|
|||
* @param ISystem $system
|
||||
* @return bool
|
||||
*/
|
||||
public static function available(ISystem $system) {
|
||||
public static function available(ISystem $system): bool {
|
||||
return $system->libSmbclientAvailable();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,16 @@
|
|||
namespace Icewind\SMB\Native;
|
||||
|
||||
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\InvalidHostException;
|
||||
use Icewind\SMB\Exception\InvalidPathException;
|
||||
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\IServer;
|
||||
use Icewind\SMB\Wrapped\Server;
|
||||
|
@ -27,28 +34,22 @@ class NativeShare extends AbstractShare {
|
|||
*/
|
||||
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();
|
||||
$this->server = $server;
|
||||
$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;
|
||||
}
|
||||
|
||||
|
@ -62,11 +63,11 @@ class NativeShare extends AbstractShare {
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function buildUrl($path) {
|
||||
private function buildUrl(string $path): string {
|
||||
$this->verifyPath($path);
|
||||
$url = sprintf('smb://%s/%s', $this->server->getHost(), $this->name);
|
||||
if ($path) {
|
||||
|
@ -81,16 +82,16 @@ class NativeShare extends AbstractShare {
|
|||
* List the content of a remote folder
|
||||
*
|
||||
* @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 = [];
|
||||
|
||||
$dh = $this->getState()->opendir($this->buildUrl($path));
|
||||
while ($file = $this->getState()->readdir($dh)) {
|
||||
while ($file = $this->getState()->readdir($dh, $path)) {
|
||||
$name = $file['name'];
|
||||
if ($name !== '.' and $name !== '..') {
|
||||
$fullPath = $path . '/' . $name;
|
||||
|
@ -98,15 +99,15 @@ class NativeShare extends AbstractShare {
|
|||
}
|
||||
}
|
||||
|
||||
$this->getState()->closedir($dh);
|
||||
$this->getState()->closedir($dh, $path);
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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));
|
||||
|
||||
// trigger attribute loading
|
||||
|
@ -119,10 +120,10 @@ class NativeShare extends AbstractShare {
|
|||
* Multibyte unicode safe version of basename()
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
protected static function mb_basename($path) {
|
||||
protected static function mb_basename(string $path): string {
|
||||
if (preg_match('@^.*[\\\\/]([^\\\\/]+)$@s', $path, $matches)) {
|
||||
return $matches[1];
|
||||
} elseif (preg_match('@^([^\\\\/]+)$@s', $path, $matches)) {
|
||||
|
@ -138,10 +139,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $path
|
||||
* @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));
|
||||
}
|
||||
|
||||
|
@ -151,10 +152,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $path
|
||||
* @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));
|
||||
}
|
||||
|
||||
|
@ -164,10 +165,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $path
|
||||
* @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));
|
||||
}
|
||||
|
||||
|
@ -178,10 +179,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $to
|
||||
* @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));
|
||||
}
|
||||
|
||||
|
@ -192,10 +193,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $target remove file
|
||||
* @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');
|
||||
$targetUrl = $this->buildUrl($target);
|
||||
|
||||
|
@ -215,20 +216,18 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $target local file
|
||||
* @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) {
|
||||
throw new InvalidPathException('Invalid target path: Filename cannot be empty');
|
||||
}
|
||||
|
||||
$sourceHandle = $this->getState()->open($this->buildUrl($source), 'r');
|
||||
if (!$sourceHandle) {
|
||||
throw new InvalidResourceException('Failed opening remote file "' . $source . '" for reading');
|
||||
}
|
||||
|
||||
$targetHandle = @fopen($target, 'wb');
|
||||
if (!$targetHandle) {
|
||||
|
@ -242,7 +241,7 @@ class NativeShare extends AbstractShare {
|
|||
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);
|
||||
}
|
||||
$this->getState()->close($sourceHandle, $this->buildUrl($source));
|
||||
|
@ -255,10 +254,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $source
|
||||
* @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);
|
||||
$handle = $this->getState()->open($url, 'r');
|
||||
return NativeReadStream::wrap($this->getState(), $handle, 'r', $url);
|
||||
|
@ -271,10 +270,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $source
|
||||
* @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);
|
||||
$handle = $this->getState()->create($url);
|
||||
return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url);
|
||||
|
@ -286,10 +285,10 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $source
|
||||
* @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);
|
||||
$handle = $this->getState()->open($url, "a+");
|
||||
return NativeWriteStream::wrap($this->getState(), $handle, "a", $url);
|
||||
|
@ -302,7 +301,7 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $attribute attribute to get the info
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
@ -314,9 +313,13 @@ class NativeShare extends AbstractShare {
|
|||
* @param string|int $value
|
||||
* @return mixed the attribute value
|
||||
*/
|
||||
public function setAttribute($path, $attribute, $value) {
|
||||
if ($attribute === 'system.dos_attr.mode' and is_int($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);
|
||||
|
@ -329,7 +332,7 @@ class NativeShare extends AbstractShare {
|
|||
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
|
||||
* @return mixed
|
||||
*/
|
||||
public function setMode($path, $mode) {
|
||||
public function setMode(string $path, int $mode) {
|
||||
return $this->setAttribute($path, 'system.dos_attr.mode', $mode);
|
||||
}
|
||||
|
||||
|
@ -340,7 +343,7 @@ class NativeShare extends AbstractShare {
|
|||
* @param string $path
|
||||
* @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)
|
||||
// so we use the smbclient based backend for this
|
||||
if (!Server::available($this->server->getSystem())) {
|
||||
|
|
|
@ -29,13 +29,13 @@ use Icewind\SMB\IOptions;
|
|||
* Low level wrapper for libsmbclient-php with error handling
|
||||
*/
|
||||
class NativeState {
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $state;
|
||||
/** @var resource|null */
|
||||
protected $state = null;
|
||||
|
||||
/** @var bool */
|
||||
protected $handlerSet = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $connected = false;
|
||||
|
||||
// see error.h
|
||||
|
@ -58,7 +58,8 @@ class NativeState {
|
|||
113 => NoRouteToHostException::class
|
||||
];
|
||||
|
||||
protected function handleError($path) {
|
||||
protected function handleError(?string $path): void {
|
||||
/** @var int $error */
|
||||
$error = smbclient_state_errno($this->state);
|
||||
if ($error === 0) {
|
||||
return;
|
||||
|
@ -66,14 +67,19 @@ class NativeState {
|
|||
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) {
|
||||
// smb://host/share/path
|
||||
if (is_string($uri) && count(explode('/', $uri, 5)) > 4) {
|
||||
list(, , , , $path) = explode('/', $uri, 5);
|
||||
$path = '/' . $path;
|
||||
} else {
|
||||
$path = null;
|
||||
$path = $uri;
|
||||
}
|
||||
$this->handleError($path);
|
||||
}
|
||||
|
@ -88,10 +94,21 @@ class NativeState {
|
|||
if ($this->connected) {
|
||||
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_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);
|
||||
/** @var bool $result */
|
||||
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());
|
||||
|
||||
$this->testResult($result, '');
|
||||
|
@ -103,7 +120,8 @@ class NativeState {
|
|||
* @param string $uri
|
||||
* @return resource
|
||||
*/
|
||||
public function opendir($uri) {
|
||||
public function opendir(string $uri) {
|
||||
/** @var resource $result */
|
||||
$result = @smbclient_opendir($this->state, $uri);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -112,23 +130,27 @@ class NativeState {
|
|||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $dir);
|
||||
$this->testResult($result, $path);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $dir
|
||||
* @param resource $dir
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function closedir($dir) {
|
||||
public function closedir($dir, string $path): bool {
|
||||
/** @var bool $result */
|
||||
$result = smbclient_closedir($this->state, $dir);
|
||||
|
||||
$this->testResult($result, $dir);
|
||||
$this->testResult($result, $path);
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -137,7 +159,8 @@ class NativeState {
|
|||
* @param string $new
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $new);
|
||||
|
@ -148,7 +171,8 @@ class NativeState {
|
|||
* @param string $uri
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($uri) {
|
||||
public function unlink(string $uri): bool {
|
||||
/** @var bool $result */
|
||||
$result = @smbclient_unlink($this->state, $uri);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -160,7 +184,8 @@ class NativeState {
|
|||
* @param int $mask
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -171,7 +196,8 @@ class NativeState {
|
|||
* @param string $uri
|
||||
* @return bool
|
||||
*/
|
||||
public function rmdir($uri) {
|
||||
public function rmdir(string $uri): bool {
|
||||
/** @var bool $result */
|
||||
$result = @smbclient_rmdir($this->state, $uri);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -180,9 +206,10 @@ class NativeState {
|
|||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -191,12 +218,14 @@ class NativeState {
|
|||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $file);
|
||||
$this->testResult($result, $path);
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -206,7 +235,8 @@ class NativeState {
|
|||
* @param int $mask
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -218,7 +248,8 @@ class NativeState {
|
|||
* @param int $mask
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -228,12 +259,14 @@ class NativeState {
|
|||
/**
|
||||
* @param resource $file
|
||||
* @param int $bytes
|
||||
* @param string $path
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $file);
|
||||
$this->testResult($result, $path);
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -241,10 +274,11 @@ class NativeState {
|
|||
* @param resource $file
|
||||
* @param string $data
|
||||
* @param string $path
|
||||
* @param int $length
|
||||
* @param int|null $length
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $path);
|
||||
|
@ -255,28 +289,38 @@ class NativeState {
|
|||
* @param resource $file
|
||||
* @param int $offset
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $file);
|
||||
$this->testResult($result, $path);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $file
|
||||
* @param int $size
|
||||
* @param string $path
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $file);
|
||||
$this->testResult($result, $path);
|
||||
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);
|
||||
|
||||
$this->testResult($result, $path);
|
||||
|
@ -288,7 +332,8 @@ class NativeState {
|
|||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function getxattr($uri, $key) {
|
||||
public function getxattr(string $uri, string $key) {
|
||||
/** @var string $result */
|
||||
$result = @smbclient_getxattr($this->state, $uri, $key);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
@ -300,9 +345,10 @@ class NativeState {
|
|||
* @param string $key
|
||||
* @param string $value
|
||||
* @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);
|
||||
|
||||
$this->testResult($result, $uri);
|
||||
|
|
|
@ -10,20 +10,24 @@ namespace Icewind\SMB\Native;
|
|||
use Icewind\SMB\Exception\Exception;
|
||||
use Icewind\SMB\Exception\InvalidRequestException;
|
||||
use Icewind\Streams\File;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class NativeStream implements File {
|
||||
abstract class NativeStream implements File {
|
||||
/**
|
||||
* @var resource
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var NativeState
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
|
@ -35,19 +39,20 @@ class NativeStream implements File {
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
protected $url = '';
|
||||
|
||||
/**
|
||||
* Wrap a stream from libsmbclient-php into a regular php stream
|
||||
*
|
||||
* @param \Icewind\SMB\NativeState $state
|
||||
* @param NativeState $state
|
||||
* @param resource $smbStream
|
||||
* @param string $mode
|
||||
* @param string $url
|
||||
* @param class-string<NativeStream> $class
|
||||
* @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([
|
||||
'nativesmb' => [
|
||||
'state' => $state,
|
||||
|
@ -73,19 +78,35 @@ class NativeStream implements File {
|
|||
}
|
||||
|
||||
public function stream_flush() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
$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;
|
||||
}
|
||||
|
||||
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) {
|
||||
$this->eof = true;
|
||||
}
|
||||
|
@ -95,12 +116,15 @@ class NativeStream implements File {
|
|||
public function stream_seek($offset, $whence = SEEK_SET) {
|
||||
$this->eof = false;
|
||||
try {
|
||||
return $this->state->lseek($this->handle, $offset, $whence) !== false;
|
||||
return $this->state->lseek($this->handle, $offset, $whence, $this->url) !== false;
|
||||
} catch (InvalidRequestException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{"mtime": int, "size": int, "mode": int}|false
|
||||
*/
|
||||
public function stream_stat() {
|
||||
try {
|
||||
return $this->state->stat($this->url);
|
||||
|
@ -110,7 +134,7 @@ class NativeStream implements File {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -118,7 +142,7 @@ class NativeStream implements File {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -7,71 +7,63 @@
|
|||
|
||||
namespace Icewind\SMB\Native;
|
||||
|
||||
use Icewind\SMB\StringBuffer;
|
||||
|
||||
/**
|
||||
* Stream optimized for write only usage
|
||||
*/
|
||||
class NativeWriteStream extends NativeStream {
|
||||
const CHUNK_SIZE = 1048576; // 1MB chunks
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $writeBuffer = null;
|
||||
|
||||
private $bufferSize = 0;
|
||||
/** @var StringBuffer */
|
||||
private $writeBuffer;
|
||||
|
||||
/** @var int */
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a stream from libsmbclient-php into a regular php stream
|
||||
*
|
||||
* @param \Icewind\SMB\NativeState $state
|
||||
* @param NativeState $state
|
||||
* @param resource $smbStream
|
||||
* @param string $mode
|
||||
* @param string $url
|
||||
* @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) {
|
||||
$this->flushWrite();
|
||||
$result = parent::stream_seek($offset, $whence);
|
||||
if ($result) {
|
||||
$this->pos = parent::stream_tell();
|
||||
$pos = parent::stream_tell();
|
||||
if ($pos === false) {
|
||||
return false;
|
||||
}
|
||||
$this->pos = $pos;
|
||||
}
|
||||
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) {
|
||||
$written = fwrite($this->writeBuffer, $data);
|
||||
$this->bufferSize += $written;
|
||||
$written = $this->writeBuffer->push($data);
|
||||
$this->pos += $written;
|
||||
|
||||
if ($this->bufferSize >= self::CHUNK_SIZE) {
|
||||
if ($this->writeBuffer->remaining() >= self::CHUNK_SIZE) {
|
||||
$this->flushWrite();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,11 +25,32 @@ class Options implements IOptions {
|
|||
/** @var int */
|
||||
private $timeout = 20;
|
||||
|
||||
public function getTimeout() {
|
||||
/** @var string|null */
|
||||
private $minProtocol;
|
||||
/** @var string|null */
|
||||
private $maxProtocol;
|
||||
|
||||
public function getTimeout(): int {
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
public function setTimeout($timeout) {
|
||||
public function setTimeout(int $timeout): void {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class ServerFactory {
|
|||
Server::class
|
||||
];
|
||||
|
||||
/** @var System */
|
||||
/** @var ISystem */
|
||||
private $system;
|
||||
|
||||
/** @var IOptions */
|
||||
|
@ -68,12 +68,12 @@ class ServerFactory {
|
|||
|
||||
|
||||
/**
|
||||
* @param $host
|
||||
* @param string $host
|
||||
* @param IAuth $credentials
|
||||
* @return IServer
|
||||
* @throws DependencyException
|
||||
*/
|
||||
public function createServer($host, IAuth $credentials) {
|
||||
public function createServer(string $host, IAuth $credentials): IServer {
|
||||
foreach (self::BACKENDS as $backend) {
|
||||
if (call_user_func("$backend::available", $this->system)) {
|
||||
return new $backend($host, $credentials, $this->system, $this->timeZoneProvider, $this->options);
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Icewind\SMB;
|
||||
|
||||
class StringBuffer {
|
||||
/** @var string */
|
||||
private $buffer = "";
|
||||
/** @var int */
|
||||
private $pos = 0;
|
||||
|
||||
public function clear(): void {
|
||||
$this->buffer = "";
|
||||
$this->pos = 0;
|
||||
}
|
||||
|
||||
public function push(string $data): int {
|
||||
$this->buffer = $this->flush() . $data;
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
public function remaining(): int {
|
||||
return strlen($this->buffer) - $this->pos;
|
||||
}
|
||||
|
||||
public function read(int $count): string {
|
||||
$chunk = substr($this->buffer, $this->pos, $this->pos + $count);
|
||||
$this->pos += strlen($chunk);
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
public function flush(): string {
|
||||
if ($this->pos === 0) {
|
||||
$remaining = $this->buffer;
|
||||
} else {
|
||||
$remaining = substr($this->buffer, $this->pos);
|
||||
}
|
||||
|
||||
$this->clear();
|
||||
|
||||
return $remaining;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ namespace Icewind\SMB;
|
|||
use Icewind\SMB\Exception\Exception;
|
||||
|
||||
class System implements ISystem {
|
||||
/** @var (string|bool)[] */
|
||||
/** @var (string|null)[] */
|
||||
private $paths = [];
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,7 @@ class System implements ISystem {
|
|||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getFD($num) {
|
||||
public function getFD(int $num): string {
|
||||
$folders = [
|
||||
'/proc/self/fd',
|
||||
'/dev/fd'
|
||||
|
@ -33,36 +33,36 @@ class System implements ISystem {
|
|||
throw new Exception('Cant find file descriptor path');
|
||||
}
|
||||
|
||||
public function getSmbclientPath() {
|
||||
public function getSmbclientPath(): ?string {
|
||||
return $this->getBinaryPath('smbclient');
|
||||
}
|
||||
|
||||
public function getNetPath() {
|
||||
public function getNetPath(): ?string {
|
||||
return $this->getBinaryPath('net');
|
||||
}
|
||||
|
||||
public function getSmbcAclsPath() {
|
||||
public function getSmbcAclsPath(): ?string {
|
||||
return $this->getBinaryPath('smbcacls');
|
||||
}
|
||||
|
||||
public function getStdBufPath() {
|
||||
public function getStdBufPath(): ?string {
|
||||
return $this->getBinaryPath('stdbuf');
|
||||
}
|
||||
|
||||
public function getDatePath() {
|
||||
public function getDatePath(): ?string {
|
||||
return $this->getBinaryPath('date');
|
||||
}
|
||||
|
||||
public function libSmbclientAvailable() {
|
||||
public function libSmbclientAvailable(): bool {
|
||||
return function_exists('smbclient_state_new');
|
||||
}
|
||||
|
||||
protected function getBinaryPath($binary) {
|
||||
protected function getBinaryPath(string $binary): ?string {
|
||||
if (!isset($this->paths[$binary])) {
|
||||
$result = null;
|
||||
$output = [];
|
||||
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];
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class TimeZoneProvider implements ITimeZoneProvider {
|
|||
$this->system = $system;
|
||||
}
|
||||
|
||||
public function get($host) {
|
||||
public function get(string $host): string {
|
||||
if (!isset($this->timeZones[$host])) {
|
||||
$timeZone = null;
|
||||
$net = $this->system->getNetPath();
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
|
||||
namespace Icewind\SMB\Wrapped;
|
||||
|
||||
use Icewind\SMB\Exception\AccessDeniedException;
|
||||
use Icewind\SMB\Exception\AuthenticationException;
|
||||
use Icewind\SMB\Exception\ConnectException;
|
||||
use Icewind\SMB\Exception\ConnectionException;
|
||||
use Icewind\SMB\Exception\ConnectionRefusedException;
|
||||
use Icewind\SMB\Exception\InvalidHostException;
|
||||
use Icewind\SMB\Exception\NoLoginServerException;
|
||||
|
||||
|
@ -20,7 +22,12 @@ class Connection extends RawConnection {
|
|||
/** @var 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);
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
@ -30,39 +37,48 @@ class Connection extends RawConnection {
|
|||
*
|
||||
* @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
|
||||
*/
|
||||
public function clearTillPrompt() {
|
||||
public function clearTillPrompt(): void {
|
||||
$this->write('');
|
||||
do {
|
||||
$promptLine = $this->readLine();
|
||||
if ($promptLine === false) {
|
||||
break;
|
||||
}
|
||||
$this->parser->checkConnectionError($promptLine);
|
||||
} while (!$this->isPrompt($promptLine));
|
||||
$this->write('');
|
||||
if ($this->write('') === false) {
|
||||
throw new ConnectionRefusedException();
|
||||
}
|
||||
$this->readLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[]
|
||||
* @throws AuthenticationException
|
||||
* @throws ConnectException
|
||||
* @throws ConnectionException
|
||||
* @throws InvalidHostException
|
||||
* @throws NoLoginServerException
|
||||
* @throws AccessDeniedException
|
||||
*/
|
||||
public function read(callable $callback = null) {
|
||||
public function read(callable $callback = null): array {
|
||||
if (!$this->isValid()) {
|
||||
throw new ConnectionException('Connection not valid');
|
||||
}
|
||||
$promptLine = $this->readLine(); //first line is prompt
|
||||
if ($promptLine === false) {
|
||||
$this->unknownError($promptLine);
|
||||
}
|
||||
$this->parser->checkConnectionError($promptLine);
|
||||
|
||||
$output = [];
|
||||
|
@ -74,7 +90,7 @@ class Connection extends RawConnection {
|
|||
if ($line === false) {
|
||||
$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)) {
|
||||
$result = $callback($line);
|
||||
if ($result === false) { // allow the callback to close the connection for infinite running commands
|
||||
|
@ -82,26 +98,21 @@ class Connection extends RawConnection {
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
$output[] .= $line;
|
||||
$output[] = $line;
|
||||
}
|
||||
$line = $this->readLine();
|
||||
}
|
||||
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
|
||||
* @return no-return
|
||||
*/
|
||||
private function unknownError($promptLine = '') {
|
||||
if ($promptLine) { //maybe we have some error we missed on the previous line
|
||||
|
@ -116,7 +127,7 @@ class Connection extends RawConnection {
|
|||
}
|
||||
}
|
||||
|
||||
public function close($terminate = true) {
|
||||
public function close(bool $terminate = true): void {
|
||||
if (get_resource_type($this->getInputStream()) === 'stream') {
|
||||
// ignore any errors while trying to send the close command, the process might already be dead
|
||||
@$this->write('close' . PHP_EOL);
|
||||
|
|
|
@ -11,34 +11,17 @@ use Icewind\SMB\ACL;
|
|||
use Icewind\SMB\IFileInfo;
|
||||
|
||||
class FileInfo implements IFileInfo {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
protected $time;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
protected $mode;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
/** @var callable(): ACL[] */
|
||||
protected $aclCallback;
|
||||
|
||||
/**
|
||||
|
@ -47,9 +30,9 @@ class FileInfo implements IFileInfo {
|
|||
* @param int $size
|
||||
* @param int $time
|
||||
* @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->name = $name;
|
||||
$this->size = $size;
|
||||
|
@ -61,63 +44,39 @@ class FileInfo implements IFileInfo {
|
|||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() {
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSize() {
|
||||
public function getSize(): int {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMTime() {
|
||||
public function getMTime(): int {
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirectory() {
|
||||
public function isDirectory(): bool {
|
||||
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
|
||||
*/
|
||||
public function isHidden() {
|
||||
public function isHidden(): bool {
|
||||
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
|
||||
*/
|
||||
public function isArchived() {
|
||||
public function isArchived(): bool {
|
||||
return (bool)($this->mode & IFileInfo::MODE_ARCHIVE);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,16 +14,13 @@ use Icewind\SMB\Exception\RevisionMismatchException;
|
|||
use Icewind\SMB\INotifyHandler;
|
||||
|
||||
class NotifyHandler implements INotifyHandler {
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
/** @var Connection */
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
private $path;
|
||||
|
||||
/** @var bool */
|
||||
private $listening = true;
|
||||
|
||||
// see error.h
|
||||
|
@ -35,7 +32,7 @@ class NotifyHandler implements INotifyHandler {
|
|||
* @param Connection $connection
|
||||
* @param string $path
|
||||
*/
|
||||
public function __construct(Connection $connection, $path) {
|
||||
public function __construct(Connection $connection, string $path) {
|
||||
$this->connection = $connection;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
@ -45,17 +42,17 @@ class NotifyHandler implements INotifyHandler {
|
|||
*
|
||||
* @return Change[]
|
||||
*/
|
||||
public function getChanges() {
|
||||
public function getChanges(): array {
|
||||
if (!$this->listening) {
|
||||
return [];
|
||||
}
|
||||
stream_set_blocking($this->connection->getOutputStream(), 0);
|
||||
stream_set_blocking($this->connection->getOutputStream(), false);
|
||||
$lines = [];
|
||||
while (($line = $this->connection->readLine())) {
|
||||
$this->checkForError($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)));
|
||||
}
|
||||
|
||||
|
@ -64,21 +61,24 @@ class NotifyHandler implements INotifyHandler {
|
|||
*
|
||||
* Note that this is a blocking process and will cause the process to block forever if not explicitly terminated
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param callable(Change):?bool $callback
|
||||
*/
|
||||
public function listen($callback) {
|
||||
public function listen(callable $callback): void {
|
||||
if ($this->listening) {
|
||||
$this->connection->read(function ($line) use ($callback) {
|
||||
$this->connection->read(function (string $line) use ($callback): bool {
|
||||
$this->checkForError($line);
|
||||
$change = $this->parseChangeLine($line);
|
||||
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);
|
||||
if ($code === 0) {
|
||||
return null;
|
||||
|
@ -91,14 +91,14 @@ class NotifyHandler implements INotifyHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private function checkForError($line) {
|
||||
private function checkForError(string $line): void {
|
||||
if (substr($line, 0, 16) === 'notify returned ') {
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
||||
public function stop() {
|
||||
public function stop(): void {
|
||||
$this->listening = false;
|
||||
$this->connection->close();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Icewind\SMB\Wrapped;
|
||||
|
||||
use Icewind\SMB\ACL;
|
||||
use Icewind\SMB\Exception\AccessDeniedException;
|
||||
use Icewind\SMB\Exception\AlreadyExistsException;
|
||||
use Icewind\SMB\Exception\AuthenticationException;
|
||||
|
@ -28,11 +29,6 @@ class Parser {
|
|||
*/
|
||||
protected $timeZone;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $host;
|
||||
|
||||
// see error.h
|
||||
const EXCEPTION_MAP = [
|
||||
ErrorCodes::LogonFailure => AuthenticationException::class,
|
||||
|
@ -60,21 +56,29 @@ class Parser {
|
|||
/**
|
||||
* @param string $timeZone
|
||||
*/
|
||||
public function __construct($timeZone) {
|
||||
public function __construct(string $timeZone) {
|
||||
$this->timeZone = $timeZone;
|
||||
}
|
||||
|
||||
private function getErrorCode($line) {
|
||||
private function getErrorCode(string $line): ?string {
|
||||
$parts = explode(' ', $line);
|
||||
foreach ($parts as $part) {
|
||||
if (substr($part, 0, 9) === 'NT_STATUS') {
|
||||
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')) {
|
||||
throw new NotFoundException($path);
|
||||
}
|
||||
|
@ -91,13 +95,13 @@ class Parser {
|
|||
/**
|
||||
* check if the first line holds a connection failure
|
||||
*
|
||||
* @param $line
|
||||
* @param string $line
|
||||
* @throws AuthenticationException
|
||||
* @throws InvalidHostException
|
||||
* @throws NoLoginServerException
|
||||
* @throws AccessDeniedException
|
||||
*/
|
||||
public function checkConnectionError($line) {
|
||||
public function checkConnectionError(string $line): void {
|
||||
$line = rtrim($line, ')');
|
||||
if (substr($line, -23) === ErrorCodes::LogonFailure) {
|
||||
throw new AuthenticationException('Invalid login');
|
||||
|
@ -119,7 +123,7 @@ class Parser {
|
|||
}
|
||||
}
|
||||
|
||||
public function parseMode($mode) {
|
||||
public function parseMode(string $mode): int {
|
||||
$result = 0;
|
||||
foreach (self::MODE_STRINGS as $char => $val) {
|
||||
if (strpos($mode, $char) !== false) {
|
||||
|
@ -129,7 +133,12 @@ class Parser {
|
|||
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 = [];
|
||||
foreach ($output as $line) {
|
||||
// A line = explode statement may not fill all array elements
|
||||
|
@ -143,14 +152,24 @@ class Parser {
|
|||
$data[$name] = $value;
|
||||
}
|
||||
}
|
||||
$attributeStart = strpos($data['attributes'], '(');
|
||||
if ($attributeStart === false) {
|
||||
throw new Exception("Malformed state response from server");
|
||||
}
|
||||
return [
|
||||
'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
|
||||
];
|
||||
}
|
||||
|
||||
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
|
||||
array_pop($output);
|
||||
$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
|
||||
|
@ -163,7 +182,7 @@ class Parser {
|
|||
$mode = $this->parseMode($mode);
|
||||
$time = strtotime($time . ' ' . $this->timeZone);
|
||||
$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);
|
||||
});
|
||||
}
|
||||
|
@ -172,7 +191,11 @@ class Parser {
|
|||
return $content;
|
||||
}
|
||||
|
||||
public function parseListShares($output) {
|
||||
/**
|
||||
* @param string[] $output
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function parseListShares(array $output): array {
|
||||
$shareNames = [];
|
||||
foreach ($output as $line) {
|
||||
if (strpos($line, '|')) {
|
||||
|
@ -188,4 +211,67 @@ class Parser {
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,10 @@ class RawConnection {
|
|||
* $pipes[4] holds the stream for writing files
|
||||
* $pipes[5] holds the stream for reading files
|
||||
*/
|
||||
private $pipes;
|
||||
private $pipes = [];
|
||||
|
||||
/**
|
||||
* @var resource $process
|
||||
* @var resource|null $process
|
||||
*/
|
||||
private $process;
|
||||
|
||||
|
@ -42,17 +42,20 @@ class RawConnection {
|
|||
*/
|
||||
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->env = $env;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConnectException
|
||||
* @psalm-assert resource $this->process
|
||||
*/
|
||||
public function connect() {
|
||||
public function connect(): void {
|
||||
if (is_null($this->getAuthStream())) {
|
||||
throw new ConnectException('Authentication not set before connecting');
|
||||
}
|
||||
|
@ -77,18 +80,18 @@ class RawConnection {
|
|||
if (!$this->isValid()) {
|
||||
throw new ConnectionException();
|
||||
}
|
||||
$this->connected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the connection is still active
|
||||
*
|
||||
* @return bool
|
||||
* @psalm-assert-if-true resource $this->process
|
||||
*/
|
||||
public function isValid() {
|
||||
public function isValid(): bool {
|
||||
if (is_resource($this->process)) {
|
||||
$status = proc_get_status($this->process);
|
||||
return $status['running'];
|
||||
return (bool)$status['running'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -98,10 +101,12 @@ class RawConnection {
|
|||
* send input to the process
|
||||
*
|
||||
* @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());
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,18 +121,19 @@ class RawConnection {
|
|||
/**
|
||||
* read a line of output
|
||||
*
|
||||
* @return string
|
||||
* @return string|false
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return array
|
||||
* @return string[]
|
||||
*/
|
||||
public function readAll() {
|
||||
public function readAll(): array {
|
||||
$output = [];
|
||||
while ($line = $this->readLine()) {
|
||||
$output[] = $line;
|
||||
|
@ -135,40 +141,67 @@ class RawConnection {
|
|||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getInputStream() {
|
||||
return $this->pipes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getOutputStream() {
|
||||
return $this->pipes[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getErrorStream() {
|
||||
return $this->pipes[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|null
|
||||
*/
|
||||
public function getAuthStream() {
|
||||
return $this->authStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getFileInputStream() {
|
||||
return $this->pipes[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getFileOutputStream() {
|
||||
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\npassword=$password\n";
|
||||
|
||||
$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)) {
|
||||
return;
|
||||
}
|
||||
|
@ -176,9 +209,10 @@ class RawConnection {
|
|||
proc_terminate($this->process);
|
||||
}
|
||||
proc_close($this->process);
|
||||
$this->process = null;
|
||||
}
|
||||
|
||||
public function reconnect() {
|
||||
public function reconnect(): void {
|
||||
$this->close();
|
||||
$this->connect();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ use Icewind\SMB\AbstractServer;
|
|||
use Icewind\SMB\Exception\AuthenticationException;
|
||||
use Icewind\SMB\Exception\ConnectException;
|
||||
use Icewind\SMB\Exception\ConnectionException;
|
||||
use Icewind\SMB\Exception\ConnectionRefusedException;
|
||||
use Icewind\SMB\Exception\Exception;
|
||||
use Icewind\SMB\Exception\InvalidHostException;
|
||||
use Icewind\SMB\IShare;
|
||||
use Icewind\SMB\ISystem;
|
||||
|
@ -22,11 +24,11 @@ class Server extends AbstractServer {
|
|||
* @param ISystem $system
|
||||
* @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()) {
|
||||
return '--authentication-file=' . $this->system->getFD(3);
|
||||
} else {
|
||||
|
@ -41,22 +43,30 @@ class Server extends AbstractServer {
|
|||
* @throws InvalidHostException
|
||||
* @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(
|
||||
'%s %s %s -L %s',
|
||||
$this->system->getSmbclientPath(),
|
||||
'%s %s %s %s %s -L %s',
|
||||
$smbClient,
|
||||
$this->getAuthFileArgument(),
|
||||
$this->getAuth()->getExtraCommandLineArguments(),
|
||||
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "",
|
||||
$minProtocol ? "--option='client min protocol=" . $minProtocol . "'" : "",
|
||||
escapeshellarg('//' . $this->getHost())
|
||||
);
|
||||
$connection = new RawConnection($command);
|
||||
$connection->writeAuthentication($this->getAuth()->getUsername(), $this->getAuth()->getPassword());
|
||||
$connection->connect();
|
||||
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();
|
||||
if (isset($output[0])) {
|
||||
|
@ -71,6 +81,9 @@ class Server extends AbstractServer {
|
|||
if (isset($output[0])) {
|
||||
$parser->checkConnectionError($output[0]);
|
||||
}
|
||||
if (count($output) === 0) {
|
||||
throw new ConnectionRefusedException();
|
||||
}
|
||||
|
||||
$shareNames = $parser->parseListShares($output);
|
||||
|
||||
|
@ -85,7 +98,7 @@ class Server extends AbstractServer {
|
|||
* @param string $name
|
||||
* @return IShare
|
||||
*/
|
||||
public function getShare($name) {
|
||||
public function getShare(string $name): IShare {
|
||||
return new Share($this, $name, $this->system);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,14 @@ namespace Icewind\SMB\Wrapped;
|
|||
|
||||
use Icewind\SMB\AbstractShare;
|
||||
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\DependencyException;
|
||||
use Icewind\SMB\Exception\Exception;
|
||||
use Icewind\SMB\Exception\FileInUseException;
|
||||
use Icewind\SMB\Exception\InvalidHostException;
|
||||
use Icewind\SMB\Exception\InvalidTypeException;
|
||||
use Icewind\SMB\Exception\NotFoundException;
|
||||
use Icewind\SMB\Exception\InvalidRequestException;
|
||||
|
@ -35,9 +40,9 @@ class Share extends AbstractShare {
|
|||
private $name;
|
||||
|
||||
/**
|
||||
* @var Connection $connection
|
||||
* @var Connection|null $connection
|
||||
*/
|
||||
public $connection;
|
||||
public $connection = null;
|
||||
|
||||
/**
|
||||
* @var Parser
|
||||
|
@ -63,7 +68,7 @@ class Share extends AbstractShare {
|
|||
* @param string $name
|
||||
* @param ISystem $system
|
||||
*/
|
||||
public function __construct(IServer $server, $name, ISystem $system) {
|
||||
public function __construct(IServer $server, string $name, ISystem $system) {
|
||||
parent::__construct();
|
||||
$this->server = $server;
|
||||
$this->name = $name;
|
||||
|
@ -71,7 +76,7 @@ class Share extends AbstractShare {
|
|||
$this->parser = new Parser($server->getTimeZone());
|
||||
}
|
||||
|
||||
private function getAuthFileArgument() {
|
||||
private function getAuthFileArgument(): string {
|
||||
if ($this->server->getAuth()->getUsername()) {
|
||||
return '--authentication-file=' . $this->system->getFD(3);
|
||||
} else {
|
||||
|
@ -79,22 +84,31 @@ class Share extends AbstractShare {
|
|||
}
|
||||
}
|
||||
|
||||
protected function getConnection() {
|
||||
protected function getConnection(): Connection {
|
||||
$maxProtocol = $this->server->getOptions()->getMaxProtocol();
|
||||
$minProtocol = $this->server->getOptions()->getMinProtocol();
|
||||
$smbClient = $this->system->getSmbclientPath();
|
||||
$stdBuf = $this->system->getStdBufPath();
|
||||
if ($smbClient === null) {
|
||||
throw new Exception("Backend not available");
|
||||
}
|
||||
$command = sprintf(
|
||||
'%s %s%s -t %s %s %s %s',
|
||||
'%s %s%s -t %s %s %s %s %s %s',
|
||||
self::EXEC_CMD,
|
||||
$this->system->getStdBufPath() ? $this->system->getStdBufPath() . ' -o0 ' : '',
|
||||
$this->system->getSmbclientPath(),
|
||||
$stdBuf ? $stdBuf . ' -o0 ' : '',
|
||||
$smbClient,
|
||||
$this->server->getOptions()->getTimeout(),
|
||||
$this->getAuthFileArgument(),
|
||||
$this->server->getAuth()->getExtraCommandLineArguments(),
|
||||
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "",
|
||||
$minProtocol ? "--option='client min protocol=" . $minProtocol . "'" : "",
|
||||
escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
|
||||
);
|
||||
$connection = new Connection($command, $this->parser);
|
||||
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
|
||||
$connection->connect();
|
||||
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
|
||||
$connection->clearTillPrompt();
|
||||
|
@ -102,34 +116,46 @@ class Share extends AbstractShare {
|
|||
}
|
||||
|
||||
/**
|
||||
* @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()) {
|
||||
return;
|
||||
return $this->connection;
|
||||
}
|
||||
$this->connection = $this->getConnection();
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
protected function reconnect() {
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the share
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
protected function simpleCommand($command, $path) {
|
||||
protected function simpleCommand(string $command, string $path): bool {
|
||||
$escapedPath = $this->escapePath($path);
|
||||
$cmd = $command . ' ' . $escapedPath;
|
||||
$output = $this->execute($cmd);
|
||||
|
@ -139,13 +165,13 @@ class Share extends AbstractShare {
|
|||
/**
|
||||
* 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);
|
||||
$output = $this->execute('cd ' . $escapedPath);
|
||||
//check output for errors
|
||||
|
@ -154,16 +180,16 @@ class Share extends AbstractShare {
|
|||
|
||||
$this->execute('cd /');
|
||||
|
||||
return $this->parser->parseDir($output, $path, function ($path) {
|
||||
return $this->parser->parseDir($output, $path, function (string $path) {
|
||||
return $this->getAcls($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
|
||||
// use the dir command instead to get the file info where possible
|
||||
if ($path !== "" && $path !== "/") {
|
||||
|
@ -200,10 +226,10 @@ class Share extends AbstractShare {
|
|||
* @param string $path
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
@ -213,10 +239,10 @@ class Share extends AbstractShare {
|
|||
* @param string $path
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
@ -230,7 +256,7 @@ class Share extends AbstractShare {
|
|||
* @throws NotFoundException
|
||||
* @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
|
||||
//we catch it so we can check if $path doesn't exist or is of invalid type
|
||||
try {
|
||||
|
@ -261,10 +287,10 @@ class Share extends AbstractShare {
|
|||
* @param string $to
|
||||
* @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);
|
||||
$path2 = $this->escapePath($to);
|
||||
$output = $this->execute('rename ' . $path1 . ' ' . $path2);
|
||||
|
@ -278,10 +304,10 @@ class Share extends AbstractShare {
|
|||
* @param string $target remove file
|
||||
* @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
|
||||
$path2 = $this->escapePath($target);
|
||||
$output = $this->execute('put ' . $path1 . ' ' . $path2);
|
||||
|
@ -295,10 +321,10 @@ class Share extends AbstractShare {
|
|||
* @param string $target local file
|
||||
* @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);
|
||||
$path2 = $this->escapeLocalPath($target); //second path is local, needs different escaping
|
||||
$output = $this->execute('get ' . $path1 . ' ' . $path2);
|
||||
|
@ -311,10 +337,10 @@ class Share extends AbstractShare {
|
|||
* @param string $source
|
||||
* @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);
|
||||
// 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
|
||||
|
@ -333,10 +359,10 @@ class Share extends AbstractShare {
|
|||
* @param string $target
|
||||
* @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);
|
||||
// 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
|
||||
|
@ -348,9 +374,14 @@ class Share extends AbstractShare {
|
|||
|
||||
// use a close callback to ensure the upload is finished before continuing
|
||||
// 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
|
||||
});
|
||||
if (is_resource($stream)) {
|
||||
return $stream;
|
||||
} else {
|
||||
throw new InvalidRequestException($target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,9 +390,9 @@ class Share extends AbstractShare {
|
|||
*
|
||||
* @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');
|
||||
}
|
||||
|
||||
|
@ -370,7 +401,7 @@ class Share extends AbstractShare {
|
|||
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
|
||||
* @return mixed
|
||||
*/
|
||||
public function setMode($path, $mode) {
|
||||
public function setMode(string $path, int $mode) {
|
||||
$modeString = '';
|
||||
foreach (self::MODE_MAP as $modeByte => $string) {
|
||||
if ($mode & $modeByte) {
|
||||
|
@ -400,7 +431,7 @@ class Share extends AbstractShare {
|
|||
* @throws ConnectionException
|
||||
* @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
|
||||
throw new DependencyException('stdbuf is required for usage of the notify command');
|
||||
}
|
||||
|
@ -412,12 +443,11 @@ class Share extends AbstractShare {
|
|||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -427,19 +457,18 @@ class Share extends AbstractShare {
|
|||
* @param string $path
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Icewind\SMB\Exception\AlreadyExistsException
|
||||
* @throws AlreadyExistsException
|
||||
* @throws \Icewind\SMB\Exception\AccessDeniedException
|
||||
* @throws \Icewind\SMB\Exception\NotEmptyException
|
||||
* @throws \Icewind\SMB\Exception\InvalidTypeException
|
||||
* @throws InvalidTypeException
|
||||
* @throws \Icewind\SMB\Exception\Exception
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
protected function parseOutput($lines, $path = '') {
|
||||
protected function parseOutput(array $lines, string $path = ''): bool {
|
||||
if (count($lines) === 0) {
|
||||
return true;
|
||||
} else {
|
||||
$this->parser->checkForError($lines, $path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -447,7 +476,7 @@ class Share extends AbstractShare {
|
|||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function escape($string) {
|
||||
protected function escape(string $string): string {
|
||||
return escapeshellarg($string);
|
||||
}
|
||||
|
||||
|
@ -455,7 +484,7 @@ class Share extends AbstractShare {
|
|||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function escapePath($path) {
|
||||
protected function escapePath(string $path): string {
|
||||
$this->verifyPath($path);
|
||||
if ($path === '/') {
|
||||
$path = '';
|
||||
|
@ -470,12 +499,18 @@ class Share extends AbstractShare {
|
|||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function escapeLocalPath($path) {
|
||||
protected function escapeLocalPath(string $path): string {
|
||||
$path = str_replace('"', '\"', $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();
|
||||
if (!$commandPath) {
|
||||
return [];
|
||||
|
@ -494,62 +529,11 @@ class Share extends AbstractShare {
|
|||
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
|
||||
$connection->connect();
|
||||
if (!$connection->isValid()) {
|
||||
throw new ConnectionException($connection->readLine());
|
||||
throw new ConnectionException((string)$connection->readLine());
|
||||
}
|
||||
|
||||
$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 {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
.idea
|
||||
vendor
|
||||
composer.lock
|
||||
build
|
||||
example.php
|
||||
*.cache
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
language: php
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
|
||||
env:
|
||||
global:
|
||||
- CURRENT_DIR=`pwd`
|
||||
|
||||
install:
|
||||
- composer install --dev --no-interaction
|
||||
|
||||
script:
|
||||
- mkdir -p build/logs
|
||||
- cd tests
|
||||
- phpunit --coverage-clover ../build/logs/clover.xml --configuration phpunit.xml
|
||||
|
||||
after_script:
|
||||
- cd $CURRENT_DIR
|
||||
- php vendor/bin/coveralls -v
|
|
@ -1,8 +1,7 @@
|
|||
# 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.
|
||||
|
||||
|
@ -14,7 +13,7 @@ it wraps an existing stream and can thus be used for any stream in php
|
|||
The callbacks are passed in the stream context along with the source stream
|
||||
and can be any valid [php callable](http://php.net/manual/en/language.types.callable.php)
|
||||
|
||||
###Example###
|
||||
### Example ###
|
||||
```php
|
||||
<?php
|
||||
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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": {
|
||||
"Icewind\\Streams\\Tests\\": "tests/",
|
||||
"Icewind\\Streams\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Icewind\\Streams\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,27 +25,27 @@ namespace Icewind\Streams;
|
|||
*/
|
||||
class CallbackWrapper extends Wrapper {
|
||||
/**
|
||||
* @var callable
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $readCallback;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $writeCallback;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $closeCallback;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $readDirCallBack;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $preCloseCallback;
|
||||
|
||||
|
@ -53,30 +53,28 @@ class CallbackWrapper extends Wrapper {
|
|||
* Wraps a stream with the provided callbacks
|
||||
*
|
||||
* @param resource $source
|
||||
* @param callable $read (optional)
|
||||
* @param callable $write (optional)
|
||||
* @param callable $close (optional)
|
||||
* @param callable $readDir (optional)
|
||||
* @return resource
|
||||
* @param callable|null $read (optional)
|
||||
* @param callable|null $write (optional)
|
||||
* @param callable|null $close (optional)
|
||||
* @param callable|null $readDir (optional)
|
||||
* @param callable|null $preClose (optional)
|
||||
* @return resource|bool
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null, $preClose = null) {
|
||||
$context = stream_context_create(array(
|
||||
'callback' => array(
|
||||
$context = [
|
||||
'source' => $source,
|
||||
'read' => $read,
|
||||
'write' => $write,
|
||||
'close' => $close,
|
||||
'readDir' => $readDir,
|
||||
'preClose' => $preClose,
|
||||
)
|
||||
));
|
||||
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CallbackWrapper');
|
||||
];
|
||||
return self::wrapSource($source, $context);
|
||||
}
|
||||
|
||||
protected function open() {
|
||||
$context = $this->loadContext('callback');
|
||||
$context = $this->loadContext();
|
||||
|
||||
$this->readCallback = $context['read'];
|
||||
$this->writeCallback = $context['write'];
|
||||
|
@ -112,7 +110,7 @@ class CallbackWrapper extends Wrapper {
|
|||
|
||||
public function stream_close() {
|
||||
if (is_callable($this->preCloseCallback)) {
|
||||
call_user_func($this->preCloseCallback, $this->loadContext('callback')['source']);
|
||||
call_user_func($this->preCloseCallback, $this->source);
|
||||
// prevent further calls by potential PHP 7 GC ghosts
|
||||
$this->preCloseCallback = null;
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class CountWrapper extends Wrapper {
|
|||
*
|
||||
* @param resource $source
|
||||
* @param callable $callback
|
||||
* @return resource
|
||||
* @return resource|bool
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
|
@ -63,17 +63,14 @@ class CountWrapper extends Wrapper {
|
|||
if (!is_callable($callback)) {
|
||||
throw new \InvalidArgumentException('Invalid or missing callback');
|
||||
}
|
||||
$context = stream_context_create(array(
|
||||
'count' => array(
|
||||
return self::wrapSource($source, [
|
||||
'source' => $source,
|
||||
'callback' => $callback
|
||||
)
|
||||
));
|
||||
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CountWrapper');
|
||||
]);
|
||||
}
|
||||
|
||||
protected function open() {
|
||||
$context = $this->loadContext('count');
|
||||
$context = $this->loadContext();
|
||||
$this->callback = $context['callback'];
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ interface Directory {
|
|||
public function dir_opendir($path, $options);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string|bool
|
||||
*/
|
||||
public function dir_readdir();
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class DirectoryFilter extends DirectoryWrapper {
|
|||
* @return bool
|
||||
*/
|
||||
public function dir_opendir($path, $options) {
|
||||
$context = $this->loadContext('filter');
|
||||
$context = $this->loadContext();
|
||||
$this->filter = $context['filter'];
|
||||
return true;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class DirectoryFilter extends DirectoryWrapper {
|
|||
public function dir_readdir() {
|
||||
$file = readdir($this->source);
|
||||
$filter = $this->filter;
|
||||
// keep reading untill we have an accepted entry or we're at the end of the folder
|
||||
// keep reading until we have an accepted entry or we're at the end of the folder
|
||||
while ($file !== false && $filter($file) === false) {
|
||||
$file = readdir($this->source);
|
||||
}
|
||||
|
@ -46,15 +46,12 @@ class DirectoryFilter extends DirectoryWrapper {
|
|||
/**
|
||||
* @param resource $source
|
||||
* @param callable $filter
|
||||
* @return resource
|
||||
* @return resource|bool
|
||||
*/
|
||||
public static function wrap($source, callable $filter) {
|
||||
$options = array(
|
||||
'filter' => array(
|
||||
return self::wrapSource($source, [
|
||||
'source' => $source,
|
||||
'filter' => $filter
|
||||
)
|
||||
);
|
||||
return self::wrapWithOptions($options, '\Icewind\Streams\DirectoryFilter');
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,37 +7,9 @@
|
|||
|
||||
namespace Icewind\Streams;
|
||||
|
||||
class DirectoryWrapper implements Directory {
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Load the source from the stream context and return the context options
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function loadContext($name) {
|
||||
$context = stream_context_get_options($this->context);
|
||||
if (isset($context[$name])) {
|
||||
$context = $context[$name];
|
||||
} else {
|
||||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
|
||||
}
|
||||
if (isset($context['source']) and is_resource($context['source'])) {
|
||||
$this->source = $context['source'];
|
||||
} else {
|
||||
throw new \BadMethodCallException('Invalid context, source not set');
|
||||
}
|
||||
return $context;
|
||||
class DirectoryWrapper extends Wrapper implements Directory {
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +18,7 @@ class DirectoryWrapper implements Directory {
|
|||
* @return bool
|
||||
*/
|
||||
public function dir_opendir($path, $options) {
|
||||
$this->loadContext('dir');
|
||||
$this->loadContext();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -72,17 +44,4 @@ class DirectoryWrapper implements Directory {
|
|||
rewinddir($this->source);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options the options for the context to wrap the stream with
|
||||
* @param string $class
|
||||
* @return resource
|
||||
*/
|
||||
protected static function wrapWithOptions($options, $class) {
|
||||
$context = stream_context_create($options);
|
||||
stream_wrapper_register('dirwrapper', $class);
|
||||
$wrapped = opendir('dirwrapper://', $context);
|
||||
stream_wrapper_unregister('dirwrapper');
|
||||
return $wrapped;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ interface File {
|
|||
* @param string $path
|
||||
* @param string $mode
|
||||
* @param int $options
|
||||
* @param string &$opened_path
|
||||
* @param string $opened_path
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_open($path, $mode, $options, &$opened_path);
|
||||
|
@ -28,19 +28,19 @@ interface File {
|
|||
public function stream_seek($offset, $whence = SEEK_SET);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @return int|false
|
||||
*/
|
||||
public function stream_tell();
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
* @return string
|
||||
* @return string|false
|
||||
*/
|
||||
public function stream_read($count);
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return int
|
||||
* @return int|false
|
||||
*/
|
||||
public function stream_write($data);
|
||||
|
||||
|
@ -59,7 +59,7 @@ interface File {
|
|||
public function stream_truncate($size);
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return array|false
|
||||
*/
|
||||
public function stream_stat();
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Icewind\Streams;
|
||||
|
||||
abstract class HashWrapper extends Wrapper {
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* @var resource|\HashContext
|
||||
*/
|
||||
private $hashContext;
|
||||
|
||||
/**
|
||||
* Wraps a stream to make it seekable
|
||||
*
|
||||
* @param resource $source
|
||||
* @param string $hash
|
||||
* @param callable $callback
|
||||
* @return resource|bool
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public static function wrap($source, $hash, $callback) {
|
||||
$context = [
|
||||
'hash' => $hash,
|
||||
'callback' => $callback,
|
||||
];
|
||||
return self::wrapSource($source, $context);
|
||||
}
|
||||
|
||||
public function dir_opendir($path, $options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
$context = $this->loadContext();
|
||||
$this->callback = $context['callback'];
|
||||
$this->hashContext = hash_init($context['hash']);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function updateHash($data) {
|
||||
hash_update($this->hashContext, $data);
|
||||
}
|
||||
|
||||
public function stream_close() {
|
||||
$hash = hash_final($this->hashContext);
|
||||
if ($this->hashContext !== false && is_callable($this->callback)) {
|
||||
call_user_func($this->callback, $hash);
|
||||
}
|
||||
return parent::stream_close();
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ namespace Icewind\Streams;
|
|||
*
|
||||
* Either 'array' or 'iterator' need to be set, if both are set, 'iterator' takes preference
|
||||
*/
|
||||
class IteratorDirectory implements Directory {
|
||||
class IteratorDirectory extends WrapperHandler implements Directory {
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
|
@ -36,18 +36,13 @@ class IteratorDirectory implements Directory {
|
|||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
protected function loadContext($name) {
|
||||
$context = stream_context_get_options($this->context);
|
||||
if (isset($context[$name])) {
|
||||
$context = $context[$name];
|
||||
} else {
|
||||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
|
||||
}
|
||||
protected function loadContext($name = null) {
|
||||
$context = parent::loadContext($name);
|
||||
if (isset($context['iterator'])) {
|
||||
$this->iterator = $context['iterator'];
|
||||
} else if (isset($context['array'])) {
|
||||
} elseif (isset($context['array'])) {
|
||||
$this->iterator = new \ArrayIterator($context['array']);
|
||||
} else {
|
||||
throw new \BadMethodCallException('Invalid context, iterator or array not set');
|
||||
|
@ -61,12 +56,12 @@ class IteratorDirectory implements Directory {
|
|||
* @return bool
|
||||
*/
|
||||
public function dir_opendir($path, $options) {
|
||||
$this->loadContext('dir');
|
||||
$this->loadContext();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string|bool
|
||||
*/
|
||||
public function dir_readdir() {
|
||||
if ($this->iterator->valid()) {
|
||||
|
@ -97,27 +92,22 @@ class IteratorDirectory implements Directory {
|
|||
* Creates a directory handle from the provided array or iterator
|
||||
*
|
||||
* @param \Iterator | array $source
|
||||
* @return resource
|
||||
* @return resource|bool
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public static function wrap($source) {
|
||||
if ($source instanceof \Iterator) {
|
||||
$context = stream_context_create(array(
|
||||
'dir' => array(
|
||||
'iterator' => $source)
|
||||
));
|
||||
} else if (is_array($source)) {
|
||||
$context = stream_context_create(array(
|
||||
'dir' => array(
|
||||
'array' => $source)
|
||||
));
|
||||
$options = [
|
||||
'iterator' => $source
|
||||
];
|
||||
} elseif (is_array($source)) {
|
||||
$options = [
|
||||
'array' => $source
|
||||
];
|
||||
} else {
|
||||
throw new \BadMethodCallException('$source should be an Iterator or array');
|
||||
}
|
||||
stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory');
|
||||
$wrapped = opendir('iterator://', $context);
|
||||
stream_wrapper_unregister('iterator');
|
||||
return $wrapped;
|
||||
return self::wrapSource(self::NO_SOURCE_DIR, $options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,29 +11,17 @@ namespace Icewind\Streams;
|
|||
* Stream wrapper that does nothing, used for tests
|
||||
*/
|
||||
class NullWrapper extends Wrapper {
|
||||
/**
|
||||
* Wraps a stream with the provided callbacks
|
||||
*
|
||||
* @param resource $source
|
||||
* @return resource
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public static function wrap($source) {
|
||||
$context = stream_context_create(array(
|
||||
'null' => array(
|
||||
'source' => $source)
|
||||
));
|
||||
return Wrapper::wrapSource($source, $context, 'null', '\Icewind\Streams\NullWrapper');
|
||||
return self::wrapSource($source);
|
||||
}
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
$this->loadContext('null');
|
||||
$this->loadContext();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function dir_opendir($path, $options) {
|
||||
$this->loadContext('null');
|
||||
$this->loadContext();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class Path {
|
|||
* @param string $class
|
||||
* @param array $contextOptions
|
||||
*/
|
||||
public function __construct($class, $contextOptions = array()) {
|
||||
public function __construct($class, $contextOptions = []) {
|
||||
$this->class = $class;
|
||||
$this->contextOptions = $contextOptions;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class Path {
|
|||
*/
|
||||
protected function appendDefaultContent($values) {
|
||||
if (!is_array(current($values))) {
|
||||
$values = array($this->getProtocol() => $values);
|
||||
$values = [$this->getProtocol() => $values];
|
||||
}
|
||||
$context = stream_context_get_default();
|
||||
$defaults = stream_context_get_options($context);
|
||||
|
|
|
@ -16,10 +16,8 @@ class PathWrapper extends NullWrapper {
|
|||
* @return Path|string
|
||||
*/
|
||||
public static function getPath($source) {
|
||||
return new Path(__CLASS__, [
|
||||
'null' => [
|
||||
'source' => $source
|
||||
]
|
||||
return new Path(NullWrapper::class, [
|
||||
NullWrapper::getProtocol() => ['source' => $source]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Icewind\Streams;
|
||||
|
||||
/**
|
||||
* Wrapper that calculates the hash on the stream on read
|
||||
*
|
||||
* The stream and hash should be passed in when wrapping the stream.
|
||||
* On close the callback will be called with the calculated checksum.
|
||||
*
|
||||
* For supported hashes see: http://php.net/manual/en/function.hash-algos.php
|
||||
*/
|
||||
class ReadHashWrapper extends HashWrapper {
|
||||
public function stream_read($count) {
|
||||
$data = parent::stream_read($count);
|
||||
$this->updateHash($data);
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -11,25 +11,8 @@ namespace Icewind\Streams;
|
|||
* Wrapper that retries reads/writes to remote streams that dont deliver/recieve all requested data at once
|
||||
*/
|
||||
class RetryWrapper extends Wrapper {
|
||||
|
||||
/**
|
||||
* Wraps a stream with the provided callbacks
|
||||
*
|
||||
* @param resource $source
|
||||
* @return resource
|
||||
*/
|
||||
public static function wrap($source) {
|
||||
$context = stream_context_create(array(
|
||||
'retry' => array(
|
||||
'source' => $source
|
||||
)
|
||||
));
|
||||
return Wrapper::wrapSource($source, $context, 'retry', '\Icewind\Streams\RetryWrapper');
|
||||
}
|
||||
|
||||
protected function open() {
|
||||
$this->loadContext('retry');
|
||||
return true;
|
||||
return self::wrapSource($source);
|
||||
}
|
||||
|
||||
public function dir_opendir($path, $options) {
|
||||
|
@ -37,7 +20,8 @@ class RetryWrapper extends Wrapper {
|
|||
}
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
return $this->open();
|
||||
$this->loadContext();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_read($count) {
|
||||
|
|
|
@ -25,21 +25,8 @@ class SeekableWrapper extends Wrapper {
|
|||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Wraps a stream to make it seekable
|
||||
*
|
||||
* @param resource $source
|
||||
* @return resource
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public static function wrap($source) {
|
||||
$context = stream_context_create(array(
|
||||
'callback' => array(
|
||||
'source' => $source
|
||||
)
|
||||
));
|
||||
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\SeekableWrapper');
|
||||
return self::wrapSource($source);
|
||||
}
|
||||
|
||||
public function dir_opendir($path, $options) {
|
||||
|
@ -47,8 +34,12 @@ class SeekableWrapper extends Wrapper {
|
|||
}
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
$this->loadContext('callback');
|
||||
$this->cache = fopen('php://temp', 'w+');
|
||||
$this->loadContext();
|
||||
$cache = fopen('php://temp', 'w+');
|
||||
if ($cache === false) {
|
||||
return false;
|
||||
}
|
||||
$this->cache = $cache;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -72,7 +63,7 @@ class SeekableWrapper extends Wrapper {
|
|||
public function stream_seek($offset, $whence = SEEK_SET) {
|
||||
if ($whence === SEEK_SET) {
|
||||
$target = $offset;
|
||||
} else if ($whence === SEEK_CUR) {
|
||||
} elseif ($whence === SEEK_CUR) {
|
||||
$current = ftell($this->cache);
|
||||
$target = $current + $offset;
|
||||
} else {
|
||||
|
|
|
@ -22,7 +22,7 @@ interface Url {
|
|||
* @param string $path
|
||||
* @param string $mode
|
||||
* @param int $options
|
||||
* @param string &$opened_path
|
||||
* @param string $opened_path
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_open($path, $mode, $options, &$opened_path);
|
||||
|
@ -50,7 +50,7 @@ interface Url {
|
|||
public function rmdir($path, $options);
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($path);
|
||||
|
@ -58,7 +58,7 @@ interface Url {
|
|||
/**
|
||||
* @param string $path
|
||||
* @param int $flags
|
||||
* @return array
|
||||
* @return array|false
|
||||
*/
|
||||
public function url_stat($path, $flags);
|
||||
}
|
||||
|
|
|
@ -47,11 +47,18 @@ class UrlCallback extends Wrapper implements Url {
|
|||
* @return \Icewind\Streams\Path
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function wrap($source, $fopen = null, $opendir = null, $mkdir = null, $rename = null, $rmdir = null,
|
||||
$unlink = null, $stat = null) {
|
||||
$options = array(
|
||||
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,
|
||||
|
@ -60,11 +67,10 @@ class UrlCallback extends Wrapper implements Url {
|
|||
'rmdir' => $rmdir,
|
||||
'unlink' => $unlink,
|
||||
'stat' => $stat
|
||||
);
|
||||
return new Path('\Icewind\Streams\UrlCallBack', $options);
|
||||
]);
|
||||
}
|
||||
|
||||
protected function loadContext($url) {
|
||||
protected function loadUrlContext($url) {
|
||||
list($protocol) = explode('://', $url);
|
||||
$options = stream_context_get_options($this->context);
|
||||
return $options[$protocol];
|
||||
|
@ -77,40 +83,48 @@ class UrlCallback extends Wrapper implements Url {
|
|||
}
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
$context = $this->loadContext($path);
|
||||
$context = $this->loadUrlContext($path);
|
||||
$this->callCallBack($context, 'fopen');
|
||||
$this->setSourceStream(fopen($context['source'], $mode));
|
||||
$source = fopen($context['source'], $mode);
|
||||
if ($source === false) {
|
||||
return false;
|
||||
}
|
||||
$this->setSourceStream($source);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function dir_opendir($path, $options) {
|
||||
$context = $this->loadContext($path);
|
||||
$context = $this->loadUrlContext($path);
|
||||
$this->callCallBack($context, 'opendir');
|
||||
$this->setSourceStream(opendir($context['source']));
|
||||
$source = opendir($context['source']);
|
||||
if ($source === false) {
|
||||
return false;
|
||||
}
|
||||
$this->setSourceStream($source);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function mkdir($path, $mode, $options) {
|
||||
$context = $this->loadContext($path);
|
||||
$context = $this->loadUrlContext($path);
|
||||
$this->callCallBack($context, 'mkdir');
|
||||
return mkdir($context['source'], $mode, $options & STREAM_MKDIR_RECURSIVE);
|
||||
return mkdir($context['source'], $mode, ($options & STREAM_MKDIR_RECURSIVE) > 0);
|
||||
}
|
||||
|
||||
public function rmdir($path, $options) {
|
||||
$context = $this->loadContext($path);
|
||||
$context = $this->loadUrlContext($path);
|
||||
$this->callCallBack($context, 'rmdir');
|
||||
return rmdir($context['source']);
|
||||
}
|
||||
|
||||
public function rename($source, $target) {
|
||||
$context = $this->loadContext($source);
|
||||
$context = $this->loadUrlContext($source);
|
||||
$this->callCallBack($context, 'rename');
|
||||
list(, $target) = explode('://', $target);
|
||||
return rename($context['source'], $target);
|
||||
}
|
||||
|
||||
public function unlink($path) {
|
||||
$context = $this->loadContext($path);
|
||||
$context = $this->loadUrlContext($path);
|
||||
$this->callCallBack($context, 'unlink');
|
||||
return unlink($context['source']);
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace Icewind\Streams;
|
|||
*
|
||||
* This wrapper itself doesn't implement any functionality but is just a base class for other wrappers to extend
|
||||
*/
|
||||
abstract class Wrapper implements File, Directory {
|
||||
abstract class Wrapper extends WrapperHandler implements File, Directory {
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
|
@ -25,44 +25,15 @@ abstract class Wrapper implements File, Directory {
|
|||
*/
|
||||
protected $source;
|
||||
|
||||
protected static function wrapSource($source, $context, $protocol, $class) {
|
||||
if (!is_resource($source)) {
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
try {
|
||||
stream_wrapper_register($protocol, $class);
|
||||
if (self::isDirectoryHandle($source)) {
|
||||
$wrapped = opendir($protocol . '://', $context);
|
||||
} else {
|
||||
$wrapped = fopen($protocol . '://', 'r+', false, $context);
|
||||
}
|
||||
} catch (\BadMethodCallException $e) {
|
||||
stream_wrapper_unregister($protocol);
|
||||
throw $e;
|
||||
}
|
||||
stream_wrapper_unregister($protocol);
|
||||
return $wrapped;
|
||||
}
|
||||
|
||||
protected static function isDirectoryHandle($resource) {
|
||||
$meta = stream_get_meta_data($resource);
|
||||
return $meta['stream_type'] == 'dir';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the source from the stream context and return the context options
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @param resource $source
|
||||
*/
|
||||
protected function loadContext($name) {
|
||||
$context = stream_context_get_options($this->context);
|
||||
if (isset($context[$name])) {
|
||||
$context = $context[$name];
|
||||
} else {
|
||||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
|
||||
protected function setSourceStream($source) {
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
protected function loadContext($name = null) {
|
||||
$context = parent::loadContext($name);
|
||||
if (isset($context['source']) and is_resource($context['source'])) {
|
||||
$this->setSourceStream($context['source']);
|
||||
} else {
|
||||
|
@ -71,13 +42,6 @@ abstract class Wrapper implements File, Directory {
|
|||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $source
|
||||
*/
|
||||
protected function setSourceStream($source) {
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence = SEEK_SET) {
|
||||
$result = fseek($this->source, $offset, $whence);
|
||||
return $result == 0 ? true : false;
|
||||
|
@ -98,14 +62,13 @@ abstract class Wrapper implements File, Directory {
|
|||
public function stream_set_option($option, $arg1, $arg2) {
|
||||
switch ($option) {
|
||||
case STREAM_OPTION_BLOCKING:
|
||||
stream_set_blocking($this->source, $arg1);
|
||||
break;
|
||||
return stream_set_blocking($this->source, (bool)$arg1);
|
||||
case STREAM_OPTION_READ_TIMEOUT:
|
||||
stream_set_timeout($this->source, $arg1, $arg2);
|
||||
break;
|
||||
return stream_set_timeout($this->source, $arg1, $arg2);
|
||||
case STREAM_OPTION_WRITE_BUFFER:
|
||||
stream_set_write_buffer($this->source, $arg1);
|
||||
return stream_set_write_buffer($this->source, $arg1) === 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_truncate($size) {
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Icewind\Streams;
|
||||
|
||||
class WrapperHandler {
|
||||
/** @var resource $context */
|
||||
protected $context;
|
||||
|
||||
const NO_SOURCE_DIR = 1;
|
||||
|
||||
/**
|
||||
* get the protocol name that is generated for the class
|
||||
* @param string|null $class
|
||||
* @return string
|
||||
*/
|
||||
public static function getProtocol($class = null) {
|
||||
if ($class === null) {
|
||||
$class = static::class;
|
||||
}
|
||||
|
||||
$parts = explode('\\', $class);
|
||||
return strtolower(array_pop($parts));
|
||||
}
|
||||
|
||||
private static function buildContext($protocol, $context, $source) {
|
||||
if (is_array($context)) {
|
||||
$context['source'] = $source;
|
||||
return stream_context_create([$protocol => $context]);
|
||||
} else {
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|int $source
|
||||
* @param resource|array $context
|
||||
* @param string|null $protocol deprecated, protocol is now automatically generated
|
||||
* @param string|null $class deprecated, class is now automatically generated
|
||||
* @return bool|resource
|
||||
*/
|
||||
protected static function wrapSource($source, $context = [], $protocol = null, $class = null) {
|
||||
if ($class === null) {
|
||||
$class = static::class;
|
||||
}
|
||||
|
||||
if ($protocol === null) {
|
||||
$protocol = self::getProtocol($class);
|
||||
}
|
||||
|
||||
$context = self::buildContext($protocol, $context, $source);
|
||||
try {
|
||||
stream_wrapper_register($protocol, $class);
|
||||
if (self::isDirectoryHandle($source)) {
|
||||
return opendir($protocol . '://', $context);
|
||||
} else {
|
||||
return fopen($protocol . '://', 'r+', false, $context);
|
||||
}
|
||||
} finally {
|
||||
stream_wrapper_unregister($protocol);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function isDirectoryHandle($resource) {
|
||||
if ($resource === self::NO_SOURCE_DIR) {
|
||||
return true;
|
||||
}
|
||||
if (!is_resource($resource)) {
|
||||
throw new \BadMethodCallException('Invalid stream source');
|
||||
}
|
||||
$meta = stream_get_meta_data($resource);
|
||||
return $meta['stream_type'] === 'dir' || $meta['stream_type'] === 'user-space-dir';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the source from the stream context and return the context options
|
||||
*
|
||||
* @param string|null $name if not set, the generated protocol name is used
|
||||
* @return array
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
protected function loadContext($name = null) {
|
||||
if ($name === null) {
|
||||
$parts = explode('\\', static::class);
|
||||
$name = strtolower(array_pop($parts));
|
||||
}
|
||||
|
||||
$context = stream_context_get_options($this->context);
|
||||
if (isset($context[$name])) {
|
||||
$context = $context[$name];
|
||||
} else {
|
||||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
|
||||
}
|
||||
return $context;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Icewind\Streams;
|
||||
|
||||
/**
|
||||
* Wrapper that calculates the hash on the stream on write
|
||||
*
|
||||
* The stream and hash should be passed in when wrapping the stream.
|
||||
* On close the callback will be called with the calculated checksum.
|
||||
*
|
||||
* For supported hashes see: http://php.net/manual/en/function.hash-algos.php
|
||||
*/
|
||||
class WriteHashWrapper extends HashWrapper {
|
||||
public function stream_write($data) {
|
||||
$this->updateHash($data);
|
||||
return parent::stream_write($data);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue