migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,13 @@
example.php
.editorconfig
icewind/smb/tests
icewind/smb/install_libsmbclient.sh
icewind/smb/Makefile
icewind/smb/.travis.yml
icewind/smb/.scrutinizer.yml
icewind/smb/example-apache-kerberos.php
icewind/smb/codecov.yml
icewind/streams/tests
.github
.php_cs*
psalm.xml
+25
View File
@@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3::getLoader();
+14
View File
@@ -0,0 +1,14 @@
{
"name": "files_external/3rdparty",
"description": "3rdparty components for files_external",
"license": "MIT",
"config": {
"vendor-dir": ".",
"optimize-autoloader": true,
"classmap-authoritative": true
},
"require": {
"icewind/smb": "3.5.4",
"icewind/streams": "0.7.7"
}
}
+111
View File
@@ -0,0 +1,111 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1419e286d2372dfbce44dd73ddbab2ff",
"packages": [
{
"name": "icewind/smb",
"version": "v3.5.4",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
"reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
"reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
"shasum": ""
},
"require": {
"icewind/streams": ">=0.7.3",
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
"phpstan/phpstan": "^0.12.57",
"phpunit/phpunit": "^8.5|^9.3.8",
"psalm/phar": "^4.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Icewind\\SMB\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"description": "php wrapper for smbclient and libsmbclient-php",
"support": {
"issues": "https://github.com/icewind1991/SMB/issues",
"source": "https://github.com/icewind1991/SMB/tree/v3.5.4"
},
"time": "2022-05-30T15:18:19+00:00"
},
{
"name": "icewind/streams",
"version": "v0.7.7",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/Streams.git",
"reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/64200fd7cfcc7f550c3c695c48d8fd8bba97fecb",
"reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^9"
},
"type": "library",
"autoload": {
"psr-4": {
"Icewind\\Streams\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"description": "A set of generic stream wrappers",
"support": {
"issues": "https://github.com/icewind1991/Streams/issues",
"source": "https://github.com/icewind1991/Streams/tree/v0.7.7"
},
"time": "2023-03-16T14:52:25+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.3.0"
}
@@ -0,0 +1,585 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
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;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}
@@ -0,0 +1,359 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
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)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
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');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
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');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
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');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
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');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
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')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}
@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,92 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$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',
'Icewind\\SMB\\AnonymousAuth' => $vendorDir . '/icewind/smb/src/AnonymousAuth.php',
'Icewind\\SMB\\BasicAuth' => $vendorDir . '/icewind/smb/src/BasicAuth.php',
'Icewind\\SMB\\Change' => $vendorDir . '/icewind/smb/src/Change.php',
'Icewind\\SMB\\Exception\\AccessDeniedException' => $vendorDir . '/icewind/smb/src/Exception/AccessDeniedException.php',
'Icewind\\SMB\\Exception\\AlreadyExistsException' => $vendorDir . '/icewind/smb/src/Exception/AlreadyExistsException.php',
'Icewind\\SMB\\Exception\\AuthenticationException' => $vendorDir . '/icewind/smb/src/Exception/AuthenticationException.php',
'Icewind\\SMB\\Exception\\ConnectException' => $vendorDir . '/icewind/smb/src/Exception/ConnectException.php',
'Icewind\\SMB\\Exception\\ConnectionAbortedException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionAbortedException.php',
'Icewind\\SMB\\Exception\\ConnectionException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionException.php',
'Icewind\\SMB\\Exception\\ConnectionRefusedException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionRefusedException.php',
'Icewind\\SMB\\Exception\\ConnectionResetException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionResetException.php',
'Icewind\\SMB\\Exception\\DependencyException' => $vendorDir . '/icewind/smb/src/Exception/DependencyException.php',
'Icewind\\SMB\\Exception\\Exception' => $vendorDir . '/icewind/smb/src/Exception/Exception.php',
'Icewind\\SMB\\Exception\\FileInUseException' => $vendorDir . '/icewind/smb/src/Exception/FileInUseException.php',
'Icewind\\SMB\\Exception\\ForbiddenException' => $vendorDir . '/icewind/smb/src/Exception/ForbiddenException.php',
'Icewind\\SMB\\Exception\\HostDownException' => $vendorDir . '/icewind/smb/src/Exception/HostDownException.php',
'Icewind\\SMB\\Exception\\InvalidArgumentException' => $vendorDir . '/icewind/smb/src/Exception/InvalidArgumentException.php',
'Icewind\\SMB\\Exception\\InvalidHostException' => $vendorDir . '/icewind/smb/src/Exception/InvalidHostException.php',
'Icewind\\SMB\\Exception\\InvalidParameterException' => $vendorDir . '/icewind/smb/src/Exception/InvalidParameterException.php',
'Icewind\\SMB\\Exception\\InvalidPathException' => $vendorDir . '/icewind/smb/src/Exception/InvalidPathException.php',
'Icewind\\SMB\\Exception\\InvalidRequestException' => $vendorDir . '/icewind/smb/src/Exception/InvalidRequestException.php',
'Icewind\\SMB\\Exception\\InvalidResourceException' => $vendorDir . '/icewind/smb/src/Exception/InvalidResourceException.php',
'Icewind\\SMB\\Exception\\InvalidTypeException' => $vendorDir . '/icewind/smb/src/Exception/InvalidTypeException.php',
'Icewind\\SMB\\Exception\\NoLoginServerException' => $vendorDir . '/icewind/smb/src/Exception/NoLoginServerException.php',
'Icewind\\SMB\\Exception\\NoRouteToHostException' => $vendorDir . '/icewind/smb/src/Exception/NoRouteToHostException.php',
'Icewind\\SMB\\Exception\\NotEmptyException' => $vendorDir . '/icewind/smb/src/Exception/NotEmptyException.php',
'Icewind\\SMB\\Exception\\NotFoundException' => $vendorDir . '/icewind/smb/src/Exception/NotFoundException.php',
'Icewind\\SMB\\Exception\\OutOfSpaceException' => $vendorDir . '/icewind/smb/src/Exception/OutOfSpaceException.php',
'Icewind\\SMB\\Exception\\RevisionMismatchException' => $vendorDir . '/icewind/smb/src/Exception/RevisionMismatchException.php',
'Icewind\\SMB\\Exception\\TimedOutException' => $vendorDir . '/icewind/smb/src/Exception/TimedOutException.php',
'Icewind\\SMB\\IAuth' => $vendorDir . '/icewind/smb/src/IAuth.php',
'Icewind\\SMB\\IFileInfo' => $vendorDir . '/icewind/smb/src/IFileInfo.php',
'Icewind\\SMB\\INotifyHandler' => $vendorDir . '/icewind/smb/src/INotifyHandler.php',
'Icewind\\SMB\\IOptions' => $vendorDir . '/icewind/smb/src/IOptions.php',
'Icewind\\SMB\\IServer' => $vendorDir . '/icewind/smb/src/IServer.php',
'Icewind\\SMB\\IShare' => $vendorDir . '/icewind/smb/src/IShare.php',
'Icewind\\SMB\\ISystem' => $vendorDir . '/icewind/smb/src/ISystem.php',
'Icewind\\SMB\\ITimeZoneProvider' => $vendorDir . '/icewind/smb/src/ITimeZoneProvider.php',
'Icewind\\SMB\\KerberosApacheAuth' => $vendorDir . '/icewind/smb/src/KerberosApacheAuth.php',
'Icewind\\SMB\\KerberosAuth' => $vendorDir . '/icewind/smb/src/KerberosAuth.php',
'Icewind\\SMB\\Native\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/Native/NativeFileInfo.php',
'Icewind\\SMB\\Native\\NativeReadStream' => $vendorDir . '/icewind/smb/src/Native/NativeReadStream.php',
'Icewind\\SMB\\Native\\NativeServer' => $vendorDir . '/icewind/smb/src/Native/NativeServer.php',
'Icewind\\SMB\\Native\\NativeShare' => $vendorDir . '/icewind/smb/src/Native/NativeShare.php',
'Icewind\\SMB\\Native\\NativeState' => $vendorDir . '/icewind/smb/src/Native/NativeState.php',
'Icewind\\SMB\\Native\\NativeStream' => $vendorDir . '/icewind/smb/src/Native/NativeStream.php',
'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',
'Icewind\\SMB\\Wrapped\\ErrorCodes' => $vendorDir . '/icewind/smb/src/Wrapped/ErrorCodes.php',
'Icewind\\SMB\\Wrapped\\FileInfo' => $vendorDir . '/icewind/smb/src/Wrapped/FileInfo.php',
'Icewind\\SMB\\Wrapped\\NotifyHandler' => $vendorDir . '/icewind/smb/src/Wrapped/NotifyHandler.php',
'Icewind\\SMB\\Wrapped\\Parser' => $vendorDir . '/icewind/smb/src/Wrapped/Parser.php',
'Icewind\\SMB\\Wrapped\\RawConnection' => $vendorDir . '/icewind/smb/src/Wrapped/RawConnection.php',
'Icewind\\SMB\\Wrapped\\Server' => $vendorDir . '/icewind/smb/src/Wrapped/Server.php',
'Icewind\\SMB\\Wrapped\\Share' => $vendorDir . '/icewind/smb/src/Wrapped/Share.php',
'Icewind\\Streams\\CallbackWrapper' => $vendorDir . '/icewind/streams/src/CallbackWrapper.php',
'Icewind\\Streams\\CountWrapper' => $vendorDir . '/icewind/streams/src/CountWrapper.php',
'Icewind\\Streams\\Directory' => $vendorDir . '/icewind/streams/src/Directory.php',
'Icewind\\Streams\\DirectoryFilter' => $vendorDir . '/icewind/streams/src/DirectoryFilter.php',
'Icewind\\Streams\\DirectoryWrapper' => $vendorDir . '/icewind/streams/src/DirectoryWrapper.php',
'Icewind\\Streams\\File' => $vendorDir . '/icewind/streams/src/File.php',
'Icewind\\Streams\\HashWrapper' => $vendorDir . '/icewind/streams/src/HashWrapper.php',
'Icewind\\Streams\\IteratorDirectory' => $vendorDir . '/icewind/streams/src/IteratorDirectory.php',
'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php',
'Icewind\\Streams\\Path' => $vendorDir . '/icewind/streams/src/Path.php',
'Icewind\\Streams\\PathWrapper' => $vendorDir . '/icewind/streams/src/PathWrapper.php',
'Icewind\\Streams\\ReadHashWrapper' => $vendorDir . '/icewind/streams/src/ReadHashWrapper.php',
'Icewind\\Streams\\RetryWrapper' => $vendorDir . '/icewind/streams/src/RetryWrapper.php',
'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php',
'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php',
'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallback.php',
'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.php',
'Icewind\\Streams\\WrapperHandler' => $vendorDir . '/icewind/streams/src/WrapperHandler.php',
'Icewind\\Streams\\WriteHashWrapper' => $vendorDir . '/icewind/streams/src/WriteHashWrapper.php',
);
@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = $vendorDir;
return array(
);
@@ -0,0 +1,11 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = $vendorDir;
return array(
'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'),
'Icewind\\SMB\\' => array($vendorDir . '/icewind/smb/src'),
);
@@ -0,0 +1,39 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
return $loader;
}
}
@@ -0,0 +1,123 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
{
public static $prefixLengthsPsr4 = array (
'I' =>
array (
'Icewind\\Streams\\' => 16,
'Icewind\\SMB\\' => 12,
),
);
public static $prefixDirsPsr4 = array (
'Icewind\\Streams\\' =>
array (
0 => __DIR__ . '/..' . '/icewind/streams/src',
),
'Icewind\\SMB\\' =>
array (
0 => __DIR__ . '/..' . '/icewind/smb/src',
),
);
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',
'Icewind\\SMB\\AnonymousAuth' => __DIR__ . '/..' . '/icewind/smb/src/AnonymousAuth.php',
'Icewind\\SMB\\BasicAuth' => __DIR__ . '/..' . '/icewind/smb/src/BasicAuth.php',
'Icewind\\SMB\\Change' => __DIR__ . '/..' . '/icewind/smb/src/Change.php',
'Icewind\\SMB\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/AccessDeniedException.php',
'Icewind\\SMB\\Exception\\AlreadyExistsException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/AlreadyExistsException.php',
'Icewind\\SMB\\Exception\\AuthenticationException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/AuthenticationException.php',
'Icewind\\SMB\\Exception\\ConnectException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectException.php',
'Icewind\\SMB\\Exception\\ConnectionAbortedException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionAbortedException.php',
'Icewind\\SMB\\Exception\\ConnectionException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionException.php',
'Icewind\\SMB\\Exception\\ConnectionRefusedException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionRefusedException.php',
'Icewind\\SMB\\Exception\\ConnectionResetException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionResetException.php',
'Icewind\\SMB\\Exception\\DependencyException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/DependencyException.php',
'Icewind\\SMB\\Exception\\Exception' => __DIR__ . '/..' . '/icewind/smb/src/Exception/Exception.php',
'Icewind\\SMB\\Exception\\FileInUseException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/FileInUseException.php',
'Icewind\\SMB\\Exception\\ForbiddenException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ForbiddenException.php',
'Icewind\\SMB\\Exception\\HostDownException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/HostDownException.php',
'Icewind\\SMB\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidArgumentException.php',
'Icewind\\SMB\\Exception\\InvalidHostException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidHostException.php',
'Icewind\\SMB\\Exception\\InvalidParameterException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidParameterException.php',
'Icewind\\SMB\\Exception\\InvalidPathException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidPathException.php',
'Icewind\\SMB\\Exception\\InvalidRequestException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidRequestException.php',
'Icewind\\SMB\\Exception\\InvalidResourceException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidResourceException.php',
'Icewind\\SMB\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidTypeException.php',
'Icewind\\SMB\\Exception\\NoLoginServerException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NoLoginServerException.php',
'Icewind\\SMB\\Exception\\NoRouteToHostException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NoRouteToHostException.php',
'Icewind\\SMB\\Exception\\NotEmptyException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NotEmptyException.php',
'Icewind\\SMB\\Exception\\NotFoundException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NotFoundException.php',
'Icewind\\SMB\\Exception\\OutOfSpaceException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/OutOfSpaceException.php',
'Icewind\\SMB\\Exception\\RevisionMismatchException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/RevisionMismatchException.php',
'Icewind\\SMB\\Exception\\TimedOutException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/TimedOutException.php',
'Icewind\\SMB\\IAuth' => __DIR__ . '/..' . '/icewind/smb/src/IAuth.php',
'Icewind\\SMB\\IFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/IFileInfo.php',
'Icewind\\SMB\\INotifyHandler' => __DIR__ . '/..' . '/icewind/smb/src/INotifyHandler.php',
'Icewind\\SMB\\IOptions' => __DIR__ . '/..' . '/icewind/smb/src/IOptions.php',
'Icewind\\SMB\\IServer' => __DIR__ . '/..' . '/icewind/smb/src/IServer.php',
'Icewind\\SMB\\IShare' => __DIR__ . '/..' . '/icewind/smb/src/IShare.php',
'Icewind\\SMB\\ISystem' => __DIR__ . '/..' . '/icewind/smb/src/ISystem.php',
'Icewind\\SMB\\ITimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/ITimeZoneProvider.php',
'Icewind\\SMB\\KerberosApacheAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosApacheAuth.php',
'Icewind\\SMB\\KerberosAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosAuth.php',
'Icewind\\SMB\\Native\\NativeFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeFileInfo.php',
'Icewind\\SMB\\Native\\NativeReadStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeReadStream.php',
'Icewind\\SMB\\Native\\NativeServer' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeServer.php',
'Icewind\\SMB\\Native\\NativeShare' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeShare.php',
'Icewind\\SMB\\Native\\NativeState' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeState.php',
'Icewind\\SMB\\Native\\NativeStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeStream.php',
'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',
'Icewind\\SMB\\Wrapped\\ErrorCodes' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/ErrorCodes.php',
'Icewind\\SMB\\Wrapped\\FileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/FileInfo.php',
'Icewind\\SMB\\Wrapped\\NotifyHandler' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/NotifyHandler.php',
'Icewind\\SMB\\Wrapped\\Parser' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Parser.php',
'Icewind\\SMB\\Wrapped\\RawConnection' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/RawConnection.php',
'Icewind\\SMB\\Wrapped\\Server' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Server.php',
'Icewind\\SMB\\Wrapped\\Share' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Share.php',
'Icewind\\Streams\\CallbackWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CallbackWrapper.php',
'Icewind\\Streams\\CountWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CountWrapper.php',
'Icewind\\Streams\\Directory' => __DIR__ . '/..' . '/icewind/streams/src/Directory.php',
'Icewind\\Streams\\DirectoryFilter' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryFilter.php',
'Icewind\\Streams\\DirectoryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryWrapper.php',
'Icewind\\Streams\\File' => __DIR__ . '/..' . '/icewind/streams/src/File.php',
'Icewind\\Streams\\HashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/HashWrapper.php',
'Icewind\\Streams\\IteratorDirectory' => __DIR__ . '/..' . '/icewind/streams/src/IteratorDirectory.php',
'Icewind\\Streams\\NullWrapper' => __DIR__ . '/..' . '/icewind/streams/src/NullWrapper.php',
'Icewind\\Streams\\Path' => __DIR__ . '/..' . '/icewind/streams/src/Path.php',
'Icewind\\Streams\\PathWrapper' => __DIR__ . '/..' . '/icewind/streams/src/PathWrapper.php',
'Icewind\\Streams\\ReadHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/ReadHashWrapper.php',
'Icewind\\Streams\\RetryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/RetryWrapper.php',
'Icewind\\Streams\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/src/SeekableWrapper.php',
'Icewind\\Streams\\Url' => __DIR__ . '/..' . '/icewind/streams/src/Url.php',
'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallback.php',
'Icewind\\Streams\\Wrapper' => __DIR__ . '/..' . '/icewind/streams/src/Wrapper.php',
'Icewind\\Streams\\WrapperHandler' => __DIR__ . '/..' . '/icewind/streams/src/WrapperHandler.php',
'Icewind\\Streams\\WriteHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/WriteHashWrapper.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::$classMap;
}, null, ClassLoader::class);
}
}
@@ -0,0 +1,104 @@
{
"packages": [
{
"name": "icewind/smb",
"version": "v3.5.4",
"version_normalized": "3.5.4.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
"reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
"reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
"shasum": ""
},
"require": {
"icewind/streams": ">=0.7.3",
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
"phpstan/phpstan": "^0.12.57",
"phpunit/phpunit": "^8.5|^9.3.8",
"psalm/phar": "^4.3"
},
"time": "2022-05-30T15:18:19+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Icewind\\SMB\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"description": "php wrapper for smbclient and libsmbclient-php",
"support": {
"issues": "https://github.com/icewind1991/SMB/issues",
"source": "https://github.com/icewind1991/SMB/tree/v3.5.4"
},
"install-path": "../icewind/smb"
},
{
"name": "icewind/streams",
"version": "v0.7.7",
"version_normalized": "0.7.7.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/Streams.git",
"reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/64200fd7cfcc7f550c3c695c48d8fd8bba97fecb",
"reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^9"
},
"time": "2023-03-16T14:52:25+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Icewind\\Streams\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"description": "A set of generic stream wrappers",
"support": {
"issues": "https://github.com/icewind1991/Streams/issues",
"source": "https://github.com/icewind1991/Streams/tree/v0.7.7"
},
"install-path": "../icewind/streams"
}
],
"dev": true,
"dev-package-names": []
}
@@ -0,0 +1,41 @@
<?php return array(
'root' => array(
'name' => 'files_external/3rdparty',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '979f4033ca1cd0be7f255f028d4cc637a216440d',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'files_external/3rdparty' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '979f4033ca1cd0be7f255f028d4cc637a216440d',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
'dev_requirement' => false,
),
'icewind/smb' => array(
'pretty_version' => 'v3.5.4',
'version' => '3.5.4.0',
'reference' => '76995aa11c14e39bccd0f2370ed63b2f8f623a6d',
'type' => 'library',
'install_path' => __DIR__ . '/../icewind/smb',
'aliases' => array(),
'dev_requirement' => false,
),
'icewind/streams' => array(
'pretty_version' => 'v0.7.7',
'version' => '0.7.7.0',
'reference' => '64200fd7cfcc7f550c3c695c48d8fd8bba97fecb',
'type' => 'library',
'install_path' => __DIR__ . '/../icewind/streams',
'aliases' => array(),
'dev_requirement' => false,
),
),
);
@@ -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
);
}
@@ -0,0 +1,7 @@
.idea
vendor
composer.lock
.php_cs.cache
listen.php
test.php
*.cache
@@ -0,0 +1,19 @@
Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,187 @@
SMB
===
[![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)
- Reuses a single `smbclient` instance for multiple requests
- Doesn't leak the password to the process list
- Simple 1-on-1 mapping of SMB commands
- A stream-based api to remove the need for temporary files
- Support for using libsmbclient directly trough [`libsmbclient-php`](https://github.com/eduardok/libsmbclient-php)
Examples
----
### Connect to a share ###
```php
<?php
use Icewind\SMB\ServerFactory;
use Icewind\SMB\BasicAuth;
require('vendor/autoload.php');
$serverFactory = new ServerFactory();
$auth = new BasicAuth('user', 'workgroup', 'password');
$server = $serverFactory->createServer('localhost', $auth);
$share = $server->getShare('test');
```
The server factory will automatically pick between the `smbclient` and `libsmbclient-php`
based backend depending on what is available.
### Using anonymous authentication ###
```php
$serverFactory = new ServerFactory();
$auth = new AnonymousAuth();
$server = $serverFactory->createServer('localhost', $auth);
```
### Using kerberos authentication ###
There are two ways of using kerberos to authenticate against the smb server:
- Using a ticket from the php server
- Re-using a ticket send by the client
### Using a server ticket
Using a server ticket allows the web server to authenticate against the smb server using an existing machine account.
The ticket needs to be available in the environment of the php process.
```php
$serverFactory = new ServerFactory();
$auth = new KerberosAuth();
$server = $serverFactory->createServer('localhost', $auth);
```
### Re-using a client ticket
By re-using a client ticket you can create a single sign-on setup where the user authenticates against
the web service using kerberos. And the web server can forward that ticket to the smb server, allowing it
to act on the behalf of the user without requiring the user to enter his passord.
The setup for such a system is fairly involved and requires roughly the following this
- The web server is authenticated against kerberos with a machine account
- Delegation is enabled for the web server's machine account
- Apache is setup to perform kerberos authentication and save the ticket in it's environment
- Php has the krb5 extension installed
- The client authenticates using a ticket with forwarding enabled
```php
$serverFactory = new ServerFactory();
$auth = new KerberosApacheAuth();
$server = $serverFactory->createServer('localhost', $auth);
```
### Upload a file ###
```php
$share->put($fileToUpload, 'example.txt');
```
### Download a file ###
```php
$share->get('example.txt', $target);
```
### List shares on the remote server ###
```php
$shares = $server->listShares();
foreach ($shares as $share) {
echo $share->getName() . "\n";
}
```
### List the content of a folder ###
```php
$content = $share->dir('test');
foreach ($content as $info) {
echo $info->getName() . "\n";
echo "\tsize :" . $info->getSize() . "\n";
}
```
### Using read streams
```php
$fh = $share->read('test.txt');
echo fread($fh, 4086);
fclose($fh);
```
### Using write streams
```php
$fh = $share->write('test.txt');
fwrite($fh, 'bar');
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 exist yet. (append() is only compatible with libsmbclient-php)
```php
$fh = $share->append('test.txt');
fwrite($fh, 'bar');
fclose($fh);
```
### Using notify
```php
$share->notify('')->listen(function (\Icewind\SMB\Change $change) {
echo $change->getCode() . ': ' . $change->getPath() . "\n";
});
```
### Changing network timeouts
```php
$options = new Options();
$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 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`.
## Testing SMB
Use the following steps to check if the library can connect to your SMB share.
1. Clone this repository or download the source as [zip](https://github.com/icewind1991/SMB/archive/master.zip)
2. Make sure [composer](https://getcomposer.org/) is installed
3. Run `composer install` in the root of the repository
4. Edit `example.php` with the relevant settings for your share.
5. Run `php example.php`
If everything works correctly then the contents of the share should be outputted.
@@ -0,0 +1,37 @@
{
"name": "icewind/smb",
"description": "php wrapper for smbclient and libsmbclient-php",
"license": "MIT",
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"require": {
"php": ">=7.2",
"icewind/streams": ">=0.7.3"
},
"require-dev": {
"phpunit/phpunit": "^8.5|^9.3.8",
"friendsofphp/php-cs-fixer": "^2.16",
"phpstan/phpstan": "^0.12.57",
"psalm/phar": "^4.3"
},
"autoload": {
"psr-4": {
"Icewind\\SMB\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Icewind\\SMB\\Test\\": "tests/"
}
},
"scripts": {
"lint": "parallel-lint --exclude src --exclude vendor --exclude target --exclude build .",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm.phar"
}
}
@@ -0,0 +1,84 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 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 ACL {
const TYPE_ALLOW = 0;
const TYPE_DENY = 1;
const MASK_READ = 0x0001;
const MASK_WRITE = 0x0002;
const MASK_EXECUTE = 0x00020;
const MASK_DELETE = 0x10000;
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) {
$this->type = $type;
$this->flags = $flags;
$this->mask = $mask;
}
/**
* Check if the acl allows a specific permissions
*
* Note that this does not take inherited acls into account
*
* @param int $mask one of the ACL::MASK_* constants
* @return bool
*/
public function allows(int $mask): bool {
return $this->type === self::TYPE_ALLOW && ($this->mask & $mask) === $mask;
}
/**
* Check if the acl allows a specific permissions
*
* Note that this does not take inherited acls into account
*
* @param int $mask one of the ACL::MASK_* constants
* @return bool
*/
public function denies(int $mask): bool {
return $this->type === self::TYPE_DENY && ($this->mask & $mask) === $mask;
}
public function getType(): int {
return $this->type;
}
public function getFlags(): int {
return $this->flags;
}
public function getMask(): int {
return $this->mask;
}
}
@@ -0,0 +1,76 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
abstract class AbstractServer implements IServer {
const LOCALE = 'en_US.UTF-8';
/** @var string */
protected $host;
/** @var IAuth */
protected $auth;
/** @var ISystem */
protected $system;
/** @var ITimeZoneProvider */
protected $timezoneProvider;
/** @var IOptions */
protected $options;
/**
* @param string $host
* @param IAuth $auth
* @param ISystem $system
* @param ITimeZoneProvider $timeZoneProvider
* @param IOptions $options
*/
public function __construct(string $host, IAuth $auth, ISystem $system, ITimeZoneProvider $timeZoneProvider, IOptions $options) {
$this->host = $host;
$this->auth = $auth;
$this->system = $system;
$this->timezoneProvider = $timeZoneProvider;
$this->options = $options;
}
public function getAuth(): IAuth {
return $this->auth;
}
public function getHost(): string {
return $this->host;
}
public function getTimeZone(): string {
return $this->timezoneProvider->get($this->host);
}
public function getSystem(): ISystem {
return $this->system;
}
public function getOptions(): IOptions {
return $this->options;
}
}
@@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2015 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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"];
}
/**
* @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');
}
}
}
/**
* @param string[] $charList
*/
public function setForbiddenChars(array $charList): void {
$this->forbiddenCharacters = $charList;
}
}
@@ -0,0 +1,48 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
use Icewind\SMB\Exception\Exception;
class AnonymousAuth implements IAuth {
public function getUsername(): ?string {
return null;
}
public function getWorkgroup(): ?string {
return 'dummy';
}
public function getPassword(): ?string {
return null;
}
public function getExtraCommandLineArguments(): string {
return '-N';
}
public function setExtraSmbClientOptions($smbClientState): void {
if (smbclient_option_set($smbClientState, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, true) === false) {
throw new Exception("Failed to set smbclient options for anonymous auth");
}
}
}
@@ -0,0 +1,57 @@
<?php
/**
* @copyright Copyright (c) 2018 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 BasicAuth implements IAuth {
/** @var string */
private $username;
/** @var string|null */
private $workgroup;
/** @var string */
private $password;
public function __construct(string $username, ?string $workgroup, string $password) {
$this->username = $username;
$this->workgroup = $workgroup;
$this->password = $password;
}
public function getUsername(): ?string {
return $this->username;
}
public function getWorkgroup(): ?string {
return $this->workgroup;
}
public function getPassword(): ?string {
return $this->password;
}
public function getExtraCommandLineArguments(): string {
return ($this->workgroup) ? '-W ' . escapeshellarg($this->workgroup) : '';
}
public function setExtraSmbClientOptions($smbClientState): void {
// noop
}
}
@@ -0,0 +1,29 @@
<?php
/**
* @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*
*/
namespace Icewind\SMB;
class Change {
/** @var int */
private $code;
/** @var string */
private $path;
public function __construct(int $code, string $path) {
$this->code = $code;
$this->path = $path;
}
public function getCode(): int {
return $this->code;
}
public function getPath(): string {
return $this->path;
}
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class AccessDeniedException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class AlreadyExistsException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class AuthenticationException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class ConnectException extends Exception {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2020 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class ConnectionAbortedException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class ConnectionException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class ConnectionRefusedException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2020 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class ConnectionResetException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2016 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class DependencyException extends Exception {
}
@@ -0,0 +1,52 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
use Throwable;
/**
* @psalm-consistent-constructor
*/
class Exception extends \Exception {
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_int($error) ? $error : 0);
}
/**
* @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, ?string $path): Exception {
if (isset($exceptionMap[$error])) {
$exceptionClass = $exceptionMap[$error];
if (is_numeric($error)) {
return new $exceptionClass($path, $error);
} else {
return new $exceptionClass($path);
}
} else {
return Exception::unknown($path, $error);
}
}
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class FileInUseException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class ForbiddenException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class HostDownException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class InvalidArgumentException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class InvalidHostException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class InvalidParameterException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class InvalidPathException extends InvalidRequestException {
}
@@ -0,0 +1,30 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class InvalidRequestException extends Exception {
/**
* @var string
*/
protected $path;
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, $previous);
$this->path = $path;
}
/**
* @return string
*/
public function getPath() {
return $this->path;
}
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class InvalidResourceException extends Exception {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class InvalidTypeException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class NoLoginServerException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class NoRouteToHostException extends ConnectException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class NotEmptyException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class NotFoundException extends InvalidRequestException {
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class OutOfSpaceException extends InvalidRequestException {
}
@@ -0,0 +1,16 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
use Throwable;
class RevisionMismatchException extends Exception {
public function __construct(string $message = 'Protocol version mismatch', int $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}
@@ -0,0 +1,11 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Exception;
class TimedOutException extends ConnectException {
}
@@ -0,0 +1,44 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
interface IAuth {
public function getUsername(): ?string;
public function getWorkgroup(): ?string;
public function getPassword(): ?string;
/**
* Any extra command line option for smbclient that are required
*
* @return string
*/
public function getExtraCommandLineArguments(): string;
/**
* Set any extra options for libsmbclient that are required
*
* @param resource $smbClientState
*/
public function setExtraSmbClientOptions($smbClientState): void;
}
@@ -0,0 +1,46 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB;
interface IFileInfo {
/*
* Mappings of the DOS mode bits, as returned by smbc_getxattr() when the
* attribute name "system.dos_attr.mode" (or "system.dos_attr.*" or
* "system.*") is specified.
*/
const MODE_READONLY = 0x01;
const MODE_HIDDEN = 0x02;
const MODE_SYSTEM = 0x04;
const MODE_VOLUME_ID = 0x08;
const MODE_DIRECTORY = 0x10;
const MODE_ARCHIVE = 0x20;
const MODE_NORMAL = 0x80;
public function getPath(): string;
public function getName(): string;
public function getSize(): int;
public function getMTime(): int;
public function isDirectory(): bool;
public function isReadOnly(): bool;
public function isHidden(): bool;
public function isSystem(): bool;
public function isArchived(): bool;
/**
* @return ACL[]
*/
public function getAcls(): array;
}
@@ -0,0 +1,45 @@
<?php
/**
* @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*
*/
namespace Icewind\SMB;
interface INotifyHandler {
// https://msdn.microsoft.com/en-us/library/dn392331.aspx
const NOTIFY_ADDED = 1;
const NOTIFY_REMOVED = 2;
const NOTIFY_MODIFIED = 3;
const NOTIFY_RENAMED_OLD = 4;
const NOTIFY_RENAMED_NEW = 5;
const NOTIFY_ADDED_STREAM = 6;
const NOTIFY_REMOVED_STREAM = 7;
const NOTIFY_MODIFIED_STREAM = 8;
const NOTIFY_REMOVED_BY_DELETE = 9;
/**
* Get all changes detected since the start of the notify process or the last call to getChanges
*
* @return Change[]
*/
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(Change):?bool $callback
*/
public function listen(callable $callback): void;
/**
* Stop listening for changes
*
* Note that any pending changes will be discarded
*/
public function stop(): void;
}
@@ -0,0 +1,41 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
interface IOptions {
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;
}
@@ -0,0 +1,46 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
interface IServer {
public function getAuth(): IAuth;
public function getHost(): string;
/**
* @return \Icewind\SMB\IShare[]
*
* @throws \Icewind\SMB\Exception\AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidHostException
*/
public function listShares(): array;
public function getShare(string $name): IShare;
public function getTimeZone(): string;
public function getSystem(): ISystem;
public function getOptions(): IOptions;
public static function available(ISystem $system): bool;
}
@@ -0,0 +1,165 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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(): string;
/**
* Download a remote file
*
* @param string $source remove file
* @param string $target local file
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function get(string $source, string $target): bool;
/**
* Upload a local file
*
* @param string $source local file
* @param string $target remove file
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function put(string $source, string $target): bool;
/**
* Open a readable stream to a remote file
*
* @param string $source
* @return resource a read only stream with the contents of the remote file
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function read(string $source);
/**
* Open a writable stream to a remote file
* Note: This method will truncate the file to 0bytes
*
* @param string $target
* @return resource a write only stream to upload a remote file
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function write(string $target);
/**
* Open a writable stream to a remote file and set the cursor to the end of the file
*
* @param string $target
* @return resource a write only stream to upload a remote file
*
* @throws NotFoundException
* @throws InvalidTypeException
* @throws InvalidRequestException
*/
public function append(string $target);
/**
* Rename a remote file
*
* @param string $from
* @param string $to
* @return bool
*
* @throws NotFoundException
* @throws AlreadyExistsException
*/
public function rename(string $from, string $to): bool;
/**
* Delete a file on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function del(string $path): bool;
/**
* List the content of a remote folder
*
* @param string $path
* @return IFileInfo[]
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function dir(string $path): array;
/**
* @param string $path
* @return IFileInfo
*
* @throws NotFoundException
*/
public function stat(string $path): IFileInfo;
/**
* Create a folder on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws AlreadyExistsException
*/
public function mkdir(string $path): bool;
/**
* Remove a folder on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
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(string $path, int $mode);
/**
* @param string $path
* @return INotifyHandler
*/
public function notify(string $path);
/**
* Get the IServer instance for this share
*
* @return IServer
*/
public function getServer(): IServer;
}
@@ -0,0 +1,78 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
/**
* The `ISystem` interface provides a way to access system dependent information
* such as the availability and location of certain binaries.
*/
interface ISystem {
/**
* Get the path to a file descriptor of the current process
*
* @param int $num the file descriptor id
* @return string
*/
public function getFD(int $num): string;
/**
* Get the full path to the `smbclient` binary of null if the binary is not available
*
* @return string|null
*/
public function getSmbclientPath(): ?string;
/**
* Get the full path to the `net` binary of null if the binary is not available
*
* @return string|null
*/
public function getNetPath(): ?string;
/**
* Get the full path to the `smbcacls` binary of null if the binary is not available
*
* @return string|null
*/
public function getSmbcAclsPath(): ?string;
/**
* Get the full path to the `stdbuf` binary of null if the binary is not available
*
* @return string|null
*/
public function getStdBufPath(): ?string;
/**
* Get the full path to the `date` binary of null if the binary is not available
*
* @return string|null
*/
public function getDatePath(): ?string;
/**
* Whether or not the smbclient php extension is enabled
*
* @return bool
*/
public function libSmbclientAvailable(): bool;
}
@@ -0,0 +1,32 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
interface ITimeZoneProvider {
/**
* Get the timezone of the smb server
*
* @param string $host
* @return string
*/
public function get(string $host): string;
}
@@ -0,0 +1,136 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Exception\Exception;
/**
* Use existing kerberos ticket to authenticate and reuse the apache ticket cache (mod_auth_kerb)
*/
class KerberosApacheAuth extends KerberosAuth implements IAuth {
/** @var string */
private $ticketPath = "";
/** @var bool */
private $init = false;
/** @var string|false */
private $ticketName;
public function __construct() {
$this->ticketName = getenv("KRB5CCNAME");
}
/**
* Copy the ticket to a temporary location and use that ticket for authentication
*
* @return void
*/
public function copyTicket(): void {
if (!$this->checkTicket()) {
return;
}
$krb5 = new \KRB5CCache();
$krb5->open($this->ticketName);
$tmpFilename = tempnam("/tmp", "krb5cc_php_");
$tmpCacheFile = "FILE:" . $tmpFilename;
$krb5->save($tmpCacheFile);
$this->ticketPath = $tmpFilename;
$this->ticketName = $tmpCacheFile;
}
/**
* Pass the ticket to smbclient by memory instead of path
*
* @return void
*/
public function passTicketFromMemory(): void {
if (!$this->checkTicket()) {
return;
}
$krb5 = new \KRB5CCache();
$krb5->open($this->ticketName);
$this->ticketName = (string)$krb5->getName();
}
/**
* Check if a valid kerberos ticket is present
*
* @return bool
* @psalm-assert-if-true string $this->ticketName
*/
public function checkTicket(): bool {
//read apache kerberos ticket cache
if (!$this->ticketName) {
return false;
}
$krb5 = new \KRB5CCache();
$krb5->open($this->ticketName);
/** @psalm-suppress MixedArgument */
return count($krb5->getEntries()) > 0;
}
private function init(): void {
if ($this->init) {
return;
}
$this->init = true;
// inspired by https://git.typo3.org/TYPO3CMS/Extensions/fal_cifs.git
if (!extension_loaded("krb5")) {
// https://pecl.php.net/package/krb5
throw new DependencyException('Ensure php-krb5 is installed.');
}
//read apache kerberos ticket cache
if (!$this->checkTicket()) {
throw new Exception('No kerberos ticket cache environment variable (KRB5CCNAME) found.');
}
// note that even if the ticketname is the value we got from `getenv("KRB5CCNAME")` we still need to set the env variable ourselves
// this is because `getenv` also reads the variables passed from the SAPI (apache-php) and we need to set the variable in the OS's env
putenv("KRB5CCNAME=" . $this->ticketName);
}
public function getExtraCommandLineArguments(): string {
$this->init();
return parent::getExtraCommandLineArguments();
}
public function setExtraSmbClientOptions($smbClientState): void {
$this->init();
try {
parent::setExtraSmbClientOptions($smbClientState);
} catch (Exception $e) {
// suppress
}
}
public function __destruct() {
if (!empty($this->ticketPath) && file_exists($this->ticketPath) && is_file($this->ticketPath)) {
unlink($this->ticketPath);
}
}
}
@@ -0,0 +1,54 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
use Icewind\SMB\Exception\Exception;
/**
* Use existing kerberos ticket to authenticate
*/
class KerberosAuth implements IAuth {
public function getUsername(): ?string {
return 'dummy';
}
public function getWorkgroup(): ?string {
return 'dummy';
}
public function getPassword(): ?string {
return null;
}
public function getExtraCommandLineArguments(): string {
return '-k';
}
public function setExtraSmbClientOptions($smbClientState): void {
$success = (bool)smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true);
$success = $success && smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false);
if (!$success) {
throw new Exception("Failed to set smbclient options for kerberos auth");
}
}
}
@@ -0,0 +1,162 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Native;
use Icewind\SMB\ACL;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\IFileInfo;
class NativeFileInfo implements IFileInfo {
/** @var string */
protected $path;
/** @var string */
protected $name;
/** @var NativeShare */
protected $share;
/** @var array{"mode": int, "size": int, "write_time": int}|null */
protected $attributeCache = null;
public function __construct(NativeShare $share, string $path, string $name) {
$this->share = $share;
$this->path = $path;
$this->name = $name;
}
public function getPath(): string {
return $this->path;
}
public function getName(): string {
return $this->name;
}
/**
* @return array{"mode": int, "size": int, "write_time": int}
*/
protected function stat(): array {
if (is_null($this->attributeCache)) {
$rawAttributes = explode(',', $this->share->getAttribute($this->path, 'system.dos_attr.*'));
$attributes = [];
foreach ($rawAttributes as $rawAttribute) {
list($name, $value) = explode(':', $rawAttribute);
$name = strtolower($name);
if ($name == 'mode') {
$attributes[$name] = (int)hexdec(substr($value, 2));
} else {
$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;
}
public function getSize(): int {
$stat = $this->stat();
return $stat['size'];
}
public function getMTime(): int {
$stat = $this->stat();
return $stat['write_time'];
}
/**
* On "mode":
*
* different smbclient versions seem to return different mode values for 'system.dos_attr.mode'
*
* older versions return the dos permissions mask as defined in `IFileInfo::MODE_*` while
* newer versions return the equivalent unix permission mask.
*
* Since the unix mask doesn't contain the proper hidden/archive/system flags we have to assume them
* as false (except for `hidden` where we use the unix dotfile convention)
*/
protected function getMode(): int {
$mode = $this->stat()['mode'];
// Let us ignore the ATTR_NOT_CONTENT_INDEXED for now
$mode &= ~0x00002000;
return $mode;
}
public function isDirectory(): bool {
$mode = $this->getMode();
if ($mode > 0x1000) {
return ($mode & 0x4000 && !($mode & 0x8000)); // 0x4000: unix directory flag shares bits with 0xC000: socket
} else {
return (bool)($mode & IFileInfo::MODE_DIRECTORY);
}
}
public function isReadOnly(): bool {
$mode = $this->getMode();
if ($mode > 0x1000) {
return !(bool)($mode & 0x80); // 0x80: owner write permissions
} else {
return (bool)($mode & IFileInfo::MODE_READONLY);
}
}
public function isHidden(): bool {
$mode = $this->getMode();
if ($mode > 0x1000) {
return strlen($this->name) > 0 && $this->name[0] === '.';
} else {
return (bool)($mode & IFileInfo::MODE_HIDDEN);
}
}
public function isSystem(): bool {
$mode = $this->getMode();
if ($mode > 0x1000) {
return false;
} else {
return (bool)($mode & IFileInfo::MODE_SYSTEM);
}
}
public function isArchived(): bool {
$mode = $this->getMode();
if ($mode > 0x1000) {
return false;
} else {
return (bool)($mode & IFileInfo::MODE_ARCHIVE);
}
}
/**
* @return ACL[]
*/
public function getAcls(): array {
$acls = [];
$attribute = $this->share->getAttribute($this->path, 'system.nt_sec_desc.acl.*+');
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((int)$type, (int)$flags, (int)$mask);
}
return $acls;
}
}
@@ -0,0 +1,93 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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 StringBuffer */
private $readBuffer;
public function __construct() {
$this->readBuffer = new StringBuffer();
}
/** @var int */
private $pos = 0;
public function stream_open($path, $mode, $options, &$opened_path) {
return parent::stream_open($path, $mode, $options, $opened_path);
}
/**
* Wrap a stream from libsmbclient-php into a regular php stream
*
* @param NativeState $state
* @param resource $smbStream
* @param string $mode
* @param string $url
* @return resource
*/
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->readBuffer->remaining() < $count) {
$chunk = parent::stream_read(self::CHUNK_SIZE);
if ($chunk === false) {
return false;
}
$this->readBuffer->push($chunk);
}
$result = $this->readBuffer->read($count);
$read = strlen($result);
$this->pos += $read;
return $result;
}
public function stream_seek($offset, $whence = SEEK_SET) {
$result = parent::stream_seek($offset, $whence);
if ($result) {
$this->readBuffer->clear();
$pos = parent::stream_tell();
if ($pos === false) {
return false;
}
$this->pos = $pos;
}
return $result;
}
public function stream_eof() {
return $this->readBuffer->remaining() <= 0 && parent::stream_eof();
}
public function stream_tell() {
return $this->pos;
}
public function stream_write($data) {
return false;
}
public function stream_truncate($size) {
return false;
}
}
@@ -0,0 +1,65 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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\ITimeZoneProvider;
class NativeServer extends AbstractServer {
/**
* @var NativeState
*/
protected $state;
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(): void {
$this->state->init($this->getAuth(), $this->getOptions());
}
/**
* @return IShare[]
* @throws AuthenticationException
* @throws InvalidHostException
*/
public function listShares(): array {
$this->connect();
$shares = [];
$dh = $this->state->opendir('smb://' . $this->getHost());
while ($share = $this->state->readdir($dh, '')) {
if ($share['type'] === 'file share') {
$shares[] = $this->getShare($share['name']);
}
}
$this->state->closedir($dh, '');
return $shares;
}
public function getShare(string $name): IShare {
return new NativeShare($this, $name);
}
/**
* Check if the smbclient php extension is available
*
* @param ISystem $system
* @return bool
*/
public static function available(ISystem $system): bool {
return $system->libSmbclientAvailable();
}
}
@@ -0,0 +1,363 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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;
use Icewind\SMB\Wrapped\Share;
class NativeShare extends AbstractShare {
/**
* @var IServer $server
*/
private $server;
/**
* @var string $name
*/
private $name;
/** @var NativeState|null $state */
private $state = null;
public function __construct(IServer $server, string $name) {
parent::__construct();
$this->server = $server;
$this->name = $name;
}
/**
* @throws ConnectionException
* @throws AuthenticationException
* @throws InvalidHostException
*/
protected function getState(): NativeState {
if ($this->state) {
return $this->state;
}
$this->state = new NativeState();
$this->state->init($this->server->getAuth(), $this->server->getOptions());
return $this->state;
}
/**
* Get the name of the share
*
* @return string
*/
public function getName(): string {
return $this->name;
}
private function buildUrl(string $path): string {
$this->verifyPath($path);
$url = sprintf('smb://%s/%s', $this->server->getHost(), $this->name);
if ($path) {
$path = trim($path, '/');
$url .= '/';
$url .= implode('/', array_map('rawurlencode', explode('/', $path)));
}
return $url;
}
/**
* List the content of a remote folder
*
* @param string $path
* @return IFileInfo[]
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function dir(string $path): array {
$files = [];
$dh = $this->getState()->opendir($this->buildUrl($path));
while ($file = $this->getState()->readdir($dh, $path)) {
$name = $file['name'];
if ($name !== '.' and $name !== '..') {
$fullPath = $path . '/' . $name;
$files [] = new NativeFileInfo($this, $fullPath, $name);
}
}
$this->getState()->closedir($dh, $path);
return $files;
}
/**
* @param string $path
* @return IFileInfo
*/
public function stat(string $path): IFileInfo {
$info = new NativeFileInfo($this, $path, self::mb_basename($path));
// trigger attribute loading
$info->getSize();
return $info;
}
/**
* Multibyte unicode safe version of basename()
*
* @param string $path
* @link http://php.net/manual/en/function.basename.php#121405
* @return string
*/
protected static function mb_basename(string $path): string {
if (preg_match('@^.*[\\\\/]([^\\\\/]+)$@s', $path, $matches)) {
return $matches[1];
} elseif (preg_match('@^([^\\\\/]+)$@s', $path, $matches)) {
return $matches[1];
}
return '';
}
/**
* Create a folder on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws AlreadyExistsException
*/
public function mkdir(string $path): bool {
return $this->getState()->mkdir($this->buildUrl($path));
}
/**
* Remove a folder on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function rmdir(string $path): bool {
return $this->getState()->rmdir($this->buildUrl($path));
}
/**
* Delete a file on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function del(string $path): bool {
return $this->getState()->unlink($this->buildUrl($path));
}
/**
* Rename a remote file
*
* @param string $from
* @param string $to
* @return bool
*
* @throws NotFoundException
* @throws AlreadyExistsException
*/
public function rename(string $from, string $to): bool {
return $this->getState()->rename($this->buildUrl($from), $this->buildUrl($to));
}
/**
* Upload a local file
*
* @param string $source local file
* @param string $target remove file
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function put(string $source, string $target): bool {
$sourceHandle = fopen($source, 'rb');
$targetUrl = $this->buildUrl($target);
$targetHandle = $this->getState()->create($targetUrl);
while ($data = fread($sourceHandle, NativeReadStream::CHUNK_SIZE)) {
$this->getState()->write($targetHandle, $data, $targetUrl);
}
$this->getState()->close($targetHandle, $targetUrl);
return true;
}
/**
* Download a remote file
*
* @param string $source remove file
* @param string $target local file
* @return bool
*
* @throws AuthenticationException
* @throws ConnectionException
* @throws InvalidHostException
* @throws InvalidPathException
* @throws InvalidResourceException
*/
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');
$targetHandle = @fopen($target, 'wb');
if (!$targetHandle) {
$error = error_get_last();
if (is_array($error)) {
$reason = $error['message'];
} else {
$reason = 'Unknown error';
}
$this->getState()->close($sourceHandle, $this->buildUrl($source));
throw new InvalidResourceException('Failed opening local file "' . $target . '" for writing: ' . $reason);
}
while ($data = $this->getState()->read($sourceHandle, NativeReadStream::CHUNK_SIZE, $source)) {
fwrite($targetHandle, $data);
}
$this->getState()->close($sourceHandle, $this->buildUrl($source));
return true;
}
/**
* Open a readable stream to a remote file
*
* @param string $source
* @return resource a read only stream with the contents of the remote file
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function read(string $source) {
$url = $this->buildUrl($source);
$handle = $this->getState()->open($url, 'r');
return NativeReadStream::wrap($this->getState(), $handle, 'r', $url);
}
/**
* Open a writeable stream to a remote file
* Note: This method will truncate the file to 0bytes first
*
* @param string $target
* @return resource a writeable stream
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function write(string $target) {
$url = $this->buildUrl($target);
$handle = $this->getState()->create($url);
return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url);
}
/**
* Open a writeable stream and set the cursor to the end of the stream
*
* @param string $target
* @return resource a writeable stream
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function append(string $target) {
$url = $this->buildUrl($target);
$handle = $this->getState()->open($url, "a+");
return NativeWriteStream::wrap($this->getState(), $handle, "a", $url);
}
/**
* Get extended attributes for the path
*
* @param string $path
* @param string $attribute attribute to get the info
* @return string the attribute value
*/
public function getAttribute(string $path, string $attribute): string {
return $this->getState()->getxattr($this->buildUrl($path), $attribute);
}
/**
* Set extended attributes for the given path
*
* @param string $path
* @param string $attribute attribute to get the info
* @param string|int $value
* @return mixed the attribute 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);
}
/**
* Set DOS comaptible node mode
*
* @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(string $path, int $mode) {
return $this->setAttribute($path, 'system.dos_attr.mode', $mode);
}
/**
* Start smb notify listener
* Note: This is a blocking call
*
* @param string $path
* @return INotifyHandler
*/
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())) {
throw new DependencyException('smbclient not found in path for notify command');
}
$share = new Share($this->server, $this->getName(), $this->server->getSystem());
return $share->notify($path);
}
public function getServer(): IServer {
return $this->server;
}
public function __destruct() {
unset($this->state);
}
}
@@ -0,0 +1,377 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Native;
use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\Exception\ConnectionRefusedException;
use Icewind\SMB\Exception\ConnectionResetException;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\FileInUseException;
use Icewind\SMB\Exception\ForbiddenException;
use Icewind\SMB\Exception\HostDownException;
use Icewind\SMB\Exception\InvalidArgumentException;
use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\ConnectionAbortedException;
use Icewind\SMB\Exception\NoRouteToHostException;
use Icewind\SMB\Exception\NotEmptyException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\Exception\OutOfSpaceException;
use Icewind\SMB\Exception\TimedOutException;
use Icewind\SMB\IAuth;
use Icewind\SMB\IOptions;
/**
* Low level wrapper for libsmbclient-php with error handling
*/
class NativeState {
/** @var resource|null */
protected $state = null;
/** @var bool */
protected $handlerSet = false;
/** @var bool */
protected $connected = false;
/**
* sync the garbage collection cycle
* __deconstruct() of KerberosAuth should not called too soon
*
* @var IAuth|null $auth
*/
protected $auth = null;
// see error.h
const EXCEPTION_MAP = [
1 => ForbiddenException::class,
2 => NotFoundException::class,
13 => ForbiddenException::class,
16 => FileInUseException::class,
17 => AlreadyExistsException::class,
20 => InvalidTypeException::class,
21 => InvalidTypeException::class,
22 => InvalidArgumentException::class,
28 => OutOfSpaceException::class,
39 => NotEmptyException::class,
103 => ConnectionAbortedException::class,
104 => ConnectionResetException::class,
110 => TimedOutException::class,
111 => ConnectionRefusedException::class,
112 => HostDownException::class,
113 => NoRouteToHostException::class
];
protected function handleError(?string $path): void {
/** @var int $error */
$error = smbclient_state_errno($this->state);
if ($error === 0) {
return;
}
throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
}
/**
* @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 = $uri;
}
$this->handleError($path);
}
}
/**
* @param IAuth $auth
* @param IOptions $options
* @return bool
*/
public function init(IAuth $auth, IOptions $options) {
if ($this->connected) {
return true;
}
/** @var resource $state */
$state = smbclient_state_new();
$this->state = $state;
/** @psalm-suppress UnusedFunctionCall */
smbclient_option_set($this->state, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, false);
/** @psalm-suppress UnusedFunctionCall */
smbclient_option_set($this->state, SMBCLIENT_OPT_TIMEOUT, $options->getTimeout() * 1000);
if (function_exists('smbclient_client_protocols')) {
smbclient_client_protocols($this->state, $options->getMinProtocol(), $options->getMaxProtocol());
}
$auth->setExtraSmbClientOptions($this->state);
// sync the garbage collection cycle
// __deconstruct() of KerberosAuth should not caled too soon
$this->auth = $auth;
/** @var bool $result */
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());
$this->testResult($result, '');
$this->connected = true;
return $result;
}
/**
* @param string $uri
* @return resource
*/
public function opendir(string $uri) {
/** @var resource $result */
$result = @smbclient_opendir($this->state, $uri);
$this->testResult($result, $uri);
return $result;
}
/**
* @param resource $dir
* @param string $path
* @return array{"type": string, "comment": string, "name": string}|false
*/
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, $path);
return $result;
}
/**
* @param resource $dir
* @param string $path
* @return bool
*/
public function closedir($dir, string $path): bool {
/** @var bool $result */
$result = smbclient_closedir($this->state, $dir);
$this->testResult($result, $path);
return $result;
}
/**
* @param string $old
* @param string $new
* @return bool
*/
public function rename(string $old, string $new): bool {
/** @var bool $result */
$result = @smbclient_rename($this->state, $old, $this->state, $new);
$this->testResult($result, $new);
return $result;
}
/**
* @param string $uri
* @return bool
*/
public function unlink(string $uri): bool {
/** @var bool $result */
$result = @smbclient_unlink($this->state, $uri);
$this->testResult($result, $uri);
return $result;
}
/**
* @param string $uri
* @param int $mask
* @return bool
*/
public function mkdir(string $uri, int $mask = 0777): bool {
/** @var bool $result */
$result = @smbclient_mkdir($this->state, $uri, $mask);
$this->testResult($result, $uri);
return $result;
}
/**
* @param string $uri
* @return bool
*/
public function rmdir(string $uri): bool {
/** @var bool $result */
$result = @smbclient_rmdir($this->state, $uri);
$this->testResult($result, $uri);
return $result;
}
/**
* @param string $uri
* @return array{"mtime": int, "size": int, "mode": int}
*/
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);
return $result;
}
/**
* @param resource $file
* @param string $path
* @return array{"mtime": int, "size": int, "mode": int}
*/
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, $path);
return $result;
}
/**
* @param string $uri
* @param string $mode
* @param int $mask
* @return resource
*/
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);
return $result;
}
/**
* @param string $uri
* @param int $mask
* @return resource
*/
public function create(string $uri, int $mask = 0666) {
/** @var resource $result */
$result = @smbclient_creat($this->state, $uri, $mask);
$this->testResult($result, $uri);
return $result;
}
/**
* @param resource $file
* @param int $bytes
* @param string $path
* @return string
*/
public function read($file, int $bytes, string $path): string {
/** @var string $result */
$result = @smbclient_read($this->state, $file, $bytes);
$this->testResult($result, $path);
return $result;
}
/**
* @param resource $file
* @param string $data
* @param string $path
* @param int|null $length
* @return int
*/
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);
return $result;
}
/**
* @param resource $file
* @param int $offset
* @param int $whence SEEK_SET | SEEK_CUR | SEEK_END
* @param string|null $path
* @return int|false new file offset as measured from the start of the file on success.
*/
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, $path);
return $result;
}
/**
* @param resource $file
* @param int $size
* @param string $path
* @return bool
*/
public function ftruncate($file, int $size, string $path): bool {
/** @var bool $result */
$result = @smbclient_ftruncate($this->state, $file, $size);
$this->testResult($result, $path);
return $result;
}
/**
* @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);
return $result;
}
/**
* @param string $uri
* @param string $key
* @return string
*/
public function getxattr(string $uri, string $key) {
/** @var string $result */
$result = @smbclient_getxattr($this->state, $uri, $key);
$this->testResult($result, $uri);
return $result;
}
/**
* @param string $uri
* @param string $key
* @param string $value
* @param int $flags
* @return bool
*/
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);
return $result;
}
public function __destruct() {
if ($this->connected) {
if (smbclient_state_free($this->state) === false) {
throw new Exception("Failed to free smb state");
}
}
}
}
@@ -0,0 +1,159 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Native;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\InvalidRequestException;
use Icewind\Streams\File;
use InvalidArgumentException;
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;
/**
* @var bool
*/
protected $eof = false;
/**
* @var string
*/
protected $url = '';
/**
* Wrap a stream from libsmbclient-php into a regular php stream
*
* @param NativeState $state
* @param resource $smbStream
* @param string $mode
* @param string $url
* @param class-string<NativeStream> $class
* @return resource
*/
protected static function wrapClass(NativeState $state, $smbStream, string $mode, string $url, string $class) {
if (stream_wrapper_register('nativesmb', $class) === false) {
throw new Exception("Failed to register stream wrapper");
}
$context = stream_context_create([
'nativesmb' => [
'state' => $state,
'handle' => $smbStream,
'url' => $url
]
]);
$fh = fopen('nativesmb://', $mode, false, $context);
if (stream_wrapper_unregister('nativesmb') === false) {
throw new Exception("Failed to unregister stream wrapper");
}
return $fh;
}
public function stream_close() {
try {
return $this->state->close($this->handle, $this->url);
} catch (\Exception $e) {
return false;
}
}
public function stream_eof() {
return $this->eof;
}
public function stream_flush() {
return false;
}
public function stream_open($path, $mode, $options, &$opened_path) {
$context = stream_context_get_options($this->context);
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, $this->url);
if (strlen($result) < $count) {
$this->eof = true;
}
return $result;
}
public function stream_seek($offset, $whence = SEEK_SET) {
$this->eof = false;
try {
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);
} catch (Exception $e) {
return false;
}
}
public function stream_tell() {
return $this->state->lseek($this->handle, 0, SEEK_CUR, $this->url);
}
public function stream_write($data) {
return $this->state->write($this->handle, $data, $this->url);
}
public function stream_truncate($size) {
return $this->state->ftruncate($this->handle, $size, $this->url);
}
public function stream_set_option($option, $arg1, $arg2) {
return false;
}
public function stream_lock($operation) {
return false;
}
}
@@ -0,0 +1,96 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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 StringBuffer */
private $writeBuffer;
/** @var int */
private $pos = 0;
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 NativeState $state
* @param resource $smbStream
* @param string $mode
* @param string $url
* @return resource
*/
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) {
$pos = parent::stream_tell();
if ($pos === false) {
return false;
}
$this->pos = $pos;
}
return $result;
}
private function flushWrite(): void {
parent::stream_write($this->writeBuffer->flush());
}
public function stream_write($data) {
$written = $this->writeBuffer->push($data);
$this->pos += $written;
if ($this->writeBuffer->remaining() >= self::CHUNK_SIZE) {
$this->flushWrite();
}
return $written;
}
public function stream_close() {
try {
$this->flushWrite();
$flushResult = true;
} catch (\Exception $e) {
$flushResult = false;
}
return parent::stream_close() && $flushResult;
}
public function stream_tell() {
return $this->pos;
}
public function stream_read($count) {
return false;
}
public function stream_truncate($size) {
$this->flushWrite();
$this->pos = $size;
return parent::stream_truncate($size);
}
}
@@ -0,0 +1,56 @@
<?php
/**
* @copyright Copyright (c) 2018 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 Options implements IOptions {
/** @var int */
private $timeout = 20;
/** @var string|null */
private $minProtocol;
/** @var string|null */
private $maxProtocol;
public function getTimeout(): int {
return $this->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;
}
}
@@ -0,0 +1,85 @@
<?php
/**
* @copyright Copyright (c) 2018 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;
use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Native\NativeServer;
use Icewind\SMB\Wrapped\Server;
class ServerFactory {
const BACKENDS = [
NativeServer::class,
Server::class
];
/** @var ISystem */
private $system;
/** @var IOptions */
private $options;
/** @var ITimeZoneProvider */
private $timeZoneProvider;
/**
* ServerFactory constructor.
*
* @param IOptions|null $options
* @param ISystem|null $system
* @param ITimeZoneProvider|null $timeZoneProvider
*/
public function __construct(
IOptions $options = null,
ISystem $system = null,
ITimeZoneProvider $timeZoneProvider = null
) {
if (is_null($options)) {
$options = new Options();
}
if (is_null($system)) {
$system = new System();
}
if (is_null($timeZoneProvider)) {
$timeZoneProvider = new TimeZoneProvider($system);
}
$this->options = $options;
$this->system = $system;
$this->timeZoneProvider = $timeZoneProvider;
}
/**
* @param string $host
* @param IAuth $credentials
* @return IServer
* @throws DependencyException
*/
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);
}
}
throw new DependencyException('No valid backend available, ensure smbclient is in the path or php-smbclient is installed');
}
}
@@ -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, $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;
}
}
@@ -0,0 +1,76 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB;
use Icewind\SMB\Exception\Exception;
class System implements ISystem {
/** @var (string|null)[] */
private $paths = [];
/**
* Get the path to a file descriptor of the current process
*
* @param int $num the file descriptor id
* @return string
* @throws Exception
*/
public function getFD(int $num): string {
$folders = [
'/proc/self/fd',
'/dev/fd'
];
foreach ($folders as $folder) {
if (file_exists($folder)) {
return $folder . '/' . $num;
}
}
throw new Exception('Cant find file descriptor path');
}
public function getSmbclientPath(): ?string {
return $this->getBinaryPath('smbclient');
}
public function getNetPath(): ?string {
return $this->getBinaryPath('net');
}
public function getSmbcAclsPath(): ?string {
return $this->getBinaryPath('smbcacls');
}
public function getStdBufPath(): ?string {
return $this->getBinaryPath('stdbuf');
}
public function getDatePath(): ?string {
return $this->getBinaryPath('date');
}
public function libSmbclientAvailable(): bool {
return function_exists('smbclient_state_new');
}
protected function getBinaryPath(string $binary): ?string {
if (!isset($this->paths[$binary])) {
$result = null;
$output = [];
exec("which $binary 2>&1", $output, $result);
if ($result === 0 && isset($output[0])) {
$this->paths[$binary] = (string)$output[0];
} else if (is_executable("/usr/bin/$binary")) {
$this->paths[$binary] = "/usr/bin/$binary";
} else {
$this->paths[$binary] = null;
}
}
return $this->paths[$binary];
}
}
@@ -0,0 +1,54 @@
<?php
/**
* Copyright (c) 2015 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB;
class TimeZoneProvider implements ITimeZoneProvider {
/**
* @var string[]
*/
private $timeZones = [];
/**
* @var ISystem
*/
private $system;
/**
* @param ISystem $system
*/
public function __construct(ISystem $system) {
$this->system = $system;
}
public function get(string $host): string {
if (!isset($this->timeZones[$host])) {
$timeZone = null;
$net = $this->system->getNetPath();
// for local domain names we can assume same timezone
if ($net && $host && strpos($host, '.') !== false) {
$command = sprintf(
'%s time zone -S %s',
$net,
escapeshellarg($host)
);
$timeZone = exec($command);
}
if (!$timeZone) {
$date = $this->system->getDatePath();
if ($date) {
$timeZone = exec($date . " +%z");
} else {
$timeZone = date_default_timezone_get();
}
}
$this->timeZones[$host] = $timeZone;
}
return $this->timeZones[$host];
}
}
@@ -0,0 +1,116 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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;
class Connection extends RawConnection {
const DELIMITER = 'smb:';
const DELIMITER_LENGTH = 4;
/** @var Parser */
private $parser;
/**
* @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;
}
/**
* send input to smbclient
*
* @param string $input
*/
public function write(string $input) {
return parent::write($input . PHP_EOL);
}
/**
* @throws ConnectException
*/
public function clearTillPrompt(): void {
$this->write('');
do {
$promptLine = $this->readTillPrompt();
if ($promptLine === false) {
break;
}
$this->parser->checkConnectionError($promptLine);
} while (!$this->isPrompt($promptLine));
if ($this->write('') === false) {
throw new ConnectionRefusedException();
}
$this->readTillPrompt();
}
/**
* get all unprocessed output from smbclient until the next prompt
*
* @return string[]
* @throws AuthenticationException
* @throws ConnectException
* @throws ConnectionException
* @throws InvalidHostException
* @throws NoLoginServerException
* @throws AccessDeniedException
*/
public function read(): array {
if (!$this->isValid()) {
throw new ConnectionException('Connection not valid');
}
$output = $this->readTillPrompt();
if ($output === false) {
$this->unknownError(false);
}
$output = explode("\n", $output);
// last line contains the prompt
array_pop($output);
return $output;
}
private function isPrompt(string $line): bool {
return substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER;
}
/**
* @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
throw new ConnectException('Unknown error (' . $promptLine . ')');
} else {
$error = $this->readError(); // maybe something on stderr
if ($error) {
throw new ConnectException('Unknown error (stderr: ' . $error . ')');
} else {
throw new ConnectException('Unknown error');
}
}
}
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);
}
$this->close_process($terminate);
}
}
@@ -0,0 +1,31 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Wrapped;
class ErrorCodes {
/**
* connection errors
*/
const LogonFailure = 'NT_STATUS_LOGON_FAILURE';
const BadHostName = 'NT_STATUS_BAD_NETWORK_NAME';
const Unsuccessful = 'NT_STATUS_UNSUCCESSFUL';
const ConnectionRefused = 'NT_STATUS_CONNECTION_REFUSED';
const NoLogonServers = 'NT_STATUS_NO_LOGON_SERVERS';
const PathNotFound = 'NT_STATUS_OBJECT_PATH_NOT_FOUND';
const NoSuchFile = 'NT_STATUS_NO_SUCH_FILE';
const ObjectNotFound = 'NT_STATUS_OBJECT_NAME_NOT_FOUND';
const NameCollision = 'NT_STATUS_OBJECT_NAME_COLLISION';
const AccessDenied = 'NT_STATUS_ACCESS_DENIED';
const DirectoryNotEmpty = 'NT_STATUS_DIRECTORY_NOT_EMPTY';
const FileIsADirectory = 'NT_STATUS_FILE_IS_A_DIRECTORY';
const NotADirectory = 'NT_STATUS_NOT_A_DIRECTORY';
const SharingViolation = 'NT_STATUS_SHARING_VIOLATION';
const InvalidParameter = 'NT_STATUS_INVALID_PARAMETER';
const RevisionMismatch = 'NT_STATUS_REVISION_MISMATCH';
}
@@ -0,0 +1,89 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Wrapped;
use Icewind\SMB\ACL;
use Icewind\SMB\IFileInfo;
class FileInfo implements IFileInfo {
/** @var string */
protected $path;
/** @var string */
protected $name;
/** @var int */
protected $size;
/** @var int */
protected $time;
/** @var int */
protected $mode;
/** @var callable(): ACL[] */
protected $aclCallback;
/**
* @param string $path
* @param string $name
* @param int $size
* @param int $time
* @param int $mode
* @param callable(): ACL[] $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;
$this->time = $time;
$this->mode = $mode;
$this->aclCallback = $aclCallback;
}
/**
* @return string
*/
public function getPath(): string {
return $this->path;
}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
public function getMTime(): int {
return $this->time;
}
public function isDirectory(): bool {
return (bool)($this->mode & IFileInfo::MODE_DIRECTORY);
}
public function isReadOnly(): bool {
return (bool)($this->mode & IFileInfo::MODE_READONLY);
}
public function isHidden(): bool {
return (bool)($this->mode & IFileInfo::MODE_HIDDEN);
}
public function isSystem(): bool {
return (bool)($this->mode & IFileInfo::MODE_SYSTEM);
}
public function isArchived(): bool {
return (bool)($this->mode & IFileInfo::MODE_ARCHIVE);
}
/**
* @return ACL[]
*/
public function getAcls(): array {
return ($this->aclCallback)();
}
}
@@ -0,0 +1,113 @@
<?php
/**
* @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*
*/
namespace Icewind\SMB\Wrapped;
use Icewind\SMB\Change;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\RevisionMismatchException;
use Icewind\SMB\INotifyHandler;
class NotifyHandler implements INotifyHandler {
/** @var Connection */
private $connection;
/** @var string */
private $path;
/** @var bool */
private $listening = true;
// see error.h
const EXCEPTION_MAP = [
ErrorCodes::RevisionMismatch => RevisionMismatchException::class,
];
/**
* @param Connection $connection
* @param string $path
*/
public function __construct(Connection $connection, string $path) {
$this->connection = $connection;
$this->path = $path;
}
/**
* Get all changes detected since the start of the notify process or the last call to getChanges
*
* @return Change[]
*/
public function getChanges(): array {
if (!$this->listening) {
return [];
}
stream_set_blocking($this->connection->getOutputStream(), false);
$lines = [];
while (($line = $this->connection->readLine())) {
$this->checkForError($line);
$lines[] = $line;
}
stream_set_blocking($this->connection->getOutputStream(), true);
return array_values(array_filter(array_map([$this, 'parseChangeLine'], $lines)));
}
/**
* 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(Change):?bool $callback
*/
public function listen(callable $callback): void {
if ($this->listening) {
while (true) {
$line = $this->connection->readLine();
if ($line === false) {
break;
}
$this->checkForError($line);
$change = $this->parseChangeLine($line);
if ($change) {
$result = $callback($change);
if ($result === false) {
break;
}
}
};
}
}
private function parseChangeLine(string $line): ?Change {
$code = (int)substr($line, 0, 4);
if ($code === 0) {
return null;
}
$subPath = str_replace('\\', '/', substr($line, 5));
if ($this->path === '') {
return new Change($code, $subPath);
} else {
return new Change($code, $this->path . '/' . $subPath);
}
}
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(): void {
$this->listening = false;
$this->connection->close();
}
public function __destruct() {
$this->stop();
}
}
@@ -0,0 +1,277 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Wrapped;
use Icewind\SMB\ACL;
use Icewind\SMB\Exception\AccessDeniedException;
use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\FileInUseException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\Exception\InvalidParameterException;
use Icewind\SMB\Exception\InvalidResourceException;
use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NoLoginServerException;
use Icewind\SMB\Exception\NotEmptyException;
use Icewind\SMB\Exception\NotFoundException;
class Parser {
const MSG_NOT_FOUND = 'Error opening local file ';
/**
* @var string
*/
protected $timeZone;
// see error.h
const EXCEPTION_MAP = [
ErrorCodes::LogonFailure => AuthenticationException::class,
ErrorCodes::PathNotFound => NotFoundException::class,
ErrorCodes::ObjectNotFound => NotFoundException::class,
ErrorCodes::NoSuchFile => NotFoundException::class,
ErrorCodes::NameCollision => AlreadyExistsException::class,
ErrorCodes::AccessDenied => AccessDeniedException::class,
ErrorCodes::DirectoryNotEmpty => NotEmptyException::class,
ErrorCodes::FileIsADirectory => InvalidTypeException::class,
ErrorCodes::NotADirectory => InvalidTypeException::class,
ErrorCodes::SharingViolation => FileInUseException::class,
ErrorCodes::InvalidParameter => InvalidParameterException::class
];
const MODE_STRINGS = [
'R' => FileInfo::MODE_READONLY,
'H' => FileInfo::MODE_HIDDEN,
'S' => FileInfo::MODE_SYSTEM,
'D' => FileInfo::MODE_DIRECTORY,
'A' => FileInfo::MODE_ARCHIVE,
'N' => FileInfo::MODE_NORMAL
];
/**
* @param string $timeZone
*/
public function __construct(string $timeZone) {
$this->timeZone = $timeZone;
}
private function getErrorCode(string $line): ?string {
$parts = explode(' ', $line);
foreach ($parts as $part) {
if (substr($part, 0, 9) === 'NT_STATUS') {
return $part;
}
}
return null;
}
/**
* @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);
}
$error = $this->getErrorCode($output[0]);
if (substr($output[0], 0, strlen(self::MSG_NOT_FOUND)) === self::MSG_NOT_FOUND) {
$localPath = substr($output[0], strlen(self::MSG_NOT_FOUND));
throw new InvalidResourceException('Failed opening local file "' . $localPath . '" for writing');
}
throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
}
/**
* check if the first line holds a connection failure
*
* @param string $line
* @throws AuthenticationException
* @throws InvalidHostException
* @throws NoLoginServerException
* @throws AccessDeniedException
*/
public function checkConnectionError(string $line): void {
$line = rtrim($line, ')');
if (substr($line, -23) === ErrorCodes::LogonFailure) {
throw new AuthenticationException('Invalid login');
}
if (substr($line, -26) === ErrorCodes::BadHostName) {
throw new InvalidHostException('Invalid hostname');
}
if (substr($line, -22) === ErrorCodes::Unsuccessful) {
throw new InvalidHostException('Connection unsuccessful');
}
if (substr($line, -28) === ErrorCodes::ConnectionRefused) {
throw new InvalidHostException('Connection refused');
}
if (substr($line, -26) === ErrorCodes::NoLogonServers) {
throw new NoLoginServerException('No login server');
}
if (substr($line, -23) === ErrorCodes::AccessDenied) {
throw new AccessDeniedException('Access denied');
}
}
public function parseMode(string $mode): int {
$result = 0;
foreach (self::MODE_STRINGS as $char => $val) {
if (strpos($mode, $char) !== false) {
$result |= $val;
}
}
return $result;
}
/**
* @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
// properly. May happen when accessing non Windows Fileservers
$words = explode(':', $line, 2);
$name = $words[0] ?? '';
$value = $words[1] ?? '';
$value = trim($value);
if (!isset($data[$name])) {
$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'], $attributeStart + 1, -1)),
'size' => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0
];
}
/**
* @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+(?:([NDHARSCndharsc]*)\s+)?([0-9]+)\s+(.*)$/';
//2 spaces, filename, optional type, size, date
$content = [];
foreach ($output as $line) {
if (preg_match($regex, $line, $matches)) {
list(, $name, $mode, $size, $time) = $matches;
if ($name !== '.' and $name !== '..') {
$mode = $this->parseMode(strtoupper($mode));
$time = strtotime($time . ' ' . $this->timeZone);
$path = $basePath . '/' . $name;
$content[] = new FileInfo($path, $name, (int)$size, $time, $mode, function () use ($aclCallback, $path): array {
return $aclCallback($path);
});
}
}
}
return $content;
}
/**
* @param string[] $output
* @return array<string, string>
*/
public function parseListShares(array $output): array {
$shareNames = [];
foreach ($output as $line) {
if (strpos($line, '|')) {
list($type, $name, $description) = explode('|', $line);
if (strtolower($type) === 'disk') {
$shareNames[$name] = $description;
}
} elseif (strpos($line, 'Disk')) {
// new output format
list($name, $description) = explode('Disk', $line);
$shareNames[trim($name)] = trim($description);
}
}
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;
}
}
@@ -0,0 +1,250 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Wrapped;
use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException;
class RawConnection {
/**
* @var string
*/
private $command;
/**
* @var string[]
*/
private $env;
/**
* @var resource[] $pipes
*
* $pipes[0] holds STDIN for smbclient
* $pipes[1] holds STDOUT for smbclient
* $pipes[3] holds the authfile for smbclient
* $pipes[4] holds the stream for writing files
* $pipes[5] holds the stream for reading files
*/
private $pipes = [];
/**
* @var resource|null $process
*/
private $process;
/**
* @var resource|null $authStream
*/
private $authStream = null;
/**
* @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(): void {
if (is_null($this->getAuthStream())) {
throw new ConnectException('Authentication not set before connecting');
}
$descriptorSpec = [
0 => ['pipe', 'r'], // child reads from stdin
1 => ['pipe', 'w'], // child writes to stdout
2 => ['pipe', 'w'], // child writes to stderr
3 => $this->getAuthStream(), // child reads from fd#3
4 => ['pipe', 'r'], // child reads from fd#4
5 => ['pipe', 'w'] // child writes to fd#5
];
setlocale(LC_ALL, Server::LOCALE);
$env = array_merge($this->env, [
'CLI_FORCE_INTERACTIVE' => 'y', // Make sure the prompt is displayed
'CLI_NO_READLINE' => 1, // Not all distros build smbclient with readline, disable it to get consistent behaviour
'LC_ALL' => Server::LOCALE,
'LANG' => Server::LOCALE,
'COLUMNS' => 8192, // prevent smbclient from line-wrapping it's output
'TZ' => 'UTC',
]);
$this->process = proc_open($this->command, $descriptorSpec, $this->pipes, '/', $env);
if (!$this->isValid()) {
throw new ConnectionException();
}
}
/**
* check if the connection is still active
*
* @return bool
* @psalm-assert-if-true resource $this->process
*/
public function isValid(): bool {
if (is_resource($this->process)) {
$status = proc_get_status($this->process);
return $status['running'];
} else {
return false;
}
}
/**
* send input to the process
*
* @param string $input
* @return int|bool
*/
public function write(string $input) {
$result = @fwrite($this->getInputStream(), $input);
fflush($this->getInputStream());
return $result;
}
/**
* read output till the next prompt
*
* @return string|false
*/
public function readTillPrompt() {
$output = "";
do {
$chunk = $this->readLine('\> ');
if ($chunk === false) {
return false;
}
$output .= $chunk;
} while (strlen($chunk) == 4096 && strpos($chunk, "smb:") === false);
return $output;
}
/**
* read a line of output
*
* @return string|false
*/
public function readLine(string $end = "\n") {
return stream_get_line($this->getOutputStream(), 4096, $end);
}
/**
* read a line of output
*
* @return string|false
*/
public function readError() {
$line = stream_get_line($this->getErrorStream(), 4086);
return $line !== false ? trim($line) : false;
}
/**
* get all output until the process closes
*
* @return string[]
*/
public function readAll(): array {
$output = [];
while ($line = $this->readLine()) {
$output[] = $line;
}
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];
}
/**
* @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->authStream, $auth);
}
/**
* @param bool $terminate
* @psalm-assert null $this->process
*/
public function close(bool $terminate = true): void {
$this->close_process($terminate);
}
/**
* @param bool $terminate
* @psalm-assert null $this->process
*/
protected function close_process(bool $terminate = true): void {
if (!is_resource($this->process)) {
return;
}
if ($terminate) {
proc_terminate($this->process);
}
proc_close($this->process);
$this->process = null;
}
public function reconnect(): void {
$this->close();
$this->connect();
}
public function __destruct() {
$this->close();
}
}
@@ -0,0 +1,104 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\SMB\Wrapped;
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;
class Server extends AbstractServer {
/**
* Check if the smbclient php extension is available
*
* @param ISystem $system
* @return bool
*/
public static function available(ISystem $system): bool {
return $system->getSmbclientPath() !== null;
}
private function getAuthFileArgument(): string {
if ($this->getAuth()->getUsername()) {
return '--authentication-file=' . $this->system->getFD(3);
} else {
return '';
}
}
/**
* @return IShare[]
*
* @throws AuthenticationException
* @throws InvalidHostException
* @throws ConnectException
*/
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 %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((string)$connection->readLine());
}
$parser = new Parser('UTC');
$output = $connection->readAll();
if (isset($output[0])) {
$parser->checkConnectionError($output[0]);
}
// sometimes we get an empty line first
if (count($output) < 2) {
$output = $connection->readAll();
}
if (isset($output[0])) {
$parser->checkConnectionError($output[0]);
}
if (count($output) === 0) {
throw new ConnectionRefusedException();
}
$shareNames = $parser->parseListShares($output);
$shares = [];
foreach ($shareNames as $name => $_description) {
$shares[] = $this->getShare($name);
}
return $shares;
}
/**
* @param string $name
* @return IShare
*/
public function getShare(string $name): IShare {
return new Share($this, $name, $this->system);
}
}
@@ -0,0 +1,554 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
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;
use Icewind\SMB\IFileInfo;
use Icewind\SMB\INotifyHandler;
use Icewind\SMB\IServer;
use Icewind\SMB\ISystem;
use Icewind\Streams\CallbackWrapper;
use Icewind\SMB\Native\NativeShare;
use Icewind\SMB\Native\NativeServer;
class Share extends AbstractShare {
/**
* @var IServer $server
*/
private $server;
/**
* @var string $name
*/
private $name;
/**
* @var Connection|null $connection
*/
public $connection = null;
/**
* @var Parser
*/
protected $parser;
/**
* @var ISystem
*/
private $system;
const MODE_MAP = [
FileInfo::MODE_READONLY => 'r',
FileInfo::MODE_HIDDEN => 'h',
FileInfo::MODE_ARCHIVE => 'a',
FileInfo::MODE_SYSTEM => 's'
];
const EXEC_CMD = 'exec';
/**
* @param IServer $server
* @param string $name
* @param ISystem $system
*/
public function __construct(IServer $server, string $name, ISystem $system) {
parent::__construct();
$this->server = $server;
$this->name = $name;
$this->system = $system;
$this->parser = new Parser('UTC');
}
private function getAuthFileArgument(): string {
if ($this->server->getAuth()->getUsername()) {
return '--authentication-file=' . $this->system->getFD(3);
} else {
return '';
}
}
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',
self::EXEC_CMD,
$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((string)$connection->readLine());
}
// some versions of smbclient add a help message in first of the first prompt
$connection->clearTillPrompt();
return $connection;
}
/**
* @throws ConnectionException
* @throws AuthenticationException
* @throws InvalidHostException
* @psalm-assert Connection $this->connection
*/
protected function connect(): Connection {
if ($this->connection and $this->connection->isValid()) {
return $this->connection;
}
$this->connection = $this->getConnection();
return $this->connection;
}
/**
* @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(): string {
return $this->name;
}
protected function simpleCommand(string $command, string $path): bool {
$escapedPath = $this->escapePath($path);
$cmd = $command . ' ' . $escapedPath;
$output = $this->execute($cmd);
return $this->parseOutput($output, $path);
}
/**
* List the content of a remote folder
*
* @param string $path
* @return IFileInfo[]
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function dir(string $path): array {
$escapedPath = $this->escapePath($path);
$output = $this->execute('cd ' . $escapedPath);
//check output for errors
$this->parseOutput($output, $path);
$output = $this->execute('dir');
$this->execute('cd /');
return $this->parser->parseDir($output, $path, function (string $path) {
return $this->getAcls($path);
});
}
/**
* @param string $path
* @return IFileInfo
*/
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 !== "/") {
$parent = dirname($path);
$dir = $this->dir($parent);
$file = array_values(array_filter($dir, function (IFileInfo $info) use ($path) {
return $info->getPath() === $path;
}));
if ($file) {
return $file[0];
}
}
$escapedPath = $this->escapePath($path);
$output = $this->execute('allinfo ' . $escapedPath);
// Windows and non Windows Fileserver may respond different
// to the allinfo command for directories. If the result is a single
// line = error line, redo it with a different allinfo parameter
if ($escapedPath == '""' && count($output) < 2) {
$output = $this->execute('allinfo ' . '"."');
}
if (count($output) < 3) {
$this->parseOutput($output, $path);
}
$stat = $this->parser->parseStat($output);
return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode'], function () use ($path) {
return $this->getAcls($path);
});
}
/**
* Create a folder on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws AlreadyExistsException
*/
public function mkdir(string $path): bool {
return $this->simpleCommand('mkdir', $path);
}
/**
* Remove a folder on the share
*
* @param string $path
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
public function rmdir(string $path): bool {
return $this->simpleCommand('rmdir', $path);
}
/**
* Delete a file on the share
*
* @param string $path
* @param bool $secondTry
* @return bool
* @throws InvalidTypeException
* @throws NotFoundException
* @throws \Exception
*/
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 {
return $this->simpleCommand('del', $path);
} catch (NotFoundException $e) {
//no need to do anything with the result, we just check if this throws the not found error
try {
$this->simpleCommand('ls', $path);
} catch (NotFoundException $e2) {
throw $e;
} catch (\Exception $e2) {
throw new InvalidTypeException($path);
}
throw $e;
} catch (FileInUseException $e) {
if ($secondTry) {
throw $e;
}
$this->reconnect();
return $this->del($path, true);
}
}
/**
* Rename a remote file
*
* @param string $from
* @param string $to
* @return bool
*
* @throws NotFoundException
* @throws AlreadyExistsException
*/
public function rename(string $from, string $to): bool {
$path1 = $this->escapePath($from);
$path2 = $this->escapePath($to);
$output = $this->execute('rename ' . $path1 . ' ' . $path2);
return $this->parseOutput($output, $to);
}
/**
* Upload a local file
*
* @param string $source local file
* @param string $target remove file
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
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);
return $this->parseOutput($output, $target);
}
/**
* Download a remote file
*
* @param string $source remove file
* @param string $target local file
* @return bool
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
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);
return $this->parseOutput($output, $source);
}
/**
* Open a readable stream to a remote file
*
* @param string $source
* @return resource a read only stream with the contents of the remote file
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
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
$connection = $this->getConnection();
stream_set_blocking($connection->getOutputStream(), false);
$connection->write('get ' . $source . ' ' . $this->system->getFD(5));
$connection->write('exit');
$fh = $connection->getFileOutputStream();
$fh = CallbackWrapper::wrap($fh, function() use ($connection) {
$connection->write('');
});
if (!is_resource($fh)) {
throw new Exception("Failed to wrap file output");
}
return $fh;
}
/**
* Open a writable stream to a remote file
*
* @param string $target
* @return resource a write only stream to upload a remote file
*
* @throws NotFoundException
* @throws InvalidTypeException
*/
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
$connection = $this->getConnection();
$fh = $connection->getFileInputStream();
$connection->write('put ' . $this->system->getFD(4) . ' ' . $target);
$connection->write('exit');
// use a close callback to ensure the upload is finished before continuing
// this also serves as a way to keep the connection in scope
$stream = CallbackWrapper::wrap($fh, function() use ($connection) {
$connection->write('');
}, 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);
}
}
/**
* Append to stream
* Note: smbclient does not support this (Use php-libsmbclient)
*
* @param string $target
*
* @throws DependencyException
*/
public function append(string $target) {
throw new DependencyException('php-libsmbclient is required for append');
}
/**
* @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(string $path, int $mode) {
$modeString = '';
foreach (self::MODE_MAP as $modeByte => $string) {
if ($mode & $modeByte) {
$modeString .= $string;
}
}
$path = $this->escapePath($path);
// first reset the mode to normal
$cmd = 'setmode ' . $path . ' -rsha';
$output = $this->execute($cmd);
$this->parseOutput($output, $path);
if ($mode !== FileInfo::MODE_NORMAL) {
// then set the modes we want
$cmd = 'setmode ' . $path . ' ' . $modeString;
$output = $this->execute($cmd);
return $this->parseOutput($output, $path);
} else {
return true;
}
}
/**
* @param string $path
* @return INotifyHandler
* @throws ConnectionException
* @throws DependencyException
*/
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');
}
$connection = $this->getConnection(); // use a fresh connection since the notify command blocks the process
$command = 'notify ' . $this->escapePath($path);
$connection->write($command . PHP_EOL);
return new NotifyHandler($connection, $path);
}
/**
* @param string $command
* @return string[]
*/
protected function execute(string $command): array {
$this->connect()->write($command);
return $this->connect()->read();
}
/**
* check output for errors
*
* @param string[] $lines
* @param string $path
*
* @return bool
* @throws AlreadyExistsException
* @throws \Icewind\SMB\Exception\AccessDeniedException
* @throws \Icewind\SMB\Exception\NotEmptyException
* @throws InvalidTypeException
* @throws \Icewind\SMB\Exception\Exception
* @throws NotFoundException
*/
protected function parseOutput(array $lines, string $path = ''): bool {
if (count($lines) === 0) {
return true;
} else {
$this->parser->checkForError($lines, $path);
}
}
/**
* @param string $string
* @return string
*/
protected function escape(string $string): string {
return escapeshellarg($string);
}
/**
* @param string $path
* @return string
*/
protected function escapePath(string $path): string {
$this->verifyPath($path);
if ($path === '/') {
$path = '';
}
$path = str_replace('/', '\\', $path);
$path = str_replace('"', '^"', $path);
$path = ltrim($path, '\\');
return '"' . $path . '"';
}
/**
* @param string $path
* @return string
*/
protected function escapeLocalPath(string $path): string {
$path = str_replace('"', '\"', $path);
return '"' . $path . '"';
}
/**
* @param string $path
* @return ACL[]
* @throws ConnectionException
* @throws ConnectException
*/
protected function getAcls(string $path): array {
$commandPath = $this->system->getSmbcAclsPath();
if (!$commandPath) {
return [];
}
$command = sprintf(
'%s %s %s %s/%s %s',
$commandPath,
$this->getAuthFileArgument(),
$this->server->getAuth()->getExtraCommandLineArguments(),
escapeshellarg('//' . $this->server->getHost()),
escapeshellarg($this->name),
escapeshellarg($path)
);
$connection = new RawConnection($command);
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
$connection->connect();
if (!$connection->isValid()) {
throw new ConnectionException((string)$connection->readLine());
}
$rawAcls = $connection->readAll();
return $this->parser->parseACLs($rawAcls);
}
public function getServer(): IServer {
return $this->server;
}
public function __destruct() {
unset($this->connection);
}
}
@@ -0,0 +1,7 @@
{
"name": "icewind/streams-dummy",
"provide": {
"icewind/streams": "0.2"
}
}
@@ -0,0 +1,6 @@
.idea
vendor
composer.lock
build
example.php
*.cache
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Robin Appelman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,52 @@
# Streams #
[![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.
## CallBackWrapper ##
A `CallBackWrapper` can be used to register callbacks on read, write and closing of the stream,
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 ###
```php
<?php
use \Icewind\Streams\CallBackWrapper;
require('vendor/autoload.php');
// get an existing stream to wrap
$source = fopen('php://temp', 'r+');
// register the callbacks
$stream = CallbackWrapper::wrap($source,
// read callback
function ($count) {
echo "read " . $count . "bytes\n";
},
// write callback
function ($data) {
echo "wrote '" . $data . "'\n";
},
// close callback
function () {
echo "stream closed\n";
});
fwrite($stream, 'some dummy data');
rewind($stream);
fread($stream, 5);
fclose($stream);
```
Note: due to php's internal stream buffering the `$count` passed to the read callback
will be equal to php's internal buffer size (8192 on default) an not the number of bytes
requested by `fopen()`
@@ -0,0 +1,29 @@
{
"name": "icewind/streams",
"description": "A set of generic stream wrappers",
"license": "MIT",
"authors": [
{
"name": "Robin Appelman",
"email": "icewind@owncloud.com"
}
],
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^9",
"friendsofphp/php-cs-fixer": "^2",
"phpstan/phpstan": "^0.12"
},
"autoload": {
"psr-4": {
"Icewind\\Streams\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Icewind\\Streams\\Tests\\": "tests/"
}
}
}
@@ -0,0 +1,133 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Wrapper that provides callbacks for write, read and close
*
* The following options should be passed in the context when opening the stream
* [
* 'callback' => [
* 'source' => resource
* 'read' => function($count){} (optional)
* 'write' => function($data){} (optional)
* 'close' => function(){} (optional)
* 'readdir' => function(){} (optional)
* ]
* ]
*
* All callbacks are called after the operation is executed on the source stream
*/
class CallbackWrapper extends Wrapper {
/**
* @var callable|null
*/
protected $readCallback;
/**
* @var callable|null
*/
protected $writeCallback;
/**
* @var callable|null
*/
protected $closeCallback;
/**
* @var callable|null
*/
protected $readDirCallBack;
/**
* @var callable|null
*/
protected $preCloseCallback;
/**
* Wraps a stream with the provided callbacks
*
* @param resource $source
* @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|false
*
*/
public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null, $preClose = null) {
$context = [
'source' => $source,
'read' => $read,
'write' => $write,
'close' => $close,
'readDir' => $readDir,
'preClose' => $preClose,
];
return self::wrapSource($source, $context);
}
protected function open() {
$context = $this->loadContext();
$this->readCallback = $context['read'];
$this->writeCallback = $context['write'];
$this->closeCallback = $context['close'];
$this->readDirCallBack = $context['readDir'];
$this->preCloseCallback = $context['preClose'];
return true;
}
public function dir_opendir($path, $options) {
return $this->open();
}
public function stream_open($path, $mode, $options, &$opened_path) {
return $this->open();
}
public function stream_read($count) {
$result = parent::stream_read($count);
if (is_callable($this->readCallback)) {
call_user_func($this->readCallback, strlen($result));
}
return $result;
}
public function stream_write($data) {
$result = parent::stream_write($data);
if (is_callable($this->writeCallback)) {
call_user_func($this->writeCallback, $data);
}
return $result;
}
public function stream_close() {
if (is_callable($this->preCloseCallback)) {
call_user_func($this->preCloseCallback, $this->source);
// prevent further calls by potential PHP 7 GC ghosts
$this->preCloseCallback = null;
}
$result = parent::stream_close();
if (is_callable($this->closeCallback)) {
call_user_func($this->closeCallback);
// prevent further calls by potential PHP 7 GC ghosts
$this->closeCallback = null;
}
return $result;
}
public function dir_readdir() {
$result = parent::dir_readdir();
if (is_callable($this->readDirCallBack)) {
call_user_func($this->readDirCallBack);
}
return $result;
}
}
@@ -0,0 +1,103 @@
<?php
/**
* @copyright Copyright (c) 2018 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 counts the amount of data read and written
*
* The following options should be passed in the context when opening the stream
* [
* 'callback' => [
* 'source' => resource
* 'callback' => function($readCount, $writeCount){}
* ]
* ]
*
* The callback will be called when the stream is closed
*/
class CountWrapper extends Wrapper {
/**
* @var int
*/
protected $readCount = 0;
/**
* @var int
*/
protected $writeCount = 0;
/**
* @var callable
*/
protected $callback;
/**
* Wraps a stream with the provided callbacks
*
* @param resource $source
* @param callable $callback
* @return resource|false
*
* @throws \BadMethodCallException
*/
public static function wrap($source, $callback) {
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Invalid or missing callback');
}
return self::wrapSource($source, [
'source' => $source,
'callback' => $callback
]);
}
protected function open() {
$context = $this->loadContext();
$this->callback = $context['callback'];
return true;
}
public function dir_opendir($path, $options) {
return $this->open();
}
public function stream_open($path, $mode, $options, &$opened_path) {
return $this->open();
}
public function stream_read($count) {
$result = parent::stream_read($count);
$this->readCount += strlen($result);
return $result;
}
public function stream_write($data) {
$result = parent::stream_write($data);
$this->writeCount += strlen($data);
return $result;
}
public function stream_close() {
$result = parent::stream_close();
call_user_func($this->callback, $this->readCount, $this->writeCount);
return $result;
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Interface for stream wrappers that implements a directory
*/
interface Directory {
/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options);
/**
* @return string|bool
*/
public function dir_readdir();
/**
* @return bool
*/
public function dir_closedir();
/**
* @return bool
*/
public function dir_rewinddir();
}
@@ -0,0 +1,57 @@
<?php
/**
* Copyright (c) 2015 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Wrapper allows filtering of directories
*
* The filter callback will be called for each entry in the folder
* when the callback return false the entry will be filtered out
*/
class DirectoryFilter extends DirectoryWrapper {
/**
* @var callable
*/
private $filter;
/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options) {
$context = $this->loadContext();
$this->filter = $context['filter'];
return true;
}
/**
* @return string
*/
public function dir_readdir() {
$file = readdir($this->source);
$filter = $this->filter;
// 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);
}
return $file;
}
/**
* @param resource $source
* @param callable $filter
* @return resource|false
*/
public static function wrap($source, callable $filter) {
return self::wrapSource($source, [
'source' => $source,
'filter' => $filter
]);
}
}
@@ -0,0 +1,47 @@
<?php
/**
* Copyright (c) 2015 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
class DirectoryWrapper extends Wrapper implements Directory {
public function stream_open($path, $mode, $options, &$opened_path) {
return false;
}
/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options) {
$this->loadContext();
return true;
}
/**
* @return string|false
*/
public function dir_readdir() {
return readdir($this->source);
}
/**
* @return bool
*/
public function dir_closedir() {
closedir($this->source);
return true;
}
/**
* @return bool
*/
public function dir_rewinddir() {
rewinddir($this->source);
return true;
}
}
@@ -0,0 +1,86 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Interface for stream wrappers that implements a file
*/
interface File {
/**
* @param string $path
* @param string $mode
* @param int $options
* @param string $opened_path
* @return bool
*/
public function stream_open($path, $mode, $options, &$opened_path);
/**
* @param int $offset
* @param int $whence
* @return bool
*/
public function stream_seek($offset, $whence = SEEK_SET);
/**
* @return int|false
*/
public function stream_tell();
/**
* @param int $count
* @return string|false
*/
public function stream_read($count);
/**
* @param string $data
* @return int|false
*/
public function stream_write($data);
/**
* @param int $option
* @param int $arg1
* @param int $arg2
* @return bool
*/
public function stream_set_option($option, $arg1, $arg2);
/**
* @param int $size
* @return bool
*/
public function stream_truncate($size);
/**
* @return array|false
*/
public function stream_stat();
/**
* @param int $operation
* @return bool
*/
public function stream_lock($operation);
/**
* @return bool
*/
public function stream_flush();
/**
* @return bool
*/
public function stream_eof();
/**
* @return bool
*/
public function stream_close();
}
@@ -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|false
*
* @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();
}
}
@@ -0,0 +1,113 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Create a directory handle from an iterator or array
*
* The following options should be passed in the context when opening the stream
* [
* 'dir' => [
* 'array' => string[]
* 'iterator' => \Iterator
* ]
* ]
*
* Either 'array' or 'iterator' need to be set, if both are set, 'iterator' takes preference
*/
class IteratorDirectory extends WrapperHandler implements Directory {
/**
* @var resource
*/
public $context;
/**
* @var \Iterator
*/
protected $iterator;
/**
* Load the source from the stream context and return the context options
*
* @param string $name
* @return array
* @throws \BadMethodCallException
*/
protected function loadContext($name = null) {
$context = parent::loadContext($name);
if (isset($context['iterator'])) {
$this->iterator = $context['iterator'];
} elseif (isset($context['array'])) {
$this->iterator = new \ArrayIterator($context['array']);
} else {
throw new \BadMethodCallException('Invalid context, iterator or array not set');
}
return $context;
}
/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options) {
$this->loadContext();
return true;
}
/**
* @return string|bool
*/
public function dir_readdir() {
if ($this->iterator->valid()) {
$result = $this->iterator->current();
$this->iterator->next();
return $result;
} else {
return false;
}
}
/**
* @return bool
*/
public function dir_closedir() {
return true;
}
/**
* @return bool
*/
public function dir_rewinddir() {
$this->iterator->rewind();
return true;
}
/**
* Creates a directory handle from the provided array or iterator
*
* @param \Iterator | array $source
* @return resource|false
*
* @throws \BadMethodCallException
*/
public static function wrap($source) {
if ($source instanceof \Iterator) {
$options = [
'iterator' => $source
];
} elseif (is_array($source)) {
$options = [
'array' => $source
];
} else {
throw new \BadMethodCallException('$source should be an Iterator or array');
}
return self::wrapSource(self::NO_SOURCE_DIR, $options);
}
}
@@ -0,0 +1,27 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Stream wrapper that does nothing, used for tests
*/
class NullWrapper extends Wrapper {
public static function wrap($source) {
return self::wrapSource($source);
}
public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext();
return true;
}
public function dir_opendir($path, $options) {
$this->loadContext();
return true;
}
}
@@ -0,0 +1,108 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* A string-like object that automatically registers a stream wrapper when used and removes the stream wrapper when no longer used
*
* Can optionally pass context options to the stream wrapper
*/
class Path {
/**
* @var bool
*/
protected $registered = false;
/**
* @var string
*/
protected $protocol;
/**
* @var string
*/
protected $class;
/**
* @var array
*/
protected $contextOptions;
/**
* @param string $class
* @param array $contextOptions
*/
public function __construct($class, $contextOptions = []) {
$this->class = $class;
$this->contextOptions = $contextOptions;
}
public function getProtocol() {
if (!$this->protocol) {
$this->protocol = 'auto' . uniqid();
}
return $this->protocol;
}
public function wrapPath($path) {
return $this->getProtocol() . '://' . $path;
}
protected function register() {
if (!$this->registered) {
$this->appendDefaultContent($this->contextOptions);
stream_wrapper_register($this->getProtocol(), $this->class);
$this->registered = true;
}
}
protected function unregister() {
stream_wrapper_unregister($this->getProtocol());
$this->unsetDefaultContent($this->getProtocol());
$this->registered = false;
}
/**
* Add values to the default stream context
*
* @param array $values
*/
protected function appendDefaultContent($values) {
if (!is_array(current($values))) {
$values = [$this->getProtocol() => $values];
}
$context = stream_context_get_default();
$defaults = stream_context_get_options($context);
foreach ($values as $key => $value) {
$defaults[$key] = $value;
}
stream_context_set_default($defaults);
}
/**
* Remove values from the default stream context
*
* @param string $key
*/
protected function unsetDefaultContent($key) {
$context = stream_context_get_default();
$defaults = stream_context_get_options($context);
unset($defaults[$key]);
stream_context_set_default($defaults);
}
public function __toString() {
$this->register();
return $this->protocol . '://';
}
public function __destruct() {
$this->unregister();
}
}
@@ -0,0 +1,23 @@
<?php
/**
* Copyright (c) 2016 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* A string-like object that maps to an existing stream when opened
*/
class PathWrapper extends NullWrapper {
/**
* @param resource $source
* @return Path|string
*/
public static function getPath($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;
}
}
@@ -0,0 +1,52 @@
<?php
/**
* Copyright (c) 2016 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Wrapper that retries reads/writes to remote streams that dont deliver/recieve all requested data at once
*/
class RetryWrapper extends Wrapper {
public static function wrap($source) {
return self::wrapSource($source);
}
public function dir_opendir($path, $options) {
return false;
}
public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext();
return true;
}
public function stream_read($count) {
$result = parent::stream_read($count);
$bytesReceived = strlen($result);
while (strlen($result) > 0 && $bytesReceived < $count && !$this->stream_eof()) {
$result .= parent::stream_read($count - $bytesReceived);
$bytesReceived = strlen($result);
}
return $result;
}
public function stream_write($data) {
$bytesToSend = strlen($data);
$bytesWritten = parent::stream_write($data);
$result = $bytesWritten;
while ($bytesWritten > 0 && $result < $bytesToSend && !$this->stream_eof()) {
$dataLeft = substr($data, $result);
$bytesWritten = parent::stream_write($dataLeft);
$result += $bytesWritten;
}
return $result;
}
}

Some files were not shown because too many files have changed in this diff Show More