@@ -0,0 +1,7 @@ | |||
<?php | |||
// autoload.php @generated by Composer | |||
require_once __DIR__ . '/composer' . '/autoload_real.php'; | |||
return ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3::getLoader(); |
@@ -0,0 +1,13 @@ | |||
{ | |||
"name": "files_external/3rdparty", | |||
"description": "3rdparty components for files_external", | |||
"license": "MIT", | |||
"config": { | |||
"vendor-dir": "." | |||
}, | |||
"require": { | |||
"icewind/smb": "dev-master", | |||
"icewind/streams": "0.2" | |||
} | |||
} | |||
@@ -0,0 +1,101 @@ | |||
{ | |||
"_readme": [ | |||
"This file locks the dependencies of your project to a known state", | |||
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", | |||
"This file is @generated automatically" | |||
], | |||
"hash": "c854ee7f5bdcb3f2c8ee0a8cfe5e193a", | |||
"packages": [ | |||
{ | |||
"name": "icewind/smb", | |||
"version": "dev-master", | |||
"source": { | |||
"type": "git", | |||
"url": "https://github.com/icewind1991/SMB.git", | |||
"reference": "ededbfbaa3d7124ce8d4b6c119cd225fa342916d" | |||
}, | |||
"dist": { | |||
"type": "zip", | |||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/ededbfbaa3d7124ce8d4b6c119cd225fa342916d", | |||
"reference": "ededbfbaa3d7124ce8d4b6c119cd225fa342916d", | |||
"shasum": "" | |||
}, | |||
"require": { | |||
"icewind/streams": "0.2.x", | |||
"php": ">=5.3" | |||
}, | |||
"require-dev": { | |||
"satooshi/php-coveralls": "dev-master" | |||
}, | |||
"type": "library", | |||
"autoload": { | |||
"psr-4": { | |||
"Icewind\\SMB\\": "src/", | |||
"Icewind\\SMB\\Test\\": "tests/" | |||
} | |||
}, | |||
"notification-url": "https://packagist.org/downloads/", | |||
"license": [ | |||
"MIT" | |||
], | |||
"authors": [ | |||
{ | |||
"name": "Robin Appelman", | |||
"email": "icewind@owncloud.com" | |||
} | |||
], | |||
"description": "php wrapper for smbclient and libsmbclient-php", | |||
"time": "2015-02-10 16:37:37" | |||
}, | |||
{ | |||
"name": "icewind/streams", | |||
"version": "0.2", | |||
"source": { | |||
"type": "git", | |||
"url": "https://github.com/icewind1991/Streams.git", | |||
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a" | |||
}, | |||
"dist": { | |||
"type": "zip", | |||
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a", | |||
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a", | |||
"shasum": "" | |||
}, | |||
"require": { | |||
"php": ">=5.3" | |||
}, | |||
"require-dev": { | |||
"satooshi/php-coveralls": "dev-master" | |||
}, | |||
"type": "library", | |||
"autoload": { | |||
"psr-4": { | |||
"Icewind\\Streams\\Tests\\": "tests/", | |||
"Icewind\\Streams\\": "src/" | |||
} | |||
}, | |||
"notification-url": "https://packagist.org/downloads/", | |||
"license": [ | |||
"MIT" | |||
], | |||
"authors": [ | |||
{ | |||
"name": "Robin Appelman", | |||
"email": "icewind@owncloud.com" | |||
} | |||
], | |||
"description": "A set of generic stream wrappers", | |||
"time": "2014-07-30 23:46:15" | |||
} | |||
], | |||
"packages-dev": [], | |||
"aliases": [], | |||
"minimum-stability": "stable", | |||
"stability-flags": { | |||
"icewind/smb": 20 | |||
}, | |||
"prefer-stable": false, | |||
"prefer-lowest": false, | |||
"platform": [], | |||
"platform-dev": [] | |||
} |
@@ -0,0 +1,413 @@ | |||
<?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 class loader | |||
* | |||
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md | |||
* | |||
* $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> | |||
*/ | |||
class ClassLoader | |||
{ | |||
// PSR-4 | |||
private $prefixLengthsPsr4 = array(); | |||
private $prefixDirsPsr4 = array(); | |||
private $fallbackDirsPsr4 = array(); | |||
// PSR-0 | |||
private $prefixesPsr0 = array(); | |||
private $fallbackDirsPsr0 = array(); | |||
private $useIncludePath = false; | |||
private $classMap = array(); | |||
private $classMapAuthoritative = false; | |||
public function getPrefixes() | |||
{ | |||
if (!empty($this->prefixesPsr0)) { | |||
return call_user_func_array('array_merge', $this->prefixesPsr0); | |||
} | |||
return array(); | |||
} | |||
public function getPrefixesPsr4() | |||
{ | |||
return $this->prefixDirsPsr4; | |||
} | |||
public function getFallbackDirs() | |||
{ | |||
return $this->fallbackDirsPsr0; | |||
} | |||
public function getFallbackDirsPsr4() | |||
{ | |||
return $this->fallbackDirsPsr4; | |||
} | |||
public function getClassMap() | |||
{ | |||
return $this->classMap; | |||
} | |||
/** | |||
* @param array $classMap Class to filename map | |||
*/ | |||
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 array|string $paths The PSR-0 root directories | |||
* @param bool $prepend Whether to prepend the directories | |||
*/ | |||
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 array|string $paths The PSR-0 base directories | |||
* @param bool $prepend Whether to prepend the directories | |||
* | |||
* @throws \InvalidArgumentException | |||
*/ | |||
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 array|string $paths The PSR-0 base directories | |||
*/ | |||
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 array|string $paths The PSR-4 base directories | |||
* | |||
* @throws \InvalidArgumentException | |||
*/ | |||
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 | |||
*/ | |||
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 | |||
*/ | |||
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; | |||
} | |||
/** | |||
* Registers this instance as an autoloader. | |||
* | |||
* @param bool $prepend Whether to prepend the autoloader or not | |||
*/ | |||
public function register($prepend = false) | |||
{ | |||
spl_autoload_register(array($this, 'loadClass'), true, $prepend); | |||
} | |||
/** | |||
* Unregisters this instance as an autoloader. | |||
*/ | |||
public function unregister() | |||
{ | |||
spl_autoload_unregister(array($this, 'loadClass')); | |||
} | |||
/** | |||
* Loads the given class or interface. | |||
* | |||
* @param string $class The name of the class | |||
* @return bool|null True if loaded, null otherwise | |||
*/ | |||
public function loadClass($class) | |||
{ | |||
if ($file = $this->findFile($class)) { | |||
includeFile($file); | |||
return true; | |||
} | |||
} | |||
/** | |||
* 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) | |||
{ | |||
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 | |||
if ('\\' == $class[0]) { | |||
$class = substr($class, 1); | |||
} | |||
// class map lookup | |||
if (isset($this->classMap[$class])) { | |||
return $this->classMap[$class]; | |||
} | |||
if ($this->classMapAuthoritative) { | |||
return false; | |||
} | |||
$file = $this->findFileWithExtension($class, '.php'); | |||
// Search for Hack files if we are running on HHVM | |||
if ($file === null && defined('HHVM_VERSION')) { | |||
$file = $this->findFileWithExtension($class, '.hh'); | |||
} | |||
if ($file === null) { | |||
// Remember that this class does not exist. | |||
return $this->classMap[$class] = false; | |||
} | |||
return $file; | |||
} | |||
private function findFileWithExtension($class, $ext) | |||
{ | |||
// PSR-4 lookup | |||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; | |||
$first = $class[0]; | |||
if (isset($this->prefixLengthsPsr4[$first])) { | |||
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { | |||
if (0 === strpos($class, $prefix)) { | |||
foreach ($this->prefixDirsPsr4[$prefix] as $dir) { | |||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { | |||
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; | |||
} | |||
} | |||
} | |||
/** | |||
* Scope isolated include. | |||
* | |||
* Prevents access to $this/self from included files. | |||
*/ | |||
function includeFile($file) | |||
{ | |||
include $file; | |||
} |
@@ -0,0 +1,9 @@ | |||
<?php | |||
// autoload_classmap.php @generated by Composer | |||
$vendorDir = dirname(dirname(__FILE__)); | |||
$baseDir = $vendorDir; | |||
return array( | |||
); |
@@ -0,0 +1,9 @@ | |||
<?php | |||
// autoload_namespaces.php @generated by Composer | |||
$vendorDir = dirname(dirname(__FILE__)); | |||
$baseDir = $vendorDir; | |||
return array( | |||
); |
@@ -0,0 +1,13 @@ | |||
<?php | |||
// autoload_psr4.php @generated by Composer | |||
$vendorDir = dirname(dirname(__FILE__)); | |||
$baseDir = $vendorDir; | |||
return array( | |||
'Icewind\\Streams\\Tests\\' => array($vendorDir . '/icewind/streams/tests'), | |||
'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'), | |||
'Icewind\\SMB\\Test\\' => array($vendorDir . '/icewind/smb/tests'), | |||
'Icewind\\SMB\\' => array($vendorDir . '/icewind/smb/src'), | |||
); |
@@ -0,0 +1,50 @@ | |||
<?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'; | |||
} | |||
} | |||
public static function getLoader() | |||
{ | |||
if (null !== self::$loader) { | |||
return self::$loader; | |||
} | |||
spl_autoload_register(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader'), true, true); | |||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); | |||
spl_autoload_unregister(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader')); | |||
$map = require __DIR__ . '/autoload_namespaces.php'; | |||
foreach ($map as $namespace => $path) { | |||
$loader->set($namespace, $path); | |||
} | |||
$map = require __DIR__ . '/autoload_psr4.php'; | |||
foreach ($map as $namespace => $path) { | |||
$loader->setPsr4($namespace, $path); | |||
} | |||
$classMap = require __DIR__ . '/autoload_classmap.php'; | |||
if ($classMap) { | |||
$loader->addClassMap($classMap); | |||
} | |||
$loader->register(true); | |||
return $loader; | |||
} | |||
} | |||
function composerRequire98fe9b281934250b3a93f69a5ce843b3($file) | |||
{ | |||
require $file; | |||
} |
@@ -0,0 +1,87 @@ | |||
[ | |||
{ | |||
"name": "icewind/streams", | |||
"version": "0.2", | |||
"version_normalized": "0.2.0.0", | |||
"source": { | |||
"type": "git", | |||
"url": "https://github.com/icewind1991/Streams.git", | |||
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a" | |||
}, | |||
"dist": { | |||
"type": "zip", | |||
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a", | |||
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a", | |||
"shasum": "" | |||
}, | |||
"require": { | |||
"php": ">=5.3" | |||
}, | |||
"require-dev": { | |||
"satooshi/php-coveralls": "dev-master" | |||
}, | |||
"time": "2014-07-30 23:46:15", | |||
"type": "library", | |||
"installation-source": "dist", | |||
"autoload": { | |||
"psr-4": { | |||
"Icewind\\Streams\\Tests\\": "tests/", | |||
"Icewind\\Streams\\": "src/" | |||
} | |||
}, | |||
"notification-url": "https://packagist.org/downloads/", | |||
"license": [ | |||
"MIT" | |||
], | |||
"authors": [ | |||
{ | |||
"name": "Robin Appelman", | |||
"email": "icewind@owncloud.com" | |||
} | |||
], | |||
"description": "A set of generic stream wrappers" | |||
}, | |||
{ | |||
"name": "icewind/smb", | |||
"version": "dev-master", | |||
"version_normalized": "9999999-dev", | |||
"source": { | |||
"type": "git", | |||
"url": "https://github.com/icewind1991/SMB.git", | |||
"reference": "ededbfbaa3d7124ce8d4b6c119cd225fa342916d" | |||
}, | |||
"dist": { | |||
"type": "zip", | |||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/ededbfbaa3d7124ce8d4b6c119cd225fa342916d", | |||
"reference": "ededbfbaa3d7124ce8d4b6c119cd225fa342916d", | |||
"shasum": "" | |||
}, | |||
"require": { | |||
"icewind/streams": "0.2.x", | |||
"php": ">=5.3" | |||
}, | |||
"require-dev": { | |||
"satooshi/php-coveralls": "dev-master" | |||
}, | |||
"time": "2015-02-10 16:37:37", | |||
"type": "library", | |||
"installation-source": "source", | |||
"autoload": { | |||
"psr-4": { | |||
"Icewind\\SMB\\": "src/", | |||
"Icewind\\SMB\\Test\\": "tests/" | |||
} | |||
}, | |||
"notification-url": "https://packagist.org/downloads/", | |||
"license": [ | |||
"MIT" | |||
], | |||
"authors": [ | |||
{ | |||
"name": "Robin Appelman", | |||
"email": "icewind@owncloud.com" | |||
} | |||
], | |||
"description": "php wrapper for smbclient and libsmbclient-php" | |||
} | |||
] |
@@ -0,0 +1,2 @@ | |||
.idea | |||
vendor |
@@ -0,0 +1,50 @@ | |||
language: php | |||
php: | |||
- 5.3 | |||
- 5.4 | |||
- 5.5 | |||
env: | |||
global: | |||
- CURRENT_DIR=`pwd` | |||
before_install: | |||
- pass=$(perl -e 'print crypt("test", "password")') | |||
- sudo useradd -m -p $pass test | |||
- sudo apt-get update -qq | |||
- sudo apt-get install samba smbclient libsmbclient-dev libsmbclient | |||
- wget -O /tmp/libsmbclient-php.zip https://github.com/eduardok/libsmbclient-php/archive/master.zip | |||
- unzip /tmp/libsmbclient-php.zip -d /tmp | |||
- cd /tmp/libsmbclient-php-master | |||
- phpize && ./configure && make && sudo make install | |||
- echo 'extension="libsmbclient.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini | |||
- cd $CURRENT_DIR | |||
- chmod go+w $HOME | |||
- printf "%s\n%s\n" test test|sudo smbpasswd -s test | |||
- sudo mkdir /home/test/test | |||
- sudo chown test /home/test/test | |||
- | | |||
echo "[test] | |||
comment = test | |||
path = /home/test | |||
guest ok = yes | |||
writeable = yes | |||
map archive = yes | |||
map system = yes | |||
map hidden = yes | |||
create mask = 0777 | |||
inherit permissions = yes" | sudo tee -a /etc/samba/smb.conf | |||
- sudo service smbd restart | |||
- testparm -s | |||
install: | |||
- composer install --dev --no-interaction | |||
script: | |||
- mkdir -p build/logs | |||
- cd tests | |||
- phpunit --coverage-clover ../build/logs/clover.xml --configuration phpunit.xml | |||
after_script: | |||
- cd $CURRENT_DIR | |||
- php vendor/bin/coveralls -v |
@@ -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,136 @@ | |||
SMB | |||
=== | |||
[![Coverage Status](https://img.shields.io/coveralls/icewind1991/SMB.svg)](https://coveralls.io/r/icewind1991/SMB?branch=master) | |||
[![Build Status](https://travis-ci.org/icewind1991/SMB.svg?branch=master)](https://travis-ci.org/icewind1991/SMB) | |||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/icewind1991/SMB/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/icewind1991/SMB/?branch=master) | |||
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 | |||
---- | |||
### Upload a file ### | |||
```php | |||
<?php | |||
use Icewind\SMB\Server; | |||
require('vendor/autoload.php'); | |||
$fileToUpload = __FILE__; | |||
$server = new Server('localhost', 'test', 'test'); | |||
$share = $server->getShare('test'); | |||
$share->put($fileToUpload, 'example.txt'); | |||
``` | |||
### Download a file ### | |||
```php | |||
<?php | |||
use Icewind\SMB\Server; | |||
require('vendor/autoload.php'); | |||
$target = __DIR__ . '/target.txt'; | |||
$server = new Server('localhost', 'test', 'test'); | |||
$share = $server->getShare('test'); | |||
$share->get('example.txt', $target); | |||
``` | |||
### List shares on the remote server ### | |||
```php | |||
<?php | |||
use Icewind\SMB\Server; | |||
require('vendor/autoload.php'); | |||
$server = new Server('localhost', 'test', 'test'); | |||
$shares = $server->listShares(); | |||
foreach ($shares as $share) { | |||
echo $share->getName() . "\n"; | |||
} | |||
``` | |||
### List the content of a folder ### | |||
```php | |||
<?php | |||
use Icewind\SMB\Server; | |||
require('vendor/autoload.php'); | |||
$server = new Server('localhost', 'test', 'test'); | |||
$share = $server->getShare('test'); | |||
$content = $share->dir('test'); | |||
foreach ($content as $info) { | |||
echo $name->getName() . "\n"; | |||
echo "\tsize :" . $info->getSize() . "\n"; | |||
} | |||
``` | |||
### Using read streams | |||
```php | |||
<?php | |||
use Icewind\SMB\Server; | |||
require('vendor/autoload.php'); | |||
$server = new Server('localhost', 'test', 'test'); | |||
$share = $server->getShare('test'); | |||
$fh = $share->read('test.txt'); | |||
echo fread($fh, 4086); | |||
fclose($fh); | |||
``` | |||
### Using write streams | |||
```php | |||
<?php | |||
use Icewind\SMB\Server; | |||
require('vendor/autoload.php'); | |||
$server = new Server('localhost', 'test', 'test'); | |||
$share = $server->getShare('test'); | |||
$fh = $share->write('test.txt'); | |||
fwrite($fh, 'bar'); | |||
fclose($fh); | |||
``` | |||
### Using libsmbclient-php ### | |||
Install [libsmbclient-php](https://github.com/eduardok/libsmbclient-php) | |||
```php | |||
<?php | |||
use Icewind\SMB\Server; | |||
use Icewind\SMB\NativeServer; | |||
require('vendor/autoload.php'); | |||
$fileToUpload = __FILE__; | |||
if (Server::NativeAvailable()) { | |||
$server = new NativeServer('localhost', 'test', 'test'); | |||
} else { | |||
echo 'libsmbclient-php not available, falling back to wrapping smbclient'; | |||
$server = new Server('localhost', 'test', 'test'); | |||
} | |||
$share = $server->getShare('test'); | |||
$share->put($fileToUpload, 'example.txt'); | |||
``` |
@@ -0,0 +1,24 @@ | |||
{ | |||
"name" : "icewind/smb", | |||
"description" : "php wrapper for smbclient and libsmbclient-php", | |||
"license" : "MIT", | |||
"authors" : [ | |||
{ | |||
"name" : "Robin Appelman", | |||
"email": "icewind@owncloud.com" | |||
} | |||
], | |||
"require" : { | |||
"php": ">=5.3", | |||
"icewind/streams": "0.2.x" | |||
}, | |||
"require-dev": { | |||
"satooshi/php-coveralls" : "dev-master" | |||
}, | |||
"autoload" : { | |||
"psr-4": { | |||
"Icewind\\SMB\\": "src/", | |||
"Icewind\\SMB\\Test\\": "tests/" | |||
} | |||
} | |||
} |
@@ -0,0 +1,78 @@ | |||
<?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\AuthenticationException; | |||
use Icewind\SMB\Exception\ConnectionException; | |||
use Icewind\SMB\Exception\InvalidHostException; | |||
class Connection extends RawConnection { | |||
const DELIMITER = 'smb:'; | |||
/** | |||
* send input to smbclient | |||
* | |||
* @param string $input | |||
*/ | |||
public function write($input) { | |||
parent::write($input . PHP_EOL); | |||
} | |||
/** | |||
* get all unprocessed output from smbclient until the next prompt | |||
* | |||
* @throws ConnectionException | |||
* @return string | |||
*/ | |||
public function read() { | |||
if (!$this->isValid()) { | |||
throw new ConnectionException(); | |||
} | |||
$line = $this->readLine(); //first line is prompt | |||
$this->checkConnectionError($line); | |||
$output = array(); | |||
$line = $this->readLine(); | |||
$length = mb_strlen(self::DELIMITER); | |||
while (mb_substr($line, 0, $length) !== self::DELIMITER) { //next prompt functions as delimiter | |||
$output[] .= $line; | |||
$line = $this->readLine(); | |||
} | |||
return $output; | |||
} | |||
/** | |||
* check if the first line holds a connection failure | |||
* | |||
* @param $line | |||
* @throws AuthenticationException | |||
* @throws InvalidHostException | |||
*/ | |||
private function checkConnectionError($line) { | |||
$line = rtrim($line, ')'); | |||
if (substr($line, -23) === ErrorCodes::LogonFailure) { | |||
throw new AuthenticationException(); | |||
} | |||
if (substr($line, -26) === ErrorCodes::BadHostName) { | |||
throw new InvalidHostException(); | |||
} | |||
if (substr($line, -22) === ErrorCodes::Unsuccessful) { | |||
throw new InvalidHostException(); | |||
} | |||
if (substr($line, -28) === ErrorCodes::ConnectionRefused) { | |||
throw new InvalidHostException(); | |||
} | |||
} | |||
public function close($terminate = true) { | |||
if (is_resource($this->getInputStream())) { | |||
$this->write('close' . PHP_EOL); | |||
} | |||
parent::close($terminate); | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
<?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; | |||
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 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'; | |||
} |
@@ -0,0 +1,10 @@ | |||
<?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,10 @@ | |||
<?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,10 @@ | |||
<?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,10 @@ | |||
<?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,10 @@ | |||
<?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,10 @@ | |||
<?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 Exception extends \Exception {} |
@@ -0,0 +1,10 @@ | |||
<?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,10 @@ | |||
<?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,10 @@ | |||
<?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,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\Exception; | |||
class InvalidRequestException extends Exception { | |||
/** | |||
* @var string | |||
*/ | |||
protected $path; | |||
/** | |||
* @param string $path | |||
* @param int $code | |||
*/ | |||
public function __construct($path, $code = 0) { | |||
parent::__construct('Invalid request for ' . $path, $code); | |||
$this->path = $path; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getPath() { | |||
return $this->path; | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
<?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 NoRouteToHostException extends ConnectException { | |||
} |
@@ -0,0 +1,10 @@ | |||
<?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,10 @@ | |||
<?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 TimedOutException extends ConnectException { | |||
} |
@@ -0,0 +1,126 @@ | |||
<?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; | |||
class FileInfo implements 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; | |||
/** | |||
* @var string | |||
*/ | |||
protected $path; | |||
/** | |||
* @var string | |||
*/ | |||
protected $name; | |||
/** | |||
* @var int | |||
*/ | |||
protected $size; | |||
/** | |||
* @var int | |||
*/ | |||
protected $time; | |||
/** | |||
* @var int | |||
*/ | |||
protected $mode; | |||
/** | |||
* @param string $path | |||
* @param string $name | |||
* @param int $size | |||
* @param int $time | |||
* @param int $mode | |||
*/ | |||
public function __construct($path, $name, $size, $time, $mode) { | |||
$this->path = $path; | |||
$this->name = $name; | |||
$this->size = $size; | |||
$this->time = $time; | |||
$this->mode = $mode; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getPath() { | |||
return $this->path; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getName() { | |||
return $this->name; | |||
} | |||
/** | |||
* @return int | |||
*/ | |||
public function getSize() { | |||
return $this->size; | |||
} | |||
/** | |||
* @return int | |||
*/ | |||
public function getMTime() { | |||
return $this->time; | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isDirectory() { | |||
return (bool)($this->mode & self::MODE_DIRECTORY); | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isReadOnly() { | |||
return (bool)($this->mode & self::MODE_READONLY); | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isHidden() { | |||
return (bool)($this->mode & self::MODE_HIDDEN); | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isSystem() { | |||
return (bool)($this->mode & self::MODE_SYSTEM); | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isArchived() { | |||
return (bool)($this->mode & self::MODE_ARCHIVE); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
<?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 { | |||
/** | |||
* @return string | |||
*/ | |||
public function getPath(); | |||
/** | |||
* @return string | |||
*/ | |||
public function getName(); | |||
/** | |||
* @return int | |||
*/ | |||
public function getSize(); | |||
/** | |||
* @return int | |||
*/ | |||
public function getMTime(); | |||
/** | |||
* @return bool | |||
*/ | |||
public function isDirectory(); | |||
/** | |||
* @return bool | |||
*/ | |||
public function isReadOnly(); | |||
/** | |||
* @return bool | |||
*/ | |||
public function isHidden(); | |||
/** | |||
* @return bool | |||
*/ | |||
public function isSystem(); | |||
/** | |||
* @return bool | |||
*/ | |||
public function isArchived(); | |||
} |
@@ -0,0 +1,134 @@ | |||
<?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 IShare { | |||
/** | |||
* Get the name of the share | |||
* | |||
* @return string | |||
*/ | |||
public function getName(); | |||
/** | |||
* Download a remote file | |||
* | |||
* @param string $source remove file | |||
* @param string $target local file | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function get($source, $target); | |||
/** | |||
* Upload a local file | |||
* | |||
* @param string $source local file | |||
* @param string $target remove file | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function put($source, $target); | |||
/** | |||
* Open a readable stream top a remote file | |||
* | |||
* @param string $source | |||
* @return resource a read only stream with the contents of the remote file | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function read($source); | |||
/** | |||
* Open a writable stream to a remote file | |||
* | |||
* @param string $target | |||
* @return resource a write only stream to upload a remote file | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function write($target); | |||
/** | |||
* Rename a remote file | |||
* | |||
* @param string $from | |||
* @param string $to | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||
*/ | |||
public function rename($from, $to); | |||
/** | |||
* Delete a file on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function del($path); | |||
/** | |||
* List the content of a remote folder | |||
* | |||
* @param $path | |||
* @return \Icewind\SMB\IFileInfo[] | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function dir($path); | |||
/** | |||
* @param string $path | |||
* @return \Icewind\SMB\IFileInfo | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function stat($path); | |||
/** | |||
* Create a folder on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||
*/ | |||
public function mkdir($path); | |||
/** | |||
* Remove a folder on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function rmdir($path); | |||
/** | |||
* @param string $path | |||
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL | |||
* @return mixed | |||
*/ | |||
public function setMode($path, $mode); | |||
} |
@@ -0,0 +1,142 @@ | |||
<?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; | |||
class NativeFileInfo implements IFileInfo { | |||
const MODE_FILE = 0100000; | |||
/** | |||
* @var string | |||
*/ | |||
protected $path; | |||
/** | |||
* @var string | |||
*/ | |||
protected $name; | |||
/** | |||
* @var \Icewind\SMB\NativeShare | |||
*/ | |||
protected $share; | |||
/** | |||
* @var array | null | |||
*/ | |||
protected $statCache; | |||
/** | |||
* @var int | |||
*/ | |||
protected $modeCache; | |||
/** | |||
* @param \Icewind\SMB\NativeShare $share | |||
* @param string $path | |||
* @param string $name | |||
* @param array $stat | |||
*/ | |||
public function __construct($share, $path, $name, $stat = null) { | |||
$this->share = $share; | |||
$this->path = $path; | |||
$this->name = $name; | |||
$this->statCache = $stat; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getPath() { | |||
return $this->path; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getName() { | |||
return $this->name; | |||
} | |||
/** | |||
* @return array | |||
*/ | |||
protected function stat() { | |||
if (!$this->statCache) { | |||
$this->statCache = $this->share->getStat($this->getPath()); | |||
} | |||
return $this->statCache; | |||
} | |||
/** | |||
* @return int | |||
*/ | |||
public function getSize() { | |||
$stat = $this->stat(); | |||
return $stat['size']; | |||
} | |||
/** | |||
* @return int | |||
*/ | |||
public function getMTime() { | |||
$stat = $this->stat(); | |||
return $stat['mtime']; | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isDirectory() { | |||
$stat = $this->stat(); | |||
return !($stat['mode'] & self::MODE_FILE); | |||
} | |||
/** | |||
* @return int | |||
*/ | |||
protected function getMode() { | |||
if (!$this->modeCache) { | |||
$attribute = $this->share->getAttribute($this->path, 'system.dos_attr.mode'); | |||
// parse hex string | |||
$this->modeCache = (int)hexdec(substr($attribute, 2)); | |||
} | |||
return $this->modeCache; | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isReadOnly() { | |||
$mode = $this->getMode(); | |||
return (bool)($mode & FileInfo::MODE_READONLY); | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isHidden() { | |||
$mode = $this->getMode(); | |||
return (bool)($mode & FileInfo::MODE_HIDDEN); | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isSystem() { | |||
$mode = $this->getMode(); | |||
return (bool)($mode & FileInfo::MODE_SYSTEM); | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
public function isArchived() { | |||
$mode = $this->getMode(); | |||
return (bool)($mode & FileInfo::MODE_ARCHIVE); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
<?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; | |||
class NativeServer extends Server { | |||
/** | |||
* @var \Icewind\SMB\NativeState | |||
*/ | |||
protected $state; | |||
/** | |||
* @param string $host | |||
* @param string $user | |||
* @param string $password | |||
*/ | |||
public function __construct($host, $user, $password) { | |||
parent::__construct($host, $user, $password); | |||
$this->state = new NativeState(); | |||
} | |||
protected function connect() { | |||
$user = $this->getUser(); | |||
$workgroup = null; | |||
if (strpos($user, '/')) { | |||
list($workgroup, $user) = explode($user, '/'); | |||
} | |||
$this->state->init($workgroup, $user, $this->getPassword()); | |||
} | |||
/** | |||
* @return \Icewind\SMB\IShare[] | |||
* @throws \Icewind\SMB\Exception\AuthenticationException | |||
* @throws \Icewind\SMB\Exception\InvalidHostException | |||
*/ | |||
public function listShares() { | |||
$this->connect(); | |||
$shares = array(); | |||
$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; | |||
} | |||
/** | |||
* @param string $name | |||
* @return \Icewind\SMB\IShare | |||
*/ | |||
public function getShare($name) { | |||
return new NativeShare($this, $name); | |||
} | |||
} |
@@ -0,0 +1,288 @@ | |||
<?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; | |||
class NativeShare implements IShare { | |||
/** | |||
* @var Server $server | |||
*/ | |||
private $server; | |||
/** | |||
* @var string $name | |||
*/ | |||
private $name; | |||
/** | |||
* @var \Icewind\SMB\NativeState $state | |||
*/ | |||
private $state; | |||
/** | |||
* @param Server $server | |||
* @param string $name | |||
*/ | |||
public function __construct($server, $name) { | |||
$this->server = $server; | |||
$this->name = $name; | |||
$this->state = new NativeState(); | |||
} | |||
/** | |||
* @throws \Icewind\SMB\Exception\ConnectionException | |||
* @throws \Icewind\SMB\Exception\AuthenticationException | |||
* @throws \Icewind\SMB\Exception\InvalidHostException | |||
*/ | |||
protected function connect() { | |||
if ($this->state and $this->state instanceof NativeShare) { | |||
return; | |||
} | |||
$user = $this->server->getUser(); | |||
if (strpos($user, '/')) { | |||
list($workgroup, $user) = explode('/', $user); | |||
} elseif (strpos($user, '\\')) { | |||
list($workgroup, $user) = explode('\\', $user); | |||
} else { | |||
$workgroup = null; | |||
} | |||
$this->state->init($workgroup, $user, $this->server->getPassword()); | |||
} | |||
/** | |||
* Get the name of the share | |||
* | |||
* @return string | |||
*/ | |||
public function getName() { | |||
return $this->name; | |||
} | |||
private function buildUrl($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 \Icewind\SMB\IFileInfo[] | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function dir($path) { | |||
$this->connect(); | |||
$files = array(); | |||
$dh = $this->state->opendir($this->buildUrl($path)); | |||
while ($file = $this->state->readdir($dh)) { | |||
$name = $file['name']; | |||
if ($name !== '.' and $name !== '..') { | |||
$files [] = new NativeFileInfo($this, $path . '/' . $name, $name); | |||
} | |||
} | |||
$this->state->closedir($dh); | |||
return $files; | |||
} | |||
/** | |||
* @param string $path | |||
* @return \Icewind\SMB\IFileInfo[] | |||
*/ | |||
public function stat($path) { | |||
return new NativeFileInfo($this, $path, basename($path), $this->getStat($path)); | |||
} | |||
public function getStat($path) { | |||
$this->connect(); | |||
return $this->state->stat($this->buildUrl($path)); | |||
} | |||
/** | |||
* Create a folder on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||
*/ | |||
public function mkdir($path) { | |||
$this->connect(); | |||
return $this->state->mkdir($this->buildUrl($path)); | |||
} | |||
/** | |||
* Remove a folder on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function rmdir($path) { | |||
$this->connect(); | |||
return $this->state->rmdir($this->buildUrl($path)); | |||
} | |||
/** | |||
* Delete a file on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function del($path) { | |||
return $this->state->unlink($this->buildUrl($path)); | |||
} | |||
/** | |||
* Rename a remote file | |||
* | |||
* @param string $from | |||
* @param string $to | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||
*/ | |||
public function rename($from, $to) { | |||
$this->connect(); | |||
return $this->state->rename($this->buildUrl($from), $this->buildUrl($to)); | |||
} | |||
/** | |||
* Upload a local file | |||
* | |||
* @param string $source local file | |||
* @param string $target remove file | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function put($source, $target) { | |||
$this->connect(); | |||
$sourceHandle = fopen($source, 'rb'); | |||
$targetHandle = $this->state->create($this->buildUrl($target)); | |||
while ($data = fread($sourceHandle, 4096)) { | |||
$this->state->write($targetHandle, $data); | |||
} | |||
$this->state->close($targetHandle); | |||
return true; | |||
} | |||
/** | |||
* Download a remote file | |||
* | |||
* @param string $source remove file | |||
* @param string $target local file | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function get($source, $target) { | |||
$this->connect(); | |||
$sourceHandle = $this->state->open($this->buildUrl($source), 'r'); | |||
$targetHandle = fopen($target, 'wb'); | |||
while ($data = $this->state->read($sourceHandle, 4096)) { | |||
fwrite($targetHandle, $data); | |||
} | |||
$this->state->close($sourceHandle); | |||
return true; | |||
} | |||
/** | |||
* Open a readable stream top a remote file | |||
* | |||
* @param string $source | |||
* @return resource a read only stream with the contents of the remote file | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function read($source) { | |||
$this->connect(); | |||
$handle = $this->state->open($this->buildUrl($source), 'r'); | |||
return NativeStream::wrap($this->state, $handle, 'r'); | |||
} | |||
/** | |||
* Open a readable stream top a remote file | |||
* | |||
* @param string $source | |||
* @return resource a read only stream with the contents of the remote file | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function write($source) { | |||
$this->connect(); | |||
$handle = $this->state->create($this->buildUrl($source)); | |||
return NativeStream::wrap($this->state, $handle, 'w'); | |||
} | |||
/** | |||
* 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($path, $attribute) { | |||
$this->connect(); | |||
$result = $this->state->getxattr($this->buildUrl($path), $attribute); | |||
return $result; | |||
} | |||
/** | |||
* Get extended attributes for the path | |||
* | |||
* @param string $path | |||
* @param string $attribute attribute to get the info | |||
* @param mixed $value | |||
* @return string the attribute value | |||
*/ | |||
public function setAttribute($path, $attribute, $value) { | |||
$this->connect(); | |||
if ($attribute === 'system.dos_attr.mode' and is_int($value)) { | |||
$value = '0x' . dechex($value); | |||
} | |||
return $this->state->setxattr($this->buildUrl($path), $attribute, $value); | |||
} | |||
/** | |||
* @param string $path | |||
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL | |||
* @return mixed | |||
*/ | |||
public function setMode($path, $mode) { | |||
return $this->setAttribute($path, 'system.dos_attr.mode', $mode); | |||
} | |||
public function __destruct() { | |||
unset($this->state); | |||
} | |||
} |
@@ -0,0 +1,308 @@ | |||
<?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\ConnectionRefusedException; | |||
use Icewind\SMB\Exception\Exception; | |||
use Icewind\SMB\Exception\ForbiddenException; | |||
use Icewind\SMB\Exception\HostDownException; | |||
use Icewind\SMB\Exception\InvalidTypeException; | |||
use Icewind\SMB\Exception\NoRouteToHostException; | |||
use Icewind\SMB\Exception\NotEmptyException; | |||
use Icewind\SMB\Exception\NotFoundException; | |||
use Icewind\SMB\Exception\TimedOutException; | |||
/** | |||
* Low level wrapper for libsmbclient-php for error handling | |||
*/ | |||
class NativeState { | |||
/** | |||
* @var resource | |||
*/ | |||
protected $state; | |||
protected $handlerSet = false; | |||
protected $connected = false; | |||
protected function handleError($path) { | |||
$error = smbclient_state_errno($this->state); | |||
switch ($error) { | |||
// see error.h | |||
case 0; | |||
return; | |||
case 1: | |||
case 13: | |||
throw new ForbiddenException($path, $error); | |||
case 2: | |||
throw new NotFoundException($path, $error); | |||
case 17: | |||
throw new AlreadyExistsException($path, $error); | |||
case 20: | |||
throw new InvalidTypeException($path, $error); | |||
case 21: | |||
throw new InvalidTypeException($path, $error); | |||
case 39: | |||
throw new NotEmptyException($path, $error); | |||
case 110: | |||
throw new TimedOutException($path, $error); | |||
case 111: | |||
throw new ConnectionRefusedException($path, $error); | |||
case 112: | |||
throw new HostDownException($path, $error); | |||
case 113: | |||
throw new NoRouteToHostException($path, $error); | |||
default: | |||
$message = 'Unknown error (' . $error . ')'; | |||
if ($path) { | |||
$message .= ' for ' . $path; | |||
} | |||
throw new Exception($message, $error); | |||
} | |||
} | |||
protected function testResult($result, $path) { | |||
if ($result === false or $result === null) { | |||
$this->handleError($path); | |||
} | |||
} | |||
/** | |||
* @param string $workGroup | |||
* @param string $user | |||
* @param string $password | |||
* @return bool | |||
*/ | |||
public function init($workGroup, $user, $password) { | |||
if ($this->connected) { | |||
return true; | |||
} | |||
$this->state = smbclient_state_new(); | |||
$result = @smbclient_state_init($this->state, $workGroup, $user, $password); | |||
$this->testResult($result, ''); | |||
$this->connected = true; | |||
return $result; | |||
} | |||
/** | |||
* @param string $uri | |||
* @return resource | |||
*/ | |||
public function opendir($uri) { | |||
$result = @smbclient_opendir($this->state, $uri); | |||
$this->testResult($result, $uri); | |||
return $result; | |||
} | |||
/** | |||
* @param resource $dir | |||
* @return array | |||
*/ | |||
public function readdir($dir) { | |||
$result = @smbclient_readdir($this->state, $dir); | |||
$this->testResult($result, $dir); | |||
return $result; | |||
} | |||
/** | |||
* @param $dir | |||
* @return bool | |||
*/ | |||
public function closedir($dir) { | |||
$result = smbclient_closedir($this->state, $dir); | |||
$this->testResult($result, $dir); | |||
return $result; | |||
} | |||
/** | |||
* @param string $old | |||
* @param string $new | |||
* @return bool | |||
*/ | |||
public function rename($old, $new) { | |||
$result = @smbclient_rename($this->state, $old, $this->state, $new); | |||
$this->testResult($result, $new); | |||
return $result; | |||
} | |||
/** | |||
* @param string $uri | |||
* @return bool | |||
*/ | |||
public function unlink($uri) { | |||
$result = @smbclient_unlink($this->state, $uri); | |||
$this->testResult($result, $uri); | |||
return $result; | |||
} | |||
/** | |||
* @param string $uri | |||
* @param int $mask | |||
* @return bool | |||
*/ | |||
public function mkdir($uri, $mask = 0777) { | |||
$result = @smbclient_mkdir($this->state, $uri, $mask); | |||
$this->testResult($result, $uri); | |||
return $result; | |||
} | |||
/** | |||
* @param string $uri | |||
* @return bool | |||
*/ | |||
public function rmdir($uri) { | |||
$result = @smbclient_rmdir($this->state, $uri); | |||
$this->testResult($result, $uri); | |||
return $result; | |||
} | |||
/** | |||
* @param string $uri | |||
* @return array | |||
*/ | |||
public function stat($uri) { | |||
$result = @smbclient_stat($this->state, $uri); | |||
$this->testResult($result, $uri); | |||
return $result; | |||
} | |||
/** | |||
* @param resource $file | |||
* @return array | |||
*/ | |||
public function fstat($file) { | |||
$result = @smbclient_fstat($this->state, $file); | |||
$this->testResult($result, $file); | |||
return $result; | |||
} | |||
/** | |||
* @param string $uri | |||
* @param string $mode | |||
* @param int $mask | |||
* @return resource | |||
*/ | |||
public function open($uri, $mode, $mask = 0666) { | |||
$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($uri, $mask = 0666) { | |||
$result = @smbclient_creat($this->state, $uri, $mask); | |||
$this->testResult($result, $uri); | |||
return $result; | |||
} | |||
/** | |||
* @param resource $file | |||
* @param int $bytes | |||
* @return string | |||
*/ | |||
public function read($file, $bytes) { | |||
$result = @smbclient_read($this->state, $file, $bytes); | |||
$this->testResult($result, $file); | |||
return $result; | |||
} | |||
/** | |||
* @param resource $file | |||
* @param string $data | |||
* @param int $length | |||
* @return int | |||
*/ | |||
public function write($file, $data, $length = null) { | |||
$result = @smbclient_write($this->state, $file, $data, $length); | |||
$this->testResult($result, $file); | |||
return $result; | |||
} | |||
/** | |||
* @param resource $file | |||
* @param int $offset | |||
* @param int $whence SEEK_SET | SEEK_CUR | SEEK_END | |||
* @return int | bool new file offset as measured from the start of the file on success, false on failure. | |||
*/ | |||
public function lseek($file, $offset, $whence = SEEK_SET) { | |||
$result = @smbclient_lseek($this->state, $file, $offset, $whence); | |||
$this->testResult($result, $file); | |||
return $result; | |||
} | |||
/** | |||
* @param resource $file | |||
* @param int $size | |||
* @return bool | |||
*/ | |||
public function ftruncate($file, $size) { | |||
$result = @smbclient_ftruncate($this->state, $file, $size); | |||
$this->testResult($result, $file); | |||
return $result; | |||
} | |||
public function close($file) { | |||
$result = @smbclient_close($this->state, $file); | |||
$this->testResult($result, $file); | |||
return $result; | |||
} | |||
/** | |||
* @param string $uri | |||
* @param string $key | |||
* @return string | |||
*/ | |||
public function getxattr($uri, $key) { | |||
$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 mixed | |||
*/ | |||
public function setxattr($uri, $key, $value, $flags = 0) { | |||
$result = @smbclient_setxattr($this->state, $uri, $key, $value, $flags); | |||
$this->testResult($result, $uri); | |||
return $result; | |||
} | |||
public function __destruct() { | |||
if ($this->connected) { | |||
smbclient_state_free($this->state); | |||
} | |||
} | |||
} |
@@ -0,0 +1,114 @@ | |||
<?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\InvalidRequestException; | |||
use Icewind\Streams\File; | |||
class NativeStream implements File { | |||
/** | |||
* @var resource | |||
*/ | |||
public $context; | |||
/** | |||
* @var \Icewind\SMB\NativeState | |||
*/ | |||
private $state; | |||
/** | |||
* @var resource | |||
*/ | |||
private $handle; | |||
/** | |||
* @var bool | |||
*/ | |||
private $eof = false; | |||
/** | |||
* Wrap a stream from libsmbclient-php into a regular php stream | |||
* | |||
* @param \Icewind\SMB\NativeState $state | |||
* @param resource $smbStream | |||
* @param string $mode | |||
* @return resource | |||
*/ | |||
public static function wrap($state, $smbStream, $mode) { | |||
stream_wrapper_register('nativesmb', '\Icewind\SMB\NativeStream'); | |||
$context = stream_context_create(array( | |||
'nativesmb' => array( | |||
'state' => $state, | |||
'handle' => $smbStream | |||
) | |||
)); | |||
$fh = fopen('nativesmb://', $mode, false, $context); | |||
stream_wrapper_unregister('nativesmb'); | |||
return $fh; | |||
} | |||
public function stream_close() { | |||
return $this->state->close($this->handle); | |||
} | |||
public function stream_eof() { | |||
return $this->eof; | |||
} | |||
public function stream_flush() { | |||
} | |||
public function stream_open($path, $mode, $options, &$opened_path) { | |||
$context = stream_context_get_options($this->context); | |||
$this->state = $context['nativesmb']['state']; | |||
$this->handle = $context['nativesmb']['handle']; | |||
return true; | |||
} | |||
public function stream_read($count) { | |||
$result = $this->state->read($this->handle, $count); | |||
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) !== false; | |||
} catch (InvalidRequestException $e) { | |||
return false; | |||
} | |||
} | |||
public function stream_stat() { | |||
return $this->state->fstat($this->handle); | |||
} | |||
public function stream_tell() { | |||
return $this->state->lseek($this->handle, 0, SEEK_CUR); | |||
} | |||
public function stream_write($data) { | |||
return $this->state->write($this->handle, $data); | |||
} | |||
public function stream_truncate($size) { | |||
return $this->state->ftruncate($this->handle, $size); | |||
} | |||
public function stream_set_option($option, $arg1, $arg2) { | |||
return false; | |||
} | |||
public function stream_lock($operation) { | |||
return false; | |||
} | |||
} |
@@ -0,0 +1,130 @@ | |||
<?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\AccessDeniedException; | |||
use Icewind\SMB\Exception\AlreadyExistsException; | |||
use Icewind\SMB\Exception\Exception; | |||
use Icewind\SMB\Exception\FileInUseException; | |||
use Icewind\SMB\Exception\InvalidTypeException; | |||
use Icewind\SMB\Exception\NotEmptyException; | |||
use Icewind\SMB\Exception\NotFoundException; | |||
class Parser { | |||
/** | |||
* @var string | |||
*/ | |||
protected $timeZone; | |||
/** | |||
* @param string $timeZone | |||
*/ | |||
public function __construct($timeZone) { | |||
$this->timeZone = $timeZone; | |||
} | |||
public function checkForError($output, $path) { | |||
if (count($output) === 0) { | |||
return true; | |||
} else { | |||
if (strpos($output[0], 'does not exist')) { | |||
throw new NotFoundException($path); | |||
} | |||
$parts = explode(' ', $output[0]); | |||
$error = false; | |||
foreach ($parts as $part) { | |||
if (substr($part, 0, 9) === 'NT_STATUS') { | |||
$error = $part; | |||
} | |||
} | |||
switch ($error) { | |||
case ErrorCodes::PathNotFound: | |||
case ErrorCodes::ObjectNotFound: | |||
case ErrorCodes::NoSuchFile: | |||
throw new NotFoundException($path); | |||
case ErrorCodes::NameCollision: | |||
throw new AlreadyExistsException($path); | |||
case ErrorCodes::AccessDenied: | |||
throw new AccessDeniedException($path); | |||
case ErrorCodes::DirectoryNotEmpty: | |||
throw new NotEmptyException($path); | |||
case ErrorCodes::FileIsADirectory: | |||
case ErrorCodes::NotADirectory: | |||
throw new InvalidTypeException($path); | |||
case ErrorCodes::SharingViolation: | |||
throw new FileInUseException($path); | |||
default: | |||
$message = 'Unknown error (' . $error . ')'; | |||
if ($path) { | |||
$message .= ' for ' . $path; | |||
} | |||
throw new Exception($message); | |||
} | |||
} | |||
} | |||
public function parseMode($mode) { | |||
$result = 0; | |||
$modeStrings = array( | |||
'R' => FileInfo::MODE_READONLY, | |||
'H' => FileInfo::MODE_HIDDEN, | |||
'S' => FileInfo::MODE_SYSTEM, | |||
'D' => FileInfo::MODE_DIRECTORY, | |||
'A' => FileInfo::MODE_ARCHIVE, | |||
'N' => FileInfo::MODE_NORMAL | |||
); | |||
foreach ($modeStrings as $char => $val) { | |||
if (strpos($mode, $char) !== false) { | |||
$result |= $val; | |||
} | |||
} | |||
return $result; | |||
} | |||
public function parseStat($output) { | |||
$mtime = 0; | |||
$mode = 0; | |||
$size = 0; | |||
foreach ($output as $line) { | |||
list($name, $value) = explode(':', $line, 2); | |||
$value = trim($value); | |||
if ($name === 'write_time') { | |||
$mtime = strtotime($value); | |||
} else if ($name === 'attributes') { | |||
$mode = hexdec(substr($value, 1, -1)); | |||
} else if ($name === 'stream') { | |||
list(, $size,) = explode(' ', $value); | |||
$size = intval($size); | |||
} | |||
} | |||
return array( | |||
'mtime' => $mtime, | |||
'mode' => $mode, | |||
'size' => $size | |||
); | |||
} | |||
public function parseDir($output, $basePath) { | |||
//last line is used space | |||
array_pop($output); | |||
$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/'; | |||
//2 spaces, filename, optional type, size, date | |||
$content = array(); | |||
foreach ($output as $line) { | |||
if (preg_match($regex, $line, $matches)) { | |||
list(, $name, $mode, $size, $time) = $matches; | |||
if ($name !== '.' and $name !== '..') { | |||
$mode = $this->parseMode($mode); | |||
$time = strtotime($time . ' ' . $this->timeZone); | |||
$content[] = new FileInfo($basePath . '/' . $name, $name, $size, $time, $mode); | |||
} | |||
} | |||
} | |||
return $content; | |||
} | |||
} |
@@ -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\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 | |||
*/ | |||
private $pipes; | |||
/** | |||
* @var resource $process | |||
*/ | |||
private $process; | |||
public function __construct($command, $env = array()) { | |||
$this->command = $command; | |||
$this->env = $env; | |||
$this->connect(); | |||
} | |||
private function connect() { | |||
$descriptorSpec = array( | |||
0 => array('pipe', 'r'), // child reads from stdin | |||
1 => array('pipe', 'w'), // child writes to stdout | |||
2 => array('pipe', 'w'), // child writes to stderr | |||
3 => array('pipe', 'r'), // child reads from fd#3 | |||
4 => array('pipe', 'r'), // child reads from fd#4 | |||
5 => array('pipe', 'w') // child writes to fd#5 | |||
); | |||
setlocale(LC_ALL, Server::LOCALE); | |||
$env = array_merge($this->env, array( | |||
'CLI_FORCE_INTERACTIVE' => 'y', // Needed or the prompt isn't displayed!! | |||
'LC_ALL' => Server::LOCALE, | |||
'LANG' => Server::LOCALE, | |||
'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output | |||
)); | |||
$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 | |||
*/ | |||
public function isValid() { | |||
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 | |||
*/ | |||
public function write($input) { | |||
fwrite($this->getInputStream(), $input); | |||
fflush($this->getInputStream()); | |||
} | |||
/** | |||
* read a line of output | |||
* | |||
* @return string | |||
*/ | |||
public function readLine() { | |||
return stream_get_line($this->getOutputStream(), 4086, "\n"); | |||
} | |||
/** | |||
* get all output until the process closes | |||
* | |||
* @return array | |||
*/ | |||
public function readAll() { | |||
$output = array(); | |||
while ($line = $this->readLine()) { | |||
$output[] = $line; | |||
} | |||
return $output; | |||
} | |||
public function getInputStream() { | |||
return $this->pipes[0]; | |||
} | |||
public function getOutputStream() { | |||
return $this->pipes[1]; | |||
} | |||
public function getErrorStream() { | |||
return $this->pipes[2]; | |||
} | |||
public function getAuthStream() { | |||
return $this->pipes[3]; | |||
} | |||
public function getFileInputStream() { | |||
return $this->pipes[4]; | |||
} | |||
public function getFileOutputStream() { | |||
return $this->pipes[5]; | |||
} | |||
public function writeAuthentication($user, $password) { | |||
$auth = ($password === false) | |||
? "username=$user" | |||
: "username=$user\npassword=$password"; | |||
if (fwrite($this->getAuthStream(), $auth) === false) { | |||
fclose($this->getAuthStream()); | |||
return false; | |||
} | |||
fclose($this->getAuthStream()); | |||
return true; | |||
} | |||
public function close($terminate = true) { | |||
if (!is_resource($this->process)) { | |||
return; | |||
} | |||
if ($terminate) { | |||
proc_terminate($this->process); | |||
} | |||
proc_close($this->process); | |||
} | |||
public function reconnect() { | |||
$this->close(); | |||
$this->connect(); | |||
} | |||
public function __destruct() { | |||
$this->close(); | |||
} | |||
} |
@@ -0,0 +1,141 @@ | |||
<?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\AuthenticationException; | |||
use Icewind\SMB\Exception\InvalidHostException; | |||
class Server { | |||
const CLIENT = 'smbclient'; | |||
const LOCALE = 'en_US.UTF-8'; | |||
/** | |||
* @var string $host | |||
*/ | |||
protected $host; | |||
/** | |||
* @var string $user | |||
*/ | |||
protected $user; | |||
/** | |||
* @var string $password | |||
*/ | |||
protected $password; | |||
/** | |||
* Check if the smbclient php extension is available | |||
* | |||
* @return bool | |||
*/ | |||
public static function NativeAvailable() { | |||
return function_exists('smbclient_state_new'); | |||
} | |||
/** | |||
* @param string $host | |||
* @param string $user | |||
* @param string $password | |||
*/ | |||
public function __construct($host, $user, $password) { | |||
$this->host = $host; | |||
$this->user = $user; | |||
$this->password = $password; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getAuthString() { | |||
return $this->user . '%' . $this->password; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getUser() { | |||
return $this->user; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getPassword() { | |||
return $this->password; | |||
} | |||
/** | |||
* return string | |||
*/ | |||
public function getHost() { | |||
return $this->host; | |||
} | |||
/** | |||
* @return \Icewind\SMB\IShare[] | |||
* | |||
* @throws \Icewind\SMB\Exception\AuthenticationException | |||
* @throws \Icewind\SMB\Exception\InvalidHostException | |||
*/ | |||
public function listShares() { | |||
$command = Server::CLIENT . ' --authentication-file=/proc/self/fd/3' . | |||
' -gL ' . escapeshellarg($this->getHost()); | |||
$connection = new RawConnection($command); | |||
$connection->writeAuthentication($this->getUser(), $this->getPassword()); | |||
$output = $connection->readAll(); | |||
$line = $output[0]; | |||
$line = rtrim($line, ')'); | |||
if (substr($line, -23) === ErrorCodes::LogonFailure) { | |||
throw new AuthenticationException(); | |||
} | |||
if (substr($line, -26) === ErrorCodes::BadHostName) { | |||
throw new InvalidHostException(); | |||
} | |||
if (substr($line, -22) === ErrorCodes::Unsuccessful) { | |||
throw new InvalidHostException(); | |||
} | |||
if (substr($line, -28) === ErrorCodes::ConnectionRefused) { | |||
throw new InvalidHostException(); | |||
} | |||
$shareNames = array(); | |||
foreach ($output as $line) { | |||
if (strpos($line, '|')) { | |||
list($type, $name, $description) = explode('|', $line); | |||
if (strtolower($type) === 'disk') { | |||
$shareNames[$name] = $description; | |||
} | |||
} | |||
} | |||
$shares = array(); | |||
foreach ($shareNames as $name => $description) { | |||
$shares[] = $this->getShare($name); | |||
} | |||
return $shares; | |||
} | |||
/** | |||
* @param string $name | |||
* @return \Icewind\SMB\IShare | |||
*/ | |||
public function getShare($name) { | |||
return new Share($this, $name); | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public function getTimeZone() { | |||
$command = 'net time zone -S ' . escapeshellarg($this->getHost()); | |||
return exec($command); | |||
} | |||
} |
@@ -0,0 +1,394 @@ | |||
<?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\AccessDeniedException; | |||
use Icewind\SMB\Exception\AlreadyExistsException; | |||
use Icewind\SMB\Exception\ConnectionException; | |||
use Icewind\SMB\Exception\Exception; | |||
use Icewind\SMB\Exception\FileInUseException; | |||
use Icewind\SMB\Exception\InvalidTypeException; | |||
use Icewind\SMB\Exception\NotEmptyException; | |||
use Icewind\SMB\Exception\NotFoundException; | |||
use Icewind\Streams\CallbackWrapper; | |||
class Share implements IShare { | |||
/** | |||
* @var Server $server | |||
*/ | |||
private $server; | |||
/** | |||
* @var string $name | |||
*/ | |||
private $name; | |||
/** | |||
* @var Connection $connection | |||
*/ | |||
public $connection; | |||
/** | |||
* @var \Icewind\SMB\Parser | |||
*/ | |||
protected $parser; | |||
private $serverTimezone; | |||
/** | |||
* @param Server $server | |||
* @param string $name | |||
*/ | |||
public function __construct($server, $name) { | |||
$this->server = $server; | |||
$this->name = $name; | |||
$this->parser = new Parser($this->server->getTimeZone()); | |||
} | |||
/** | |||
* @throws \Icewind\SMB\Exception\ConnectionException | |||
* @throws \Icewind\SMB\Exception\AuthenticationException | |||
* @throws \Icewind\SMB\Exception\InvalidHostException | |||
*/ | |||
protected function connect() { | |||
if ($this->connection and $this->connection->isValid()) { | |||
return; | |||
} | |||
$command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s', | |||
Server::CLIENT, | |||
$this->server->getHost(), | |||
$this->name | |||
); | |||
$this->connection = new Connection($command); | |||
$this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); | |||
if (!$this->connection->isValid()) { | |||
throw new ConnectionException(); | |||
} | |||
} | |||
protected function reconnect() { | |||
$this->connection->reconnect(); | |||
$this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); | |||
if (!$this->connection->isValid()) { | |||
throw new ConnectionException(); | |||
} | |||
} | |||
/** | |||
* Get the name of the share | |||
* | |||
* @return string | |||
*/ | |||
public function getName() { | |||
return $this->name; | |||
} | |||
protected function simpleCommand($command, $path) { | |||
$path = $this->escapePath($path); | |||
$cmd = $command . ' ' . $path; | |||
$output = $this->execute($cmd); | |||
return $this->parseOutput($output, $path); | |||
} | |||
/** | |||
* List the content of a remote folder | |||
* | |||
* @param $path | |||
* @return \Icewind\SMB\IFileInfo[] | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function dir($path) { | |||
$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); | |||
} | |||
/** | |||
* @param string $path | |||
* @return \Icewind\SMB\IFileInfo[] | |||
*/ | |||
public function stat($path) { | |||
$escapedPath = $this->escapePath($path); | |||
$output = $this->execute('allinfo ' . $escapedPath); | |||
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']); | |||
} | |||
/** | |||
* Create a folder on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||
*/ | |||
public function mkdir($path) { | |||
return $this->simpleCommand('mkdir', $path); | |||
} | |||
/** | |||
* Remove a folder on the share | |||
* | |||
* @param string $path | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function rmdir($path) { | |||
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($path, $secondTry = false) { | |||
//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 \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||
*/ | |||
public function rename($from, $to) { | |||
$path1 = $this->escapePath($from); | |||
$path2 = $this->escapePath($to); | |||
$cmd = 'rename ' . $path1 . ' ' . $path2; | |||
$output = $this->execute($cmd); | |||
return $this->parseOutput($output, $to); | |||
} | |||
/** | |||
* Upload a local file | |||
* | |||
* @param string $source local file | |||
* @param string $target remove file | |||
* @return bool | |||
* | |||
* @throws \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function put($source, $target) { | |||
$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 \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function get($source, $target) { | |||
$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 \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function read($source) { | |||
$source = $this->escapePath($source); | |||
// close the single quote, open a double quote where we put the single quote... | |||
$source = str_replace('\'', '\'"\'"\'', $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 | |||
$command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s -c \'get %s /proc/self/fd/5\'', | |||
Server::CLIENT, | |||
$this->server->getHost(), | |||
$this->name, | |||
$source | |||
); | |||
$connection = new Connection($command); | |||
$connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); | |||
$fh = $connection->getFileOutputStream(); | |||
stream_context_set_option($fh, 'file', 'connection', $connection); | |||
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 \Icewind\SMB\Exception\NotFoundException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function write($target) { | |||
$target = $this->escapePath($target); | |||
// close the single quote, open a double quote where we put the single quote... | |||
$target = str_replace('\'', '\'"\'"\'', $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 | |||
$command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s -c \'put /proc/self/fd/4 %s\'', | |||
Server::CLIENT, | |||
$this->server->getHost(), | |||
$this->name, | |||
$target | |||
); | |||
$connection = new RawConnection($command); | |||
$connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); | |||
$fh = $connection->getFileInputStream(); | |||
// use a close callback to ensure the upload is finished before continuing | |||
// this also serves as a way to keep the connection in scope | |||
return CallbackWrapper::wrap($fh, null, null, function () use ($connection) { | |||
$connection->close(false); // dont terminate, give the upload some time | |||
}); | |||
} | |||
/** | |||
* @param string $path | |||
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL | |||
* @return mixed | |||
*/ | |||
public function setMode($path, $mode) { | |||
$modeString = ''; | |||
$modeMap = array( | |||
FileInfo::MODE_READONLY => 'r', | |||
FileInfo::MODE_HIDDEN => 'h', | |||
FileInfo::MODE_ARCHIVE => 'a', | |||
FileInfo::MODE_SYSTEM => 's' | |||
); | |||
foreach ($modeMap 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); | |||
// then set the modes we want | |||
$cmd = 'setmode ' . $path . ' ' . $modeString; | |||
$output = $this->execute($cmd); | |||
return $this->parseOutput($output, $path); | |||
} | |||
/** | |||
* @param string $command | |||
* @return array | |||
*/ | |||
protected function execute($command) { | |||
$this->connect(); | |||
$this->connection->write($command . PHP_EOL); | |||
$output = $this->connection->read(); | |||
return $output; | |||
} | |||
/** | |||
* check output for errors | |||
* | |||
* @param string[] $lines | |||
* @param string $path | |||
* | |||
* @throws NotFoundException | |||
* @throws \Icewind\SMB\Exception\AlreadyExistsException | |||
* @throws \Icewind\SMB\Exception\AccessDeniedException | |||
* @throws \Icewind\SMB\Exception\NotEmptyException | |||
* @throws \Icewind\SMB\Exception\InvalidTypeException | |||
* @throws \Icewind\SMB\Exception\Exception | |||
* @return bool | |||
*/ | |||
protected function parseOutput($lines, $path = '') { | |||
$this->parser->checkForError($lines, $path); | |||
} | |||
/** | |||
* @param string $string | |||
* @return string | |||
*/ | |||
protected function escape($string) { | |||
return escapeshellarg($string); | |||
} | |||
/** | |||
* @param string $path | |||
* @return string | |||
*/ | |||
protected function escapePath($path) { | |||
$path = str_replace('/', '\\', $path); | |||
$path = str_replace('"', '^"', $path); | |||
return '"' . $path . '"'; | |||
} | |||
/** | |||
* @param string $path | |||
* @return string | |||
*/ | |||
protected function escapeLocalPath($path) { | |||
$path = str_replace('"', '\"', $path); | |||
return '"' . $path . '"'; | |||
} | |||
public function __destruct() { | |||
unset($this->connection); | |||
} | |||
} |
@@ -0,0 +1,534 @@ | |||
<?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\Test; | |||
use Icewind\SMB\FileInfo; | |||
abstract class AbstractShare extends \PHPUnit_Framework_TestCase { | |||
/** | |||
* @var \Icewind\SMB\Server $server | |||
*/ | |||
protected $server; | |||
/** | |||
* @var \Icewind\SMB\IShare $share | |||
*/ | |||
protected $share; | |||
/** | |||
* @var string $root | |||
*/ | |||
protected $root; | |||
protected $config; | |||
public function tearDown() { | |||
try { | |||
if ($this->share) { | |||
$this->cleanDir($this->root); | |||
} | |||
unset($this->share); | |||
} catch (\Exception $e) { | |||
unset($this->share); | |||
throw $e; | |||
} | |||
} | |||
public function nameProvider() { | |||
// / ? < > \ : * | " are illegal characters in path on windows | |||
return array( | |||
array('simple'), | |||
array('with spaces_and-underscores'), | |||
array("single'quote'"), | |||
array('日本語'), | |||
array('url %2F +encode'), | |||
array('a somewhat longer filename than the other with more charaters as the all the other filenames'), | |||
array('$as#d€££Ö€ßœĚęĘĞĜΣΥΦΩΫ') | |||
); | |||
} | |||
public function fileDataProvider() { | |||
return array( | |||
array('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'), | |||
array('Mixed language, 日本語 が わからか and Various _/* characters \\|” €') | |||
); | |||
} | |||
public function nameAndDataProvider() { | |||
$names = $this->nameProvider(); | |||
$data = $this->fileDataProvider(); | |||
$result = array(); | |||
foreach ($names as $name) { | |||
foreach ($data as $text) { | |||
$result[] = array($name[0], $text[0]); | |||
} | |||
} | |||
return $result; | |||
} | |||
public function cleanDir($dir) { | |||
$content = $this->share->dir($dir); | |||
foreach ($content as $metadata) { | |||
if ($metadata->isDirectory()) { | |||
$this->cleanDir($metadata->getPath()); | |||
} else { | |||
$this->share->del($metadata->getPath()); | |||
} | |||
} | |||
$this->share->rmdir($dir); | |||
} | |||
private function getTextFile($text = '') { | |||
if (!$text) { | |||
$text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'; | |||
} | |||
$file = tempnam('/tmp', 'smb_test_'); | |||
file_put_contents($file, $text); | |||
return $file; | |||
} | |||
public function testListShares() { | |||
$shares = $this->server->listShares(); | |||
foreach ($shares as $share) { | |||
if ($share->getName() === $this->config->share) { | |||
return; | |||
} | |||
} | |||
$this->fail('Share "' . $this->config->share . '" not found'); | |||
} | |||
public function testRootStartsEmpty() { | |||
$this->assertEquals(array(), $this->share->dir($this->root)); | |||
} | |||
/** | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testMkdir($name) { | |||
$this->share->mkdir($this->root . '/' . $name); | |||
$dirs = $this->share->dir($this->root); | |||
$this->assertCount(1, $dirs); | |||
$this->assertEquals($name, $dirs[0]->getName()); | |||
$this->assertTrue($dirs[0]->isDirectory()); | |||
} | |||
/** | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testRenameDirectory($name) { | |||
$this->share->mkdir($this->root . '/' . $name); | |||
$this->share->rename($this->root . '/' . $name, $this->root . '/' . $name . '_rename'); | |||
$dirs = $this->share->dir($this->root); | |||
$this->assertEquals(1, count($dirs)); | |||
$this->assertEquals($name . '_rename', $dirs[0]->getName()); | |||
} | |||
/** | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testRmdir($name) { | |||
$this->share->mkdir($this->root . '/' . $name); | |||
$this->share->rmdir($this->root . '/' . $name); | |||
$this->assertCount(0, $this->share->dir($this->root)); | |||
} | |||
/** | |||
* @dataProvider nameAndDataProvider | |||
*/ | |||
public function testPut($name, $text) { | |||
$tmpFile = $this->getTextFile($text); | |||
$size = filesize($tmpFile); | |||
$this->share->put($tmpFile, $this->root . '/' . $name); | |||
unlink($tmpFile); | |||
$files = $this->share->dir($this->root); | |||
$this->assertCount(1, $files); | |||
$this->assertEquals($name, $files[0]->getName()); | |||
$this->assertEquals($size, $files[0]->getSize()); | |||
$this->assertFalse($files[0]->isDirectory()); | |||
} | |||
/** | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testRenameFile($name) { | |||
$tmpFile = $this->getTextFile(); | |||
$this->share->put($tmpFile, $this->root . '/' . $name); | |||
unlink($tmpFile); | |||
$this->share->rename($this->root . '/' . $name, $this->root . '/' . $name . '_renamed'); | |||
$files = $this->share->dir($this->root); | |||
$this->assertEquals(1, count($files)); | |||
$this->assertEquals($name . '_renamed', $files[0]->getName()); | |||
} | |||
/** | |||
* @dataProvider nameAndDataProvider | |||
*/ | |||
public function testGet($name, $text) { | |||
$tmpFile = $this->getTextFile($text); | |||
$this->share->put($tmpFile, $this->root . '/' . $name); | |||
unlink($tmpFile); | |||
$targetFile = tempnam('/tmp', 'smb_test_'); | |||
$this->share->get($this->root . '/' . $name, $targetFile); | |||
$this->assertEquals($text, file_get_contents($targetFile)); | |||
unlink($targetFile); | |||
} | |||
/** | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testDel($name) { | |||
$tmpFile = $this->getTextFile(); | |||
$this->share->put($tmpFile, $this->root . '/' . $name); | |||
unlink($tmpFile); | |||
$this->share->del($this->root . '/' . $name); | |||
$this->assertCount(0, $this->share->dir($this->root)); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testCreateFolderInNonExistingFolder() { | |||
$this->share->mkdir($this->root . '/foo/bar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testRemoveFolderInNonExistingFolder() { | |||
$this->share->rmdir($this->root . '/foo/bar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testRemoveNonExistingFolder() { | |||
$this->share->rmdir($this->root . '/foo'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\AlreadyExistsException | |||
*/ | |||
public function testCreateExistingFolder() { | |||
$this->share->mkdir($this->root . '/bar'); | |||
$this->share->mkdir($this->root . '/bar'); | |||
$this->share->rmdir($this->root . '/bar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function testCreateFileExistingFolder() { | |||
$this->share->mkdir($this->root . '/bar'); | |||
$this->share->put($this->getTextFile(), $this->root . '/bar'); | |||
$this->share->rmdir($this->root . '/bar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testCreateFileInNonExistingFolder() { | |||
$this->share->put($this->getTextFile(), $this->root . '/foo/bar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testTestRemoveNonExistingFile() { | |||
$this->share->del($this->root . '/foo'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testDownloadNonExistingFile() { | |||
$this->share->get($this->root . '/foo', '/dev/null'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function testDownloadFolder() { | |||
$this->share->mkdir($this->root . '/foobar'); | |||
$this->share->get($this->root . '/foobar', '/dev/null'); | |||
$this->share->rmdir($this->root . '/foobar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function testDelFolder() { | |||
$this->share->mkdir($this->root . '/foobar'); | |||
$this->share->del($this->root . '/foobar'); | |||
$this->share->rmdir($this->root . '/foobar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\InvalidTypeException | |||
*/ | |||
public function testRmdirFile() { | |||
$this->share->put($this->getTextFile(), $this->root . '/foobar'); | |||
$this->share->rmdir($this->root . '/foobar'); | |||
$this->share->del($this->root . '/foobar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotEmptyException | |||
*/ | |||
public function testRmdirNotEmpty() { | |||
$this->share->mkdir($this->root . '/foobar'); | |||
$this->share->put($this->getTextFile(), $this->root . '/foobar/asd'); | |||
$this->share->rmdir($this->root . '/foobar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testDirNonExisting() { | |||
$this->share->dir('/foobar/asd'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testRmDirNonExisting() { | |||
$this->share->rmdir('/foobar/asd'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testRenameNonExisting() { | |||
$this->share->rename('/foobar/asd', '/foobar/bar'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testRenameTargetNonExisting() { | |||
$txt = $this->getTextFile(); | |||
$this->share->put($txt, $this->root . '/foo.txt'); | |||
unlink($txt); | |||
$this->share->rename($this->root . '/foo.txt', $this->root . '/bar/foo.txt'); | |||
} | |||
public function testModifiedDate() { | |||
$now = time(); | |||
$this->share->put($this->getTextFile(), $this->root . '/foo.txt'); | |||
$dir = $this->share->dir($this->root); | |||
$mtime = $dir[0]->getMTime(); | |||
$this->assertTrue(abs($now - $mtime) <= 1, 'Modified time differs by ' . abs($now - $mtime) . ' seconds'); | |||
$this->share->del($this->root . '/foo.txt'); | |||
} | |||
/** | |||
* @dataProvider nameAndDataProvider | |||
*/ | |||
public function testReadStream($name, $text) { | |||
$sourceFile = $this->getTextFile($text); | |||
$this->share->put($sourceFile, $this->root . '/' . $name); | |||
$fh = $this->share->read($this->root . '/' . $name); | |||
$content = stream_get_contents($fh); | |||
fclose($fh); | |||
$this->share->del($this->root . '/' . $name); | |||
$this->assertEquals(file_get_contents($sourceFile), $content); | |||
} | |||
/** | |||
* @dataProvider nameAndDataProvider | |||
*/ | |||
public function testWriteStream($name, $text) { | |||
$fh = $this->share->write($this->root . '/' . $name); | |||
fwrite($fh, $text); | |||
fclose($fh); | |||
$tmpFile1 = tempnam('/tmp', 'smb_test_'); | |||
$this->share->get($this->root . '/' . $name, $tmpFile1); | |||
$this->assertEquals($text, file_get_contents($tmpFile1)); | |||
$this->share->del($this->root . '/' . $name); | |||
unlink($tmpFile1); | |||
} | |||
public function testDir() { | |||
$txtFile = $this->getTextFile(); | |||
$this->share->mkdir($this->root . '/dir'); | |||
$this->share->put($txtFile, $this->root . '/file.txt'); | |||
unlink($txtFile); | |||
$dir = $this->share->dir($this->root); | |||
if ($dir[0]->getName() === 'dir') { | |||
$dirEntry = $dir[0]; | |||
} else { | |||
$dirEntry = $dir[1]; | |||
} | |||
$this->assertTrue($dirEntry->isDirectory()); | |||
$this->assertFalse($dirEntry->isReadOnly()); | |||
$this->assertFalse($dirEntry->isReadOnly()); | |||
if ($dir[0]->getName() === 'file.txt') { | |||
$fileEntry = $dir[0]; | |||
} else { | |||
$fileEntry = $dir[1]; | |||
} | |||
$this->assertFalse($fileEntry->isDirectory()); | |||
$this->assertFalse($fileEntry->isReadOnly()); | |||
$this->assertFalse($fileEntry->isReadOnly()); | |||
} | |||
/** | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testStat($name) { | |||
$txtFile = $this->getTextFile(); | |||
$size = filesize($txtFile); | |||
$this->share->put($txtFile, $this->root . '/' . $name); | |||
unlink($txtFile); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertEquals($size, $info->getSize()); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\NotFoundException | |||
*/ | |||
public function testStatNonExisting() { | |||
$this->share->stat($this->root . '/fo.txt'); | |||
} | |||
/** | |||
* note setting archive and system bit is not supported | |||
* | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testSetMode($name) { | |||
$txtFile = $this->getTextFile(); | |||
$this->share->put($txtFile, $this->root . '/' . $name); | |||
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_NORMAL); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertFalse($info->isReadOnly()); | |||
$this->assertFalse($info->isArchived()); | |||
$this->assertFalse($info->isSystem()); | |||
$this->assertFalse($info->isHidden()); | |||
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_READONLY); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertTrue($info->isReadOnly()); | |||
$this->assertFalse($info->isArchived()); | |||
$this->assertFalse($info->isSystem()); | |||
$this->assertFalse($info->isHidden()); | |||
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_ARCHIVE); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertFalse($info->isReadOnly()); | |||
$this->assertTrue($info->isArchived()); | |||
$this->assertFalse($info->isSystem()); | |||
$this->assertFalse($info->isHidden()); | |||
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertTrue($info->isReadOnly()); | |||
$this->assertTrue($info->isArchived()); | |||
$this->assertFalse($info->isSystem()); | |||
$this->assertFalse($info->isHidden()); | |||
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_HIDDEN); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertFalse($info->isReadOnly()); | |||
$this->assertFalse($info->isArchived()); | |||
$this->assertFalse($info->isSystem()); | |||
$this->assertTrue($info->isHidden()); | |||
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_SYSTEM); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertFalse($info->isReadOnly()); | |||
$this->assertFalse($info->isArchived()); | |||
$this->assertTrue($info->isSystem()); | |||
$this->assertFalse($info->isHidden()); | |||
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_NORMAL); | |||
$info = $this->share->stat($this->root . '/' . $name); | |||
$this->assertFalse($info->isReadOnly()); | |||
$this->assertFalse($info->isArchived()); | |||
$this->assertFalse($info->isSystem()); | |||
$this->assertFalse($info->isHidden()); | |||
} | |||
public function pathProvider() { | |||
// / ? < > \ : * | " are illegal characters in path on windows | |||
return array( | |||
array('dir/sub/foo.txt'), | |||
array('bar.txt'), | |||
array("single'quote'/sub/foo.txt"), | |||
array('日本語/url %2F +encode/asd.txt'), | |||
array( | |||
'a somewhat longer folder than the other with more charaters as the all the other filenames/' . | |||
'followed by a somewhat long file name after that.txt' | |||
) | |||
); | |||
} | |||
/** | |||
* @dataProvider pathProvider | |||
*/ | |||
public function testSubDirs($path) { | |||
$dirs = explode('/', $path); | |||
$name = array_pop($dirs); | |||
$fullPath = ''; | |||
foreach ($dirs as $dir) { | |||
$fullPath .= '/' . $dir; | |||
$this->share->mkdir($this->root . $fullPath); | |||
} | |||
$txtFile = $this->getTextFile(); | |||
$size = filesize($txtFile); | |||
$this->share->put($txtFile, $this->root . $fullPath . '/' . $name); | |||
unlink($txtFile); | |||
$info = $this->share->stat($this->root . $fullPath . '/' . $name); | |||
$this->assertEquals($size, $info->getSize()); | |||
$this->assertFalse($info->isHidden()); | |||
} | |||
public function testDelAfterStat() { | |||
$name = 'foo.txt'; | |||
$txtFile = $this->getTextFile(); | |||
$this->share->put($txtFile, $this->root . '/' . $name); | |||
unlink($txtFile); | |||
$this->share->stat($this->root . '/' . $name); | |||
$this->share->del($this->root . '/foo.txt'); | |||
} | |||
/** | |||
* @param $name | |||
* @dataProvider nameProvider | |||
*/ | |||
public function testDirPaths($name) { | |||
$txtFile = $this->getTextFile(); | |||
$this->share->mkdir($this->root . '/' . $name); | |||
$this->share->put($txtFile, $this->root . '/' . $name . '/' . $name); | |||
unlink($txtFile); | |||
$content = $this->share->dir($this->root . '/' . $name); | |||
$this->assertCount(1, $content); | |||
$this->assertEquals($name, $content[0]->getName()); | |||
} | |||
} |
@@ -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\SMB\Test; | |||
use Icewind\SMB\NativeServer; | |||
class NativeShare extends AbstractShare { | |||
public function setUp() { | |||
if (!function_exists('smbclient_state_new')) { | |||
$this->markTestSkipped('libsmbclient php extension not installed'); | |||
} | |||
$this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); | |||
$this->server = new NativeServer($this->config->host, $this->config->user, $this->config->password); | |||
$this->share = $this->server->getShare($this->config->share); | |||
if ($this->config->root) { | |||
$this->root = '/' . $this->config->root . '/' . uniqid(); | |||
} else { | |||
$this->root = '/' . uniqid(); | |||
} | |||
$this->share->mkdir($this->root); | |||
} | |||
} |
@@ -0,0 +1,143 @@ | |||
<?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\Test; | |||
use Icewind\SMB\NativeServer; | |||
class NativeStream extends \PHPUnit_Framework_TestCase { | |||
/** | |||
* @var \Icewind\SMB\Server $server | |||
*/ | |||
protected $server; | |||
/** | |||
* @var \Icewind\SMB\NativeShare $share | |||
*/ | |||
protected $share; | |||
/** | |||
* @var string $root | |||
*/ | |||
protected $root; | |||
protected $config; | |||
public function setUp() { | |||
if (!function_exists('smbclient_state_new')) { | |||
$this->markTestSkipped('libsmbclient php extension not installed'); | |||
} | |||
$this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); | |||
$this->server = new NativeServer($this->config->host, $this->config->user, $this->config->password); | |||
$this->share = $this->server->getShare($this->config->share); | |||
if ($this->config->root) { | |||
$this->root = '/' . $this->config->root . '/' . uniqid(); | |||
} else { | |||
$this->root = '/' . uniqid(); | |||
} | |||
$this->share->mkdir($this->root); | |||
} | |||
private function getTextFile() { | |||
$text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'; | |||
$file = tempnam('/tmp', 'smb_test_'); | |||
file_put_contents($file, $text); | |||
return $file; | |||
} | |||
public function testSeekTell() { | |||
$sourceFile = $this->getTextFile(); | |||
$this->share->put($sourceFile, $this->root . '/foobar'); | |||
$fh = $this->share->read($this->root . '/foobar'); | |||
$content = fread($fh, 3); | |||
$this->assertEquals('Lor', $content); | |||
fseek($fh, -2, SEEK_CUR); | |||
$content = fread($fh, 3); | |||
$this->assertEquals('ore', $content); | |||
fseek($fh, 3, SEEK_SET); | |||
$content = fread($fh, 3); | |||
$this->assertEquals('em ', $content); | |||
fseek($fh, -3, SEEK_END); | |||
$content = fread($fh, 3); | |||
$this->assertEquals('qua', $content); | |||
fseek($fh, -3, SEEK_END); | |||
$this->assertEquals(120, ftell($fh)); | |||
} | |||
public function testStat() { | |||
$sourceFile = $this->getTextFile(); | |||
$this->share->put($sourceFile, $this->root . '/foobar'); | |||
$fh = $this->share->read($this->root . '/foobar'); | |||
$stat = fstat($fh); | |||
$this->assertEquals(filesize($sourceFile), $stat['size']); | |||
unlink($sourceFile); | |||
} | |||
public function testTruncate() { | |||
if (version_compare(phpversion(), '5.4.0', '<')) { | |||
$this->markTestSkipped('php <5.4 doesn\'t support truncate for stream wrappers'); | |||
} | |||
$fh = $this->share->write($this->root . '/foobar'); | |||
fwrite($fh, 'foobar'); | |||
ftruncate($fh, 3); | |||
fclose($fh); | |||
$fh = $this->share->read($this->root . '/foobar'); | |||
$this->assertEquals('foo', stream_get_contents($fh)); | |||
} | |||
public function testEOF() { | |||
if (version_compare(phpversion(), '5.4.0', '<')) { | |||
$this->markTestSkipped('php <5.4 doesn\'t support truncate for stream wrappers'); | |||
} | |||
$fh = $this->share->write($this->root . '/foobar'); | |||
fwrite($fh, 'foobar'); | |||
fclose($fh); | |||
$fh = $this->share->read($this->root . '/foobar'); | |||
fread($fh, 3); | |||
$this->assertFalse(feof($fh)); | |||
fread($fh, 5); | |||
$this->assertTrue(feof($fh)); | |||
} | |||
public function testLockUnsupported() { | |||
$fh = $this->share->write($this->root . '/foobar'); | |||
$this->assertFalse(flock($fh, LOCK_SH)); | |||
} | |||
public function testSetOptionUnsupported() { | |||
$fh = $this->share->write($this->root . '/foobar'); | |||
$this->assertFalse(stream_set_blocking($fh, false)); | |||
} | |||
public function tearDown() { | |||
if ($this->share) { | |||
$this->cleanDir($this->root); | |||
} | |||
unset($this->share); | |||
} | |||
public function cleanDir($dir) { | |||
$content = $this->share->dir($dir); | |||
foreach ($content as $metadata) { | |||
if ($metadata->isDirectory()) { | |||
$this->cleanDir($metadata->getPath()); | |||
} else { | |||
$this->share->del($metadata->getPath()); | |||
} | |||
} | |||
$this->share->rmdir($dir); | |||
} | |||
} |
@@ -0,0 +1,87 @@ | |||
<?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\Test; | |||
use Icewind\SMB\FileInfo; | |||
class Parser extends \PHPUnit_Framework_TestCase { | |||
public function modeProvider() { | |||
return array( | |||
array('D', FileInfo::MODE_DIRECTORY), | |||
array('A', FileInfo::MODE_ARCHIVE), | |||
array('S', FileInfo::MODE_SYSTEM), | |||
array('H', FileInfo::MODE_HIDDEN), | |||
array('R', FileInfo::MODE_READONLY), | |||
array('N', FileInfo::MODE_NORMAL), | |||
array('RA', FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE), | |||
array('RAH', FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE | FileInfo::MODE_HIDDEN) | |||
); | |||
} | |||
/** | |||
* @dataProvider modeProvider | |||
*/ | |||
public function testParseMode($string, $mode) { | |||
$parser = new \Icewind\SMB\Parser('UTC'); | |||
$this->assertEquals($mode, $parser->parseMode($string), 'Failed parsing ' . $string); | |||
} | |||
public function statProvider() { | |||
return array( | |||
array( | |||
array( | |||
'altname: test.txt', | |||
'create_time: Sat Oct 12 07:05:58 PM 2013 CEST', | |||
'access_time: Tue Oct 15 02:58:48 PM 2013 CEST', | |||
'write_time: Sat Oct 12 07:05:58 PM 2013 CEST', | |||
'change_time: Sat Oct 12 07:05:58 PM 2013 CEST', | |||
'attributes: (80)', | |||
'stream: [::$DATA], 29634 bytes' | |||
), | |||
array( | |||
'mtime' => strtotime('12 Oct 2013 19:05:58 CEST'), | |||
'mode' => FileInfo::MODE_NORMAL, | |||
'size' => 29634 | |||
)) | |||
); | |||
} | |||
/** | |||
* @dataProvider statProvider | |||
*/ | |||
public function testStat($output, $stat) { | |||
$parser = new \Icewind\SMB\Parser('UTC'); | |||
$this->assertEquals($stat, $parser->parseStat($output)); | |||
} | |||
public function dirProvider() { | |||
return array( | |||
array( | |||
array( | |||
' . D 0 Tue Aug 26 19:11:56 2014', | |||
' .. DR 0 Sun Oct 28 15:24:02 2012', | |||
' c.pdf N 29634 Sat Oct 12 19:05:58 2013', | |||
'', | |||
' 62536 blocks of size 8388608. 57113 blocks available' | |||
), | |||
array( | |||
new FileInfo('/c.pdf', 'c.pdf', 29634, strtotime('12 Oct 2013 19:05:58 CEST'), FileInfo::MODE_NORMAL) | |||
) | |||
) | |||
); | |||
} | |||
/** | |||
* @dataProvider dirProvider | |||
*/ | |||
public function testDir($output, $dir) { | |||
$parser = new \Icewind\SMB\Parser('CEST'); | |||
$this->assertEquals($dir, $parser->parseDir($output, '')); | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
<?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\Test; | |||
class Server extends \PHPUnit_Framework_TestCase { | |||
/** | |||
* @var \Icewind\SMB\Server $server | |||
*/ | |||
private $server; | |||
private $config; | |||
public function setUp() { | |||
$this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); | |||
$this->server = new \Icewind\SMB\Server($this->config->host, $this->config->user, $this->config->password); | |||
} | |||
public function testListShares() { | |||
$shares = $this->server->listShares(); | |||
foreach ($shares as $share) { | |||
if ($share->getName() === $this->config->share) { | |||
return; | |||
} | |||
} | |||
$this->fail('Share "' . $this->config->share . '" not found'); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\AuthenticationException | |||
*/ | |||
public function testWrongUserName() { | |||
$this->markTestSkipped('This fails for no reason on travis'); | |||
$server = new \Icewind\SMB\Server($this->config->host, uniqid(), uniqid()); | |||
$server->listShares(); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\AuthenticationException | |||
*/ | |||
public function testWrongPassword() { | |||
$server = new \Icewind\SMB\Server($this->config->host, $this->config->user, uniqid()); | |||
$server->listShares(); | |||
} | |||
/** | |||
* @expectedException \Icewind\SMB\Exception\InvalidHostException | |||
*/ | |||
public function testWrongHost() { | |||
$server = new \Icewind\SMB\Server(uniqid(), $this->config->user, $this->config->password); | |||
$server->listShares(); | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
<?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\Test; | |||
use Icewind\SMB\Server as NormalServer; | |||
class Share extends AbstractShare { | |||
public function setUp() { | |||
$this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); | |||
$this->server = new NormalServer($this->config->host, $this->config->user, $this->config->password); | |||
$this->share = $this->server->getShare($this->config->share); | |||
if ($this->config->root) { | |||
$this->root = '/' . $this->config->root . '/' . uniqid(); | |||
} else { | |||
$this->root = '/' . uniqid(); | |||
} | |||
$this->share->mkdir($this->root); | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
<?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 | |||
*/ | |||
date_default_timezone_set('UTC'); | |||
require_once __DIR__.'/../vendor/autoload.php'; |
@@ -0,0 +1,7 @@ | |||
{ | |||
"host": "localhost", | |||
"user": "test", | |||
"password": "test", | |||
"share": "test", | |||
"root": "test" | |||
} |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="utf-8" ?> | |||
<phpunit bootstrap="bootstrap.php"> | |||
<testsuite name='SMB'> | |||
<directory suffix='.php'>./</directory> | |||
</testsuite> | |||
</phpunit> |
@@ -0,0 +1,3 @@ | |||
.idea | |||
vendor | |||
composer.lock |
@@ -0,0 +1,26 @@ | |||
language: php | |||
php: | |||
- 5.3 | |||
- 5.4 | |||
- 5.5 | |||
- hhvm | |||
matrix: | |||
allow_failures: | |||
- php: hhvm # due to facebook/hhvm#3321 | |||
env: | |||
global: | |||
- CURRENT_DIR=`pwd` | |||
install: | |||
- composer install --dev --no-interaction | |||
script: | |||
- mkdir -p build/logs | |||
- cd tests | |||
- phpunit --coverage-clover ../build/logs/clover.xml --configuration phpunit.xml | |||
after_script: | |||
- cd $CURRENT_DIR | |||
- php vendor/bin/coveralls -v |
@@ -0,0 +1,52 @@ | |||
#Streams# | |||
[![Build Status](https://travis-ci.org/icewind1991/Streams.svg?branch=master)](https://travis-ci.org/icewind1991/Streams) | |||
[![Coverage Status](https://img.shields.io/coveralls/icewind1991/Streams.svg)](https://coveralls.io/r/icewind1991/Streams?branch=master) | |||
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,23 @@ | |||
{ | |||
"name" : "icewind/streams", | |||
"description" : "A set of generic stream wrappers", | |||
"license" : "MIT", | |||
"authors" : [ | |||
{ | |||
"name" : "Robin Appelman", | |||
"email": "icewind@owncloud.com" | |||
} | |||
], | |||
"require" : { | |||
"php": ">=5.3" | |||
}, | |||
"require-dev" : { | |||
"satooshi/php-coveralls": "dev-master" | |||
}, | |||
"autoload" : { | |||
"psr-4": { | |||
"Icewind\\Streams\\Tests\\": "tests/", | |||
"Icewind\\Streams\\": "src/" | |||
} | |||
} | |||
} |
@@ -0,0 +1,110 @@ | |||
<?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) | |||
* ] | |||
* ] | |||
* | |||
* All callbacks are called after the operation is executed on the source stream | |||
*/ | |||
class CallbackWrapper extends Wrapper { | |||
/** | |||
* @var callable | |||
*/ | |||
protected $readCallback; | |||
/** | |||
* @var callable | |||
*/ | |||
protected $writeCallback; | |||
/** | |||
* @var callable | |||
*/ | |||
protected $closeCallback; | |||
/** | |||
* Wraps a stream with the provided callbacks | |||
* | |||
* @param resource $source | |||
* @param callable $read (optional) | |||
* @param callable $write (optional) | |||
* @param callable $close (optional) | |||
* @return resource | |||
* | |||
* @throws \BadMethodCallException | |||
*/ | |||
public static function wrap($source, $read = null, $write = null, $close = null) { | |||
$context = stream_context_create(array( | |||
'callback' => array( | |||
'source' => $source, | |||
'read' => $read, | |||
'write' => $write, | |||
'close' => $close | |||
) | |||
)); | |||
stream_wrapper_register('callback', '\Icewind\Streams\CallbackWrapper'); | |||
try { | |||
$wrapped = fopen('callback://', 'r+', false, $context); | |||
} catch (\BadMethodCallException $e) { | |||
stream_wrapper_unregister('callback'); | |||
throw $e; | |||
} | |||
stream_wrapper_unregister('callback'); | |||
return $wrapped; | |||
} | |||
public function stream_open($path, $mode, $options, &$opened_path) { | |||
$context = $this->loadContext('callback'); | |||
if (isset($context['read']) and is_callable($context['read'])) { | |||
$this->readCallback = $context['read']; | |||
} | |||
if (isset($context['write']) and is_callable($context['write'])) { | |||
$this->writeCallback = $context['write']; | |||
} | |||
if (isset($context['close']) and is_callable($context['close'])) { | |||
$this->closeCallback = $context['close']; | |||
} | |||
return true; | |||
} | |||
public function stream_read($count) { | |||
$result = parent::stream_read($count); | |||
if ($this->readCallback) { | |||
call_user_func($this->readCallback, $count); | |||
} | |||
return $result; | |||
} | |||
public function stream_write($data) { | |||
$result = parent::stream_write($data); | |||
if ($this->writeCallback) { | |||
call_user_func($this->writeCallback, $data); | |||
} | |||
return $result; | |||
} | |||
public function stream_close() { | |||
$result = parent::stream_close(); | |||
if ($this->closeCallback) { | |||
call_user_func($this->closeCallback); | |||
} | |||
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 | |||
*/ | |||
public function dir_readdir(); | |||
/** | |||
* @return bool | |||
*/ | |||
public function dir_closedir(); | |||
/** | |||
* @return bool | |||
*/ | |||
public function dir_rewinddir(); | |||
} |
@@ -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 string $offset | |||
* @param int $whence | |||
* @return bool | |||
*/ | |||
public function stream_seek($offset, $whence = SEEK_SET); | |||
/** | |||
* @return int | |||
*/ | |||
public function stream_tell(); | |||
/** | |||
* @param int $count | |||
* @return string | |||
*/ | |||
public function stream_read($count); | |||
/** | |||
* @param string $data | |||
* @return int | |||
*/ | |||
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 | |||
*/ | |||
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,123 @@ | |||
<?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 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 \Exception | |||
*/ | |||
protected function loadContext($name) { | |||
$context = stream_context_get_options($this->context); | |||
if (isset($context[$name])) { | |||
$context = $context[$name]; | |||
} else { | |||
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); | |||
} | |||
if (isset($context['iterator']) and $context['iterator'] instanceof \Iterator) { | |||
$this->iterator = $context['iterator']; | |||
} else if (isset($context['array']) and is_array($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('dir'); | |||
return true; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
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 | |||
* | |||
* @throws \BadMethodCallException | |||
*/ | |||
public static function wrap($source) { | |||
if ($source instanceof \Iterator) { | |||
$context = stream_context_create(array( | |||
'dir' => array( | |||
'iterator' => $source) | |||
)); | |||
} else if (is_array($source)) { | |||
$context = stream_context_create(array( | |||
'dir' => array( | |||
'array' => $source) | |||
)); | |||
} else { | |||
throw new \BadMethodCallException('$source should be an Iterator or array'); | |||
} | |||
stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); | |||
$wrapped = opendir('iterator://', $context); | |||
stream_wrapper_unregister('iterator'); | |||
return $wrapped; | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
<?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 { | |||
/** | |||
* Wraps a stream with the provided callbacks | |||
* | |||
* @param resource $source | |||
* @return resource | |||
* | |||
* @throws \BadMethodCallException | |||
*/ | |||
public static function wrap($source) { | |||
$context = stream_context_create(array( | |||
'null' => array( | |||
'source' => $source) | |||
)); | |||
stream_wrapper_register('null', '\Icewind\Streams\NullWrapper'); | |||
try { | |||
$wrapped = fopen('null://', 'r+', false, $context); | |||
} catch (\BadMethodCallException $e) { | |||
stream_wrapper_unregister('null'); | |||
throw $e; | |||
} | |||
stream_wrapper_unregister('null'); | |||
return $wrapped; | |||
} | |||
public function stream_open($path, $mode, $options, &$opened_path) { | |||
$this->loadContext('null'); | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,110 @@ | |||
<?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; | |||
/** | |||
* Base class for stream wrappers, wraps an existing stream | |||
* | |||
* This wrapper itself doesn't implement any functionality but is just a base class for other wrappers to extend | |||
*/ | |||
abstract class Wrapper implements File { | |||
/** | |||
* @var resource | |||
*/ | |||
public $context; | |||
/** | |||
* The wrapped stream | |||
* | |||
* @var resource | |||
*/ | |||
protected $source; | |||
/** | |||
* Load the source from the stream context and return the context options | |||
* | |||
* @param string $name | |||
* @return array | |||
* @throws \Exception | |||
*/ | |||
protected function loadContext($name) { | |||
$context = stream_context_get_options($this->context); | |||
if (isset($context[$name])) { | |||
$context = $context[$name]; | |||
} else { | |||
throw new \BadMethodCallException('Invalid context, "callable" options not set'); | |||
} | |||
if (isset($context['source']) and is_resource($context['source'])) { | |||
$this->setSourceStream($context['source']); | |||
} else { | |||
throw new \BadMethodCallException('Invalid context, source not set'); | |||
} | |||
return $context; | |||
} | |||
/** | |||
* @param resource $source | |||
*/ | |||
protected function setSourceStream($source) { | |||
$this->source = $source; | |||
} | |||
public function stream_seek($offset, $whence = SEEK_SET) { | |||
$result = fseek($this->source, $offset, $whence); | |||
return $result == 0 ? true : false; | |||
} | |||
public function stream_tell() { | |||
return ftell($this->source); | |||
} | |||
public function stream_read($count) { | |||
return fread($this->source, $count); | |||
} | |||
public function stream_write($data) { | |||
return fwrite($this->source, $data); | |||
} | |||
public function stream_set_option($option, $arg1, $arg2) { | |||
switch ($option) { | |||
case STREAM_OPTION_BLOCKING: | |||
stream_set_blocking($this->source, $arg1); | |||
break; | |||
case STREAM_OPTION_READ_TIMEOUT: | |||
stream_set_timeout($this->source, $arg1, $arg2); | |||
break; | |||
case STREAM_OPTION_WRITE_BUFFER: | |||
stream_set_write_buffer($this->source, $arg1); | |||
} | |||
} | |||
public function stream_truncate($size) { | |||
return ftruncate($this->source, $size); | |||
} | |||
public function stream_stat() { | |||
return fstat($this->source); | |||
} | |||
public function stream_lock($mode) { | |||
return flock($this->source, $mode); | |||
} | |||
public function stream_flush() { | |||
return fflush($this->source); | |||
} | |||
public function stream_eof() { | |||
return feof($this->source); | |||
} | |||
public function stream_close() { | |||
return fclose($this->source); | |||
} | |||
} |
@@ -0,0 +1,72 @@ | |||
<?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\Tests; | |||
class CallbackWrapper extends Wrapper { | |||
/** | |||
* @param resource $source | |||
* @param callable $read | |||
* @param callable $write | |||
* @param callable $close | |||
* @return resource | |||
*/ | |||
protected function wrapSource($source, $read = null, $write = null, $close = null) { | |||
return \Icewind\Streams\CallbackWrapper::wrap($source, $read, $write, $close); | |||
} | |||
/** | |||
* @expectedException \BadMethodCallException | |||
*/ | |||
public function testWrapInvalidSource() { | |||
$this->wrapSource('foo'); | |||
} | |||
public function testReadCallback() { | |||
$called = false; | |||
$callBack = function () use (&$called) { | |||
$called = true; | |||
}; | |||
$source = fopen('php://temp', 'r+'); | |||
fwrite($source, 'foobar'); | |||
rewind($source); | |||
$wrapped = $this->wrapSource($source, $callBack); | |||
$this->assertEquals('foo', fread($wrapped, 3)); | |||
$this->assertTrue($called); | |||
} | |||
public function testWriteCallback() { | |||
$lastData = ''; | |||
$callBack = function ($data) use (&$lastData) { | |||
$lastData = $data; | |||
}; | |||
$source = fopen('php://temp', 'r+'); | |||
$wrapped = $this->wrapSource($source, null, $callBack); | |||
fwrite($wrapped, 'foobar'); | |||
$this->assertEquals('foobar', $lastData); | |||
} | |||
public function testCloseCallback() { | |||
$called = false; | |||
$callBack = function () use (&$called) { | |||
$called = true; | |||
}; | |||
$source = fopen('php://temp', 'r+'); | |||
fwrite($source, 'foobar'); | |||
rewind($source); | |||
$wrapped = $this->wrapSource($source, null, null, $callBack); | |||
fclose($wrapped); | |||
$this->assertTrue($called); | |||
} | |||
} |
@@ -0,0 +1,130 @@ | |||
<?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\Tests; | |||
class IteratorDirectory extends \PHPUnit_Framework_TestCase { | |||
/** | |||
* @param \Iterator | array $source | |||
* @return resource | |||
*/ | |||
protected function wrapSource($source) { | |||
return \Icewind\Streams\IteratorDirectory::wrap($source); | |||
} | |||
/** | |||
* @expectedException \BadMethodCallException | |||
*/ | |||
public function testNoContext() { | |||
$context = stream_context_create(array()); | |||
stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); | |||
try { | |||
opendir('iterator://', $context); | |||
stream_wrapper_unregister('iterator'); | |||
} catch (\Exception $e) { | |||
stream_wrapper_unregister('iterator'); | |||
throw $e; | |||
} | |||
} | |||
/** | |||
* @expectedException \BadMethodCallException | |||
*/ | |||
public function testInvalidSource() { | |||
$context = stream_context_create(array( | |||
'dir' => array( | |||
'array' => 2 | |||
) | |||
)); | |||
stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); | |||
try { | |||
opendir('iterator://', $context); | |||
stream_wrapper_unregister('iterator'); | |||
} catch (\Exception $e) { | |||
stream_wrapper_unregister('iterator'); | |||
throw $e; | |||
} | |||
} | |||
/** | |||
* @expectedException \BadMethodCallException | |||
*/ | |||
public function testWrapInvalidSource() { | |||
$this->wrapSource(2); | |||
} | |||
public function fileListProvider() { | |||
$longList = array_fill(0, 500, 'foo'); | |||
return array( | |||
array( | |||
array( | |||
'foo', | |||
'bar', | |||
'qwerty' | |||
) | |||
), | |||
array( | |||
array( | |||
'with spaces', | |||
'under_scores', | |||
'日本語', | |||
'character %$_', | |||
'.', | |||
'0', | |||
'double "quotes"', | |||
"single 'quotes'" | |||
) | |||
), | |||
array( | |||
array( | |||
'single item' | |||
) | |||
), | |||
array( | |||
$longList | |||
), | |||
array( | |||
array() | |||
) | |||
); | |||
} | |||
protected function basicTest($fileList, $dh) { | |||
$result = array(); | |||
while (($file = readdir($dh)) !== false) { | |||
$result[] = $file; | |||
} | |||
$this->assertEquals($fileList, $result); | |||
rewinddir($dh); | |||
if (count($fileList)) { | |||
$this->assertEquals($fileList[0], readdir($dh)); | |||
} else { | |||
$this->assertFalse(readdir($dh)); | |||
} | |||
} | |||
/** | |||
* @dataProvider fileListProvider | |||
*/ | |||
public function testBasicIterator($fileList) { | |||
$iterator = new \ArrayIterator($fileList); | |||
$dh = $this->wrapSource($iterator); | |||
$this->basicTest($fileList, $dh); | |||
} | |||
/** | |||
* @dataProvider fileListProvider | |||
*/ | |||
public function testBasicArray($fileList) { | |||
$dh = $this->wrapSource($fileList); | |||
$this->basicTest($fileList, $dh); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
<?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\Tests; | |||
class NullWrapper extends Wrapper { | |||
/** | |||
* @param resource $source | |||
* @return resource | |||
*/ | |||
protected function wrapSource($source) { | |||
return \Icewind\Streams\NullWrapper::wrap($source); | |||
} | |||
/** | |||
* @expectedException \BadMethodCallException | |||
*/ | |||
public function testNoContext() { | |||
stream_wrapper_register('null', '\Icewind\Streams\NullWrapper'); | |||
$context = stream_context_create(array()); | |||
try { | |||
fopen('null://', 'r+', false, $context); | |||
stream_wrapper_unregister('null'); | |||
} catch (\Exception $e) { | |||
stream_wrapper_unregister('null'); | |||
throw $e; | |||
} | |||
} | |||
/** | |||
* @expectedException \BadMethodCallException | |||
*/ | |||
public function testNoSource() { | |||
stream_wrapper_register('null', '\Icewind\Streams\NullWrapper'); | |||
$context = stream_context_create(array( | |||
'null' => array( | |||
'source' => 'bar' | |||
) | |||
)); | |||
try { | |||
fopen('null://', 'r+', false, $context); | |||
} catch (\Exception $e) { | |||
stream_wrapper_unregister('null'); | |||
throw $e; | |||
} | |||
} | |||
/** | |||
* @expectedException \BadMethodCallException | |||
*/ | |||
public function testWrapInvalidSource() { | |||
$this->wrapSource('foo'); | |||
} | |||
} |
@@ -0,0 +1,105 @@ | |||
<?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\Tests; | |||
abstract class Wrapper extends \PHPUnit_Framework_TestCase { | |||
/** | |||
* @param resource $source | |||
* @return resource | |||
*/ | |||
abstract protected function wrapSource($source); | |||
public function testRead() { | |||
$source = fopen('php://temp', 'r+'); | |||
fwrite($source, 'foobar'); | |||
rewind($source); | |||
$wrapped = $this->wrapSource($source); | |||
$this->assertEquals('foo', fread($wrapped, 3)); | |||
$this->assertEquals('bar', fread($wrapped, 3)); | |||
$this->assertEquals('', fread($wrapped, 3)); | |||
} | |||
public function testWrite() { | |||
$source = fopen('php://temp', 'r+'); | |||
rewind($source); | |||
$wrapped = $this->wrapSource($source); | |||
$this->assertEquals(6, fwrite($wrapped, 'foobar')); | |||
rewind($source); | |||
$this->assertEquals('foobar', stream_get_contents($source)); | |||
} | |||
public function testClose() { | |||
$source = fopen('php://temp', 'r+'); | |||
rewind($source); | |||
$wrapped = $this->wrapSource($source); | |||
fclose($wrapped); | |||
$this->assertFalse(is_resource($source)); | |||
} | |||
public function testSeekTell() { | |||
$source = fopen('php://temp', 'r+'); | |||
fwrite($source, 'foobar'); | |||
rewind($source); | |||
$wrapped = $this->wrapSource($source); | |||
$this->assertEquals(0, ftell($wrapped)); | |||
fseek($wrapped, 2); | |||
$this->assertEquals(2, ftell($source)); | |||
$this->assertEquals(2, ftell($wrapped)); | |||
fseek($wrapped, 2, SEEK_CUR); | |||
$this->assertEquals(4, ftell($source)); | |||
$this->assertEquals(4, ftell($wrapped)); | |||
fseek($wrapped, -1, SEEK_END); | |||
$this->assertEquals(5, ftell($source)); | |||
$this->assertEquals(5, ftell($wrapped)); | |||
} | |||
public function testStat() { | |||
$source = fopen(__FILE__, 'r+'); | |||
$wrapped = $this->wrapSource($source); | |||
$this->assertEquals(stat(__FILE__), fstat($wrapped)); | |||
} | |||
public function testTruncate() { | |||
if (version_compare(phpversion(), '5.4.0', '<')) { | |||
$this->markTestSkipped('php <5.4 doesn\'t support truncate for stream wrappers'); | |||
} | |||
$source = fopen('php://temp', 'r+'); | |||
fwrite($source, 'foobar'); | |||
rewind($source); | |||
$wrapped = $this->wrapSource($source); | |||
ftruncate($wrapped, 2); | |||
$this->assertEquals('fo', fread($wrapped, 10)); | |||
} | |||
public function testLock() { | |||
$source = tmpfile(); | |||
$wrapped = $this->wrapSource($source); | |||
if (!flock($wrapped, LOCK_EX)) { | |||
$this->fail('Unable to acquire lock'); | |||
} | |||
} | |||
public function testStreamOptions() { | |||
$source = fopen('php://temp', 'r+'); | |||
$wrapped = $this->wrapSource($source); | |||
stream_set_blocking($wrapped, 0); | |||
stream_set_timeout($wrapped, 1, 0); | |||
stream_set_write_buffer($wrapped, 0); | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
<?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 | |||
*/ | |||
date_default_timezone_set('UTC'); | |||
require_once __DIR__ . '/../vendor/autoload.php'; |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="utf-8" ?> | |||
<phpunit bootstrap="bootstrap.php"> | |||
<testsuite name='Stream'> | |||
<directory suffix='.php'>./</directory> | |||
</testsuite> | |||
</phpunit> |
@@ -1,516 +0,0 @@ | |||
<?php | |||
################################################################### | |||
# smb.php | |||
# This class implements a SMB stream wrapper based on 'smbclient' | |||
# | |||
# Date: lun oct 22 10:35:35 CEST 2007 | |||
# | |||
# Homepage: http://www.phpclasses.org/smb4php | |||
# | |||
# Copyright (c) 2007 Victor M. Varela <vmvarela@gmail.com> | |||
# Copyright (c) 2012 Frank Karlitschek <frank@owncloud.org> | |||
# Copyright (c) 2014 Robin McCorkell <rmccorkell@karoshi.org.uk> | |||
# | |||
# This program is free software; you can redistribute it and/or | |||
# modify it under the terms of the GNU General Public License | |||
# as published by the Free Software Foundation; either version 2 | |||
# 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 General Public License for more details. | |||
# | |||
# On the official website http://www.phpclasses.org/smb4php the | |||
# license is listed as LGPL so we assume that this is | |||
# dual-licensed GPL/LGPL | |||
################################################################### | |||
define ('SMB4PHP_VERSION', '0.8'); | |||
################################################################### | |||
# CONFIGURATION SECTION - Change for your needs | |||
################################################################### | |||
define ('SMB4PHP_SMBCLIENT', 'smbclient'); | |||
define ('SMB4PHP_SMBOPTIONS', 'TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE SO_RCVBUF=8192 SO_SNDBUF=8192'); | |||
define ('SMB4PHP_AUTHMODE', 'arg'); # set to 'env' to use USER enviroment variable | |||
################################################################### | |||
# SMB - commands that does not need an instance | |||
################################################################### | |||
$GLOBALS['__smb_cache'] = array ('stat' => array (), 'dir' => array ()); | |||
class smb { | |||
private static $regexp = array ( | |||
'^added interface ip=(.*) bcast=(.*) nmask=(.*)$' => 'skip', | |||
'Anonymous login successful' => 'skip', | |||
'^Domain=\[(.*)\] OS=\[(.*)\] Server=\[(.*)\]$' => 'skip', | |||
'^\tSharename[ ]+Type[ ]+Comment$' => 'shares', | |||
'^\t---------[ ]+----[ ]+-------$' => 'skip', | |||
'^\tServer [ ]+Comment$' => 'servers', | |||
'^\t---------[ ]+-------$' => 'skip', | |||
'^\tWorkgroup[ ]+Master$' => 'workg', | |||
'^\t(.*)[ ]+(Disk|IPC)[ ]+IPC.*$' => 'skip', | |||
'^\tIPC\\\$(.*)[ ]+IPC' => 'skip', | |||
'^\t(.*)[ ]+(Disk)[ ]+(.*)$' => 'share', | |||
'^\t(.*)[ ]+(Printer)[ ]+(.*)$' => 'skip', | |||
'([0-9]+) blocks of size ([0-9]+)\. ([0-9]+) blocks available' => 'skip', | |||
'Got a positive name query response from ' => 'skip', | |||
'^(session setup failed): (.*)$' => 'error', | |||
'^(.*): ERRSRV - ERRbadpw' => 'error', | |||
'^Error returning browse list: (.*)$' => 'error', | |||
'^tree connect failed: (.*)$' => 'error', | |||
'^(Connection to .* failed)(.*)$' => 'error-connect', | |||
'^NT_STATUS_(.*) ' => 'error', | |||
'^NT_STATUS_(.*)\$' => 'error', | |||
'ERRDOS - ERRbadpath \((.*).\)' => 'error', | |||
'cd (.*): (.*)$' => 'error', | |||
'^cd (.*): NT_STATUS_(.*)' => 'error', | |||
'^\t(.*)$' => 'srvorwg', | |||
'^([0-9]+)[ ]+([0-9]+)[ ]+(.*)$' => 'skip', | |||
'^Job ([0-9]+) cancelled' => 'skip', | |||
'^[ ]+(.*)[ ]+([0-9]+)[ ]+(Mon|Tue|Wed|Thu|Fri|Sat|Sun)[ ](Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ ]+([0-9]+)[ ]+([0-9]{2}:[0-9]{2}:[0-9]{2})[ ]([0-9]{4})$' => 'files', | |||
'^message start: ERRSRV - (ERRmsgoff)' => 'error' | |||
); | |||
function getRegexp() { | |||
return self::$regexp; | |||
} | |||
function parse_url ($url) { | |||
$pu = parse_url (trim($url)); | |||
foreach (array ('domain', 'user', 'pass', 'host', 'port', 'path') as $i) { | |||
if (! isset($pu[$i])) { | |||
$pu[$i] = ''; | |||
} | |||
} | |||
if (count ($userdomain = explode (';', urldecode ($pu['user']))) > 1) { | |||
@list ($pu['domain'], $pu['user']) = $userdomain; | |||
} | |||
$path = preg_replace (array ('/^\//', '/\/$/'), '', urldecode ($pu['path'])); | |||
list ($pu['share'], $pu['path']) = (preg_match ('/^([^\/]+)\/(.*)/', $path, $regs)) | |||
? array ($regs[1], preg_replace ('/\//', '\\', $regs[2])) | |||
: array ($path, ''); | |||
$pu['type'] = $pu['path'] ? 'path' : ($pu['share'] ? 'share' : ($pu['host'] ? 'host' : '**error**')); | |||
if (! ($pu['port'] = intval(@$pu['port']))) { | |||
$pu['port'] = 139; | |||
} | |||
// decode user and password | |||
$pu['user'] = urldecode($pu['user']); | |||
$pu['pass'] = urldecode($pu['pass']); | |||
return $pu; | |||
} | |||
function look ($purl) { | |||
return smb::client ('-L ' . escapeshellarg ($purl['host']), $purl); | |||
} | |||
function execute ($command, $purl, $regexp = NULL) { | |||
return smb::client ('-d 0 ' | |||
. escapeshellarg ('//' . $purl['host'] . '/' . $purl['share']) | |||
. ' -c ' . escapeshellarg ($command), $purl, $regexp | |||
); | |||
} | |||
function client ($params, $purl, $regexp = NULL) { | |||
if ($regexp === NULL) $regexp = smb::$regexp; | |||
if (SMB4PHP_AUTHMODE == 'env') { | |||
putenv("USER={$purl['user']}%{$purl['pass']}"); | |||
$auth = ''; | |||
} else { | |||
$auth = ($purl['user'] <> '' ? (' -U ' . escapeshellarg ($purl['user'] . '%' . $purl['pass'])) : ''); | |||
} | |||
if ($purl['domain'] <> '') { | |||
$auth .= ' -W ' . escapeshellarg ($purl['domain']); | |||
} | |||
$port = ($purl['port'] <> 139 ? ' -p ' . escapeshellarg ($purl['port']) : ''); | |||
$options = '-O ' . escapeshellarg(SMB4PHP_SMBOPTIONS); | |||
// this put env is necessary to read the output of smbclient correctly | |||
$old_locale = getenv('LC_ALL'); | |||
putenv('LC_ALL=en_US.UTF-8'); | |||
$output = popen ('TZ=UTC '.SMB4PHP_SMBCLIENT." -N {$auth} {$options} {$port} {$options} {$params} 2>/dev/null", 'r'); | |||
$gotInfo = false; | |||
$info = array (); | |||
$info['info']= array (); | |||
$mode = ''; | |||
while ($line = fgets ($output, 4096)) { | |||
list ($tag, $regs, $i) = array ('skip', array (), array ()); | |||
reset ($regexp); | |||
foreach ($regexp as $r => $t) if (preg_match ('/'.$r.'/', $line, $regs)) { | |||
$tag = $t; | |||
break; | |||
} | |||
switch ($tag) { | |||
case 'skip': continue; | |||
case 'shares': $mode = 'shares'; break; | |||
case 'servers': $mode = 'servers'; break; | |||
case 'workg': $mode = 'workgroups'; break; | |||
case 'share': | |||
list($name, $type) = array ( | |||
trim(substr($line, 1, 15)), | |||
trim(strtolower(substr($line, 17, 10))) | |||
); | |||
$i = ($type <> 'disk' && preg_match('/^(.*) Disk/', $line, $regs)) | |||
? array(trim($regs[1]), 'disk') | |||
: array($name, 'disk'); | |||
break; | |||
case 'srvorwg': | |||
list ($name, $master) = array ( | |||
strtolower(trim(substr($line,1,21))), | |||
strtolower(trim(substr($line, 22))) | |||
); | |||
$i = ($mode == 'servers') ? array ($name, "server") : array ($name, "workgroup", $master); | |||
break; | |||
case 'files': | |||
list ($attr, $name) = preg_match ("/^(.*)[ ]+([D|A|H|N|S|R]+)$/", trim ($regs[1]), $regs2) | |||
? array (trim ($regs2[2]), trim ($regs2[1])) | |||
: array ('', trim ($regs[1])); | |||
list ($his, $im) = array ( | |||
explode(':', $regs[6]), 1 + strpos("JanFebMarAprMayJunJulAugSepOctNovDec", $regs[4]) / 3); | |||
$i = ($name <> '.' && $name <> '..') | |||
? array ( | |||
$name, | |||
(strpos($attr,'D') === FALSE) ? 'file' : 'folder', | |||
'attr' => $attr, | |||
'size' => intval($regs[2]), | |||
'time' => mktime ($his[0], $his[1], $his[2], $im, $regs[5], $regs[7]) | |||
) | |||
: array(); | |||
break; | |||
case 'error': | |||
if(substr($regs[0],0,22)=='NT_STATUS_NO_SUCH_FILE'){ | |||
return false; | |||
}elseif(substr($regs[0],0,31)=='NT_STATUS_OBJECT_NAME_COLLISION'){ | |||
return false; | |||
}elseif(substr($regs[0],0,31)=='NT_STATUS_OBJECT_PATH_NOT_FOUND'){ | |||
return false; | |||
}elseif(substr($regs[0],0,31)=='NT_STATUS_OBJECT_NAME_NOT_FOUND'){ | |||
return false; | |||
}elseif(substr($regs[0],0,29)=='NT_STATUS_FILE_IS_A_DIRECTORY'){ | |||
return false; | |||
} | |||
trigger_error($regs[0].' params('.$params.')', E_USER_ERROR); | |||
case 'error-connect': | |||
// connection error can happen after obtaining share list if | |||
// NetBIOS is disabled/blocked on the target server, | |||
// in which case we keep the info and continue | |||
if (!$gotInfo) { | |||
return false; | |||
} | |||
} | |||
if ($i) switch ($i[1]) { | |||
case 'file': | |||
case 'folder': $info['info'][$i[0]] = $i; | |||
case 'disk': | |||
case 'server': | |||
case 'workgroup': $info[$i[1]][] = $i[0]; | |||
$gotInfo = true; | |||
} | |||
} | |||
pclose($output); | |||
// restore previous locale | |||
if ($old_locale===false) { | |||
putenv('LC_ALL'); | |||
} else { | |||
putenv('LC_ALL='.$old_locale); | |||
} | |||
return $info; | |||
} | |||
# stats | |||
function url_stat ($url, $flags = STREAM_URL_STAT_LINK) { | |||
if ($s = smb::getstatcache($url)) { | |||
return $s; | |||
} | |||
list ($stat, $pu) = array (false, smb::parse_url ($url)); | |||
switch ($pu['type']) { | |||
case 'host': | |||
if ($o = smb::look ($pu)) | |||
$stat = stat ("/tmp"); | |||
else | |||
trigger_error ("url_stat(): list failed for host '{$pu['host']}'", E_USER_WARNING); | |||
break; | |||
case 'share': | |||
if (smb::execute("ls", $pu)) | |||
$stat = stat ("/tmp"); | |||
else | |||
trigger_error ("url_stat(): disk resource '{$pu['share']}' not found in '{$pu['host']}'", E_USER_WARNING); | |||
break; | |||
case 'path': | |||
if ($o = smb::execute ('dir "'.$pu['path'].'"', $pu)) { | |||
$p = explode('\\', $pu['path']); | |||
$name = $p[count($p)-1]; | |||
if (isset ($o['info'][$name])) { | |||
$stat = smb::addstatcache ($url, $o['info'][$name]); | |||
} else { | |||
trigger_error ("url_stat(): path '{$pu['path']}' not found", E_USER_WARNING); | |||
} | |||
} else { | |||
return false; | |||
// trigger_error ("url_stat(): dir failed for path '{$pu['path']}'", E_USER_WARNING); | |||
} | |||
break; | |||
default: trigger_error ('error in URL', E_USER_ERROR); | |||
} | |||
return $stat; | |||
} | |||
function addstatcache ($url, $info) { | |||
$url = str_replace('//', '/', $url); | |||
$url = rtrim($url, '/'); | |||
global $__smb_cache; | |||
$is_file = (strpos ($info['attr'],'D') === FALSE); | |||
$s = ($is_file) ? stat ('/etc/passwd') : stat ('/tmp'); | |||
$s[7] = $s['size'] = $info['size']; | |||
$s[8] = $s[9] = $s[10] = $s['atime'] = $s['mtime'] = $s['ctime'] = $info['time']; | |||
return $__smb_cache['stat'][$url] = $s; | |||
} | |||
function getstatcache ($url) { | |||
$url = str_replace('//', '/', $url); | |||
$url = rtrim($url, '/'); | |||
global $__smb_cache; | |||
return isset ($__smb_cache['stat'][$url]) ? $__smb_cache['stat'][$url] : FALSE; | |||
} | |||
function clearstatcache ($url='') { | |||
$url = str_replace('//', '/', $url); | |||
$url = rtrim($url, '/'); | |||
global $__smb_cache; | |||
if ($url == '') $__smb_cache['stat'] = array (); else unset ($__smb_cache['stat'][$url]); | |||
} | |||
# commands | |||
function unlink ($url) { | |||
$pu = smb::parse_url($url); | |||
if ($pu['type'] <> 'path') trigger_error('unlink(): error in URL', E_USER_ERROR); | |||
smb::clearstatcache ($url); | |||
smb_stream_wrapper::cleardircache (dirname($url)); | |||
return smb::execute ('del "'.$pu['path'].'"', $pu); | |||
} | |||
function rename ($url_from, $url_to) { | |||
$replace = false; | |||
list ($from, $to) = array (smb::parse_url($url_from), smb::parse_url($url_to)); | |||
if ($from['host'] <> $to['host'] || | |||
$from['share'] <> $to['share'] || | |||
$from['user'] <> $to['user'] || | |||
$from['pass'] <> $to['pass'] || | |||
$from['domain'] <> $to['domain']) { | |||
trigger_error('rename(): FROM & TO must be in same server-share-user-pass-domain', E_USER_ERROR); | |||
} | |||
if ($from['type'] <> 'path' || $to['type'] <> 'path') { | |||
trigger_error('rename(): error in URL', E_USER_ERROR); | |||
} | |||
smb::clearstatcache ($url_from); | |||
$cmd = ''; | |||
// check if target file exists | |||
if (smb::url_stat($url_to)) { | |||
// delete target file first | |||
$cmd = 'del "' . $to['path'] . '"; '; | |||
$replace = true; | |||
} | |||
$cmd .= 'rename "' . $from['path'] . '" "' . $to['path'] . '"'; | |||
$result = smb::execute($cmd, $to); | |||
if ($replace) { | |||
// clear again, else the cache will return the info | |||
// from the old file | |||
smb::clearstatcache ($url_to); | |||
} | |||
return $result !== false; | |||
} | |||
function mkdir ($url, $mode, $options) { | |||
$pu = smb::parse_url($url); | |||
if ($pu['type'] <> 'path') trigger_error('mkdir(): error in URL', E_USER_ERROR); | |||
return smb::execute ('mkdir "'.$pu['path'].'"', $pu)!==false; | |||
} | |||
function rmdir ($url) { | |||
$pu = smb::parse_url($url); | |||
if ($pu['type'] <> 'path') trigger_error('rmdir(): error in URL', E_USER_ERROR); | |||
smb::clearstatcache ($url); | |||
smb_stream_wrapper::cleardircache (dirname($url)); | |||
return smb::execute ('rmdir "'.$pu['path'].'"', $pu)!==false; | |||
} | |||
} | |||
################################################################### | |||
# SMB_STREAM_WRAPPER - class to be registered for smb:// URLs | |||
################################################################### | |||
class smb_stream_wrapper extends smb { | |||
# variables | |||
private $stream, $url, $parsed_url = array (), $mode, $tmpfile; | |||
private $need_flush = FALSE; | |||
private $dir = array (), $dir_index = -1; | |||
# directories | |||
function dir_opendir ($url, $options) { | |||
if ($d = $this->getdircache ($url)) { | |||
$this->dir = $d; | |||
$this->dir_index = 0; | |||
return TRUE; | |||
} | |||
$pu = smb::parse_url ($url); | |||
switch ($pu['type']) { | |||
case 'host': | |||
if ($o = smb::look ($pu)) { | |||
$this->dir = $o['disk']; | |||
$this->dir_index = 0; | |||
} else { | |||
trigger_error ("dir_opendir(): list failed for host '{$pu['host']}'", E_USER_WARNING); | |||
return false; | |||
} | |||
break; | |||
case 'share': | |||
case 'path': | |||
if (is_array($o = smb::execute ('dir "'.$pu['path'].'\*"', $pu))) { | |||
$this->dir = array_keys($o['info']); | |||
$this->dir_index = 0; | |||
$this->adddircache ($url, $this->dir); | |||
if(substr($url,-1,1)=='/'){ | |||
$url=substr($url,0,-1); | |||
} | |||
foreach ($o['info'] as $name => $info) { | |||
smb::addstatcache($url . '/' . $name, $info); | |||
} | |||
} else { | |||
trigger_error ("dir_opendir(): dir failed for path '".$pu['path']."'", E_USER_WARNING); | |||
return false; | |||
} | |||
break; | |||
default: | |||
trigger_error ('dir_opendir(): error in URL', E_USER_ERROR); | |||
return false; | |||
} | |||
return TRUE; | |||
} | |||
function dir_readdir () { | |||
return ($this->dir_index < count($this->dir)) ? $this->dir[$this->dir_index++] : FALSE; | |||
} | |||
function dir_rewinddir () { $this->dir_index = 0; } | |||
function dir_closedir () { $this->dir = array(); $this->dir_index = -1; return TRUE; } | |||
# cache | |||
function adddircache ($url, $content) { | |||
$url = str_replace('//', '/', $url); | |||
$url = rtrim($url, '/'); | |||
global $__smb_cache; | |||
return $__smb_cache['dir'][$url] = $content; | |||
} | |||
function getdircache ($url) { | |||
$url = str_replace('//', '/', $url); | |||
$url = rtrim($url, '/'); | |||
global $__smb_cache; | |||
return isset ($__smb_cache['dir'][$url]) ? $__smb_cache['dir'][$url] : FALSE; | |||
} | |||
function cleardircache ($url='') { | |||
$url = str_replace('//', '/', $url); | |||
$url = rtrim($url, '/'); | |||
global $__smb_cache; | |||
if ($url == ''){ | |||
$__smb_cache['dir'] = array (); | |||
}else{ | |||
unset ($__smb_cache['dir'][$url]); | |||
} | |||
} | |||
# streams | |||
function stream_open ($url, $mode, $options, $opened_path) { | |||
$this->url = $url; | |||
$this->mode = $mode; | |||
$this->parsed_url = $pu = smb::parse_url($url); | |||
if ($pu['type'] <> 'path') trigger_error('stream_open(): error in URL', E_USER_ERROR); | |||
switch ($mode) { | |||
case 'r': | |||
case 'r+': | |||
case 'rb': | |||
case 'a': | |||
case 'a+': $this->tmpfile = tempnam('/tmp', 'smb.down.'); | |||
$result = smb::execute ('get "'.$pu['path'].'" "'.$this->tmpfile.'"', $pu); | |||
if($result === false){ | |||
return $result; | |||
} | |||
break; | |||
case 'w': | |||
case 'w+': | |||
case 'wb': | |||
case 'x': | |||
case 'x+': $this->cleardircache(); | |||
$this->tmpfile = tempnam('/tmp', 'smb.up.'); | |||
$this->need_flush=true; | |||
} | |||
$this->stream = fopen ($this->tmpfile, $mode); | |||
return TRUE; | |||
} | |||
function stream_close () { return fclose($this->stream); } | |||
function stream_read ($count) { return fread($this->stream, $count); } | |||
function stream_write ($data) { $this->need_flush = TRUE; return fwrite($this->stream, $data); } | |||
function stream_eof () { return feof($this->stream); } | |||
function stream_tell () { return ftell($this->stream); } | |||
// PATCH: the wrapper must return true when fseek succeeded by returning 0. | |||
function stream_seek ($offset, $whence=null) { return fseek($this->stream, $offset, $whence) === 0; } | |||
function stream_flush () { | |||
if ($this->mode <> 'r' && $this->need_flush) { | |||
smb::clearstatcache ($this->url); | |||
smb::execute ('put "'.$this->tmpfile.'" "'.$this->parsed_url['path'].'"', $this->parsed_url); | |||
$this->need_flush = FALSE; | |||
} | |||
} | |||
function stream_stat () { return smb::url_stat ($this->url); } | |||
function __destruct () { | |||
if ($this->tmpfile <> '') { | |||
if ($this->need_flush) $this->stream_flush (); | |||
unlink ($this->tmpfile); | |||
} | |||
} | |||
} | |||
################################################################### | |||
# Register 'smb' protocol ! | |||
################################################################### | |||
stream_wrapper_register('smb', 'smb_stream_wrapper') | |||
or die ('Failed to register protocol'); |
@@ -22,6 +22,8 @@ OC::$CLASSPATH['OC\Files\Storage\SFTP_Key'] = 'files_external/lib/sftp_key.php'; | |||
OC::$CLASSPATH['OC_Mount_Config'] = 'files_external/lib/config.php'; | |||
OC::$CLASSPATH['OCA\Files\External\Api'] = 'files_external/lib/api.php'; | |||
require_once __DIR__ . '/../3rdparty/autoload.php'; | |||
OCP\App::registerAdmin('files_external', 'settings'); | |||
if (OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes') == 'yes') { | |||
OCP\App::registerPersonal('files_external', 'personal'); |
@@ -8,139 +8,271 @@ | |||
namespace OC\Files\Storage; | |||
require_once __DIR__ . '/../3rdparty/smb4php/smb.php'; | |||
use Icewind\SMB\Exception\Exception; | |||
use Icewind\SMB\Exception\NotFoundException; | |||
use Icewind\SMB\NativeServer; | |||
use Icewind\SMB\Server; | |||
use Icewind\Streams\CallbackWrapper; | |||
use Icewind\Streams\IteratorDirectory; | |||
use OC\Files\Filesystem; | |||
class SMB extends \OC\Files\Storage\StreamWrapper{ | |||
private $password; | |||
private $user; | |||
private $host; | |||
private $root; | |||
private $share; | |||
class SMB extends Common { | |||
/** | |||
* @var \Icewind\SMB\Server | |||
*/ | |||
protected $server; | |||
/** | |||
* @var \Icewind\SMB\Share | |||
*/ | |||
protected $share; | |||
/** | |||
* @var \Icewind\SMB\FileInfo[] | |||
*/ | |||
protected $statCache = array(); | |||
public function __construct($params) { | |||
if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) { | |||
$this->host=$params['host']; | |||
$this->user=$params['user']; | |||
$this->password=$params['password']; | |||
$this->share=$params['share']; | |||
$this->root=isset($params['root'])?$params['root']:'/'; | |||
if ( ! $this->root || $this->root[0]!='/') { | |||
$this->root='/'.$this->root; | |||
} | |||
if (substr($this->root, -1, 1)!='/') { | |||
$this->root.='/'; | |||
if (Server::NativeAvailable()) { | |||
$this->server = new NativeServer($params['host'], $params['user'], $params['password']); | |||
} else { | |||
$this->server = new Server($params['host'], $params['user'], $params['password']); | |||
} | |||
if ( ! $this->share || $this->share[0]!='/') { | |||
$this->share='/'.$this->share; | |||
} | |||
if (substr($this->share, -1, 1)=='/') { | |||
$this->share = substr($this->share, 0, -1); | |||
$this->share = $this->server->getShare(trim($params['share'], '/')); | |||
$this->root = isset($params['root']) ? $params['root'] : '/'; | |||
if (!$this->root || $this->root[0] != '/') { | |||
$this->root = '/' . $this->root; | |||
} | |||
} else { | |||
throw new \Exception('Invalid configuration'); | |||
} | |||
} | |||
public function getId(){ | |||
return 'smb::' . $this->user . '@' . $this->host . '/' . $this->share . '/' . $this->root; | |||
/** | |||
* @return string | |||
*/ | |||
public function getId() { | |||
return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '/' . $this->share->getName() . '/' . $this->root; | |||
} | |||
public function constructUrl($path) { | |||
if (substr($path, -1)=='/') { | |||
$path = substr($path, 0, -1); | |||
} | |||
if (substr($path, 0, 1)=='/') { | |||
$path = substr($path, 1); | |||
/** | |||
* @param string $path | |||
* @return string | |||
*/ | |||
protected function buildPath($path) { | |||
return Filesystem::normalizePath($this->root . '/' . $path); | |||
} | |||
/** | |||
* @param string $path | |||
* @return \Icewind\SMB\IFileInfo | |||
*/ | |||
protected function getFileInfo($path) { | |||
$path = $this->buildPath($path); | |||
if (!isset($this->statCache[$path])) { | |||
$this->statCache[$path] = $this->share->stat($path); | |||
} | |||
// remove trailing dots which some versions of samba don't seem to like | |||
$path = rtrim($path, '.'); | |||
$path = urlencode($path); | |||
$user = urlencode($this->user); | |||
$pass = urlencode($this->password); | |||
return 'smb://'.$user.':'.$pass.'@'.$this->host.$this->share.$this->root.$path; | |||
return $this->statCache[$path]; | |||
} | |||
public function stat($path) { | |||
if ( ! $path and $this->root=='/') {//mtime doesn't work for shares | |||
$stat=stat($this->constructUrl($path)); | |||
if (empty($stat)) { | |||
return false; | |||
} | |||
$mtime=$this->shareMTime(); | |||
$stat['mtime']=$mtime; | |||
return $stat; | |||
} else { | |||
$stat = stat($this->constructUrl($path)); | |||
/** | |||
* @param string $path | |||
* @return \Icewind\SMB\IFileInfo[] | |||
*/ | |||
protected function getFolderContents($path) { | |||
$path = $this->buildPath($path); | |||
$files = $this->share->dir($path); | |||
foreach ($files as $file) { | |||
$this->statCache[$path . '/' . $file->getName()] = $file; | |||
} | |||
return $files; | |||
} | |||
// smb4php can return an empty array if the connection could not be established | |||
if (empty($stat)) { | |||
return false; | |||
} | |||
/** | |||
* @param \Icewind\SMB\IFileInfo $info | |||
* @return array | |||
*/ | |||
protected function formatInfo($info) { | |||
return array( | |||
'size' => $info->getSize(), | |||
'mtime' => $info->getMTime() | |||
); | |||
} | |||
return $stat; | |||
} | |||
/** | |||
* @param string $path | |||
* @return array | |||
*/ | |||
public function stat($path) { | |||
return $this->formatInfo($this->getFileInfo($path)); | |||
} | |||
/** | |||
* Unlinks file or directory | |||
* @param string $path | |||
* @return bool | |||
*/ | |||
public function unlink($path) { | |||
if ($this->is_dir($path)) { | |||
$this->rmdir($path); | |||
} | |||
else { | |||
$url = $this->constructUrl($path); | |||
unlink($url); | |||
clearstatcache(false, $url); | |||
return $this->rmdir($path); | |||
} else { | |||
$path = $this->buildPath($path); | |||
unset($this->statCache[$path]); | |||
$this->share->del($path); | |||
return true; | |||
} | |||
// smb4php still returns false even on success so | |||
// check here whether file was really deleted | |||
return !file_exists($path); | |||
} | |||
/** | |||
* check if a file or folder has been updated since $time | |||
* | |||
* @param string $path | |||
* @param int $time | |||
* @return bool | |||
*/ | |||
public function hasUpdated($path,$time) { | |||
if(!$path and $this->root=='/') { | |||
public function hasUpdated($path, $time) { | |||
if (!$path and $this->root == '/') { | |||
// mtime doesn't work for shares, but giving the nature of the backend, | |||
// doing a full update is still just fast enough | |||
return true; | |||
} else { | |||
$actualTime=$this->filemtime($path); | |||
return $actualTime>$time; | |||
$actualTime = $this->filemtime($path); | |||
return $actualTime > $time; | |||
} | |||
} | |||
/** | |||
* get the best guess for the modification time of the share | |||
* @param string $path | |||
* @param string $mode | |||
* @return resource | |||
*/ | |||
private function shareMTime() { | |||
$dh=$this->opendir(''); | |||
$lastCtime=0; | |||
if(is_resource($dh)) { | |||
while (($file = readdir($dh)) !== false) { | |||
if ($file!='.' and $file!='..') { | |||
$ctime=$this->filemtime($file); | |||
if ($ctime>$lastCtime) { | |||
$lastCtime=$ctime; | |||
public function fopen($path, $mode) { | |||
$fullPath = $this->buildPath($path); | |||
try { | |||
switch ($mode) { | |||
case 'r': | |||
case 'rb': | |||
if (!$this->file_exists($path)) { | |||
return false; | |||
} | |||
return $this->share->read($fullPath); | |||
case 'w': | |||
case 'wb': | |||
return $this->share->write($fullPath); | |||
case 'a': | |||
case 'ab': | |||
case 'r+': | |||
case 'w+': | |||
case 'wb+': | |||
case 'a+': | |||
case 'x': | |||
case 'x+': | |||
case 'c': | |||
case 'c+': | |||
//emulate these | |||
if (strrpos($path, '.') !== false) { | |||
$ext = substr($path, strrpos($path, '.')); | |||
} else { | |||
$ext = ''; | |||
} | |||
if ($this->file_exists($path)) { | |||
if (!$this->isUpdatable($path)) { | |||
return false; | |||
} | |||
$tmpFile = $this->getCachedFile($path); | |||
} else { | |||
if (!$this->isCreatable(dirname($path))) { | |||
return false; | |||
} | |||
$tmpFile = \OCP\Files::tmpFile($ext); | |||
} | |||
$source = fopen($tmpFile, $mode); | |||
$share = $this->share; | |||
return CallBackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) { | |||
$share->put($tmpFile, $fullPath); | |||
unlink($tmpFile); | |||
}); | |||
} | |||
return false; | |||
} catch (NotFoundException $e) { | |||
return false; | |||
} | |||
} | |||
public function rmdir($path) { | |||
try { | |||
$this->statCache = array(); | |||
$content = $this->share->dir($this->buildPath($path)); | |||
foreach ($content as $file) { | |||
if ($file->isDirectory()) { | |||
$this->rmdir($path . '/' . $file->getName()); | |||
} else { | |||
$this->share->del($file->getPath()); | |||
} | |||
} | |||
$this->share->rmdir($this->buildPath($path)); | |||
return true; | |||
} catch (NotFoundException $e) { | |||
return false; | |||
} | |||
} | |||
public function touch($path, $time = null) { | |||
if (!$this->file_exists($path)) { | |||
$fh = $this->share->write($this->buildPath($path)); | |||
fclose($fh); | |||
return true; | |||
} | |||
return false; | |||
} | |||
public function opendir($path) { | |||
$files = $this->getFolderContents($path); | |||
$names = array_map(function ($info) { | |||
/** @var \Icewind\SMB\IFileInfo $info */ | |||
return $info->getName(); | |||
}, $files); | |||
return IteratorDirectory::wrap($names); | |||
} | |||
public function filetype($path) { | |||
try { | |||
return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file'; | |||
} catch (NotFoundException $e) { | |||
return false; | |||
} | |||
} | |||
public function mkdir($path) { | |||
$path = $this->buildPath($path); | |||
try { | |||
$this->share->mkdir($path); | |||
return true; | |||
} catch (Exception $e) { | |||
return false; | |||
} | |||
} | |||
public function file_exists($path) { | |||
try { | |||
$this->getFileInfo($path); | |||
return true; | |||
} catch (NotFoundException $e) { | |||
return false; | |||
} | |||
return $lastCtime; | |||
} | |||
/** | |||
* check if smbclient is installed | |||
*/ | |||
public static function checkDependencies() { | |||
$smbClientExists = (bool) \OC_Helper::findBinaryPath('smbclient'); | |||
return $smbClientExists ? true : array('smbclient'); | |||
if (function_exists('shell_exec')) { | |||
$output = shell_exec('command -v smbclient 2> /dev/null'); | |||
if (!empty($output)) { | |||
return true; | |||
} | |||
} | |||
return array('smbclient'); | |||
} | |||
} |
@@ -8,9 +8,12 @@ | |||
namespace OC\Files\Storage; | |||
require_once __DIR__ . '/../3rdparty/smb4php/smb.php'; | |||
class SMB_OC extends \OC\Files\Storage\SMB { | |||
use Icewind\SMB\Exception\AccessDeniedException; | |||
use Icewind\SMB\Exception\Exception; | |||
use Icewind\SMB\Server; | |||
class SMB_OC extends SMB { | |||
private $username_as_share; | |||
/** | |||
@@ -19,18 +22,18 @@ class SMB_OC extends \OC\Files\Storage\SMB { | |||
*/ | |||
public function __construct($params) { | |||
if (isset($params['host']) && \OC::$server->getSession()->exists('smb-credentials')) { | |||
$host=$params['host']; | |||
$host = $params['host']; | |||
$this->username_as_share = ($params['username_as_share'] === 'true'); | |||
$params_auth = json_decode(\OC::$server->getCrypto()->decrypt(\OC::$server->getSession()->get('smb-credentials')), true); | |||
$user = \OC::$server->getSession()->get('loginname'); | |||
$password = $params_auth['password']; | |||
$root=isset($params['root'])?$params['root']:'/'; | |||
$root = isset($params['root']) ? $params['root'] : '/'; | |||
$share = ''; | |||
if ($this->username_as_share) { | |||
$share = '/'.$user; | |||
$share = '/' . $user; | |||
} elseif (isset($params['share'])) { | |||
$share = $params['share']; | |||
} else { | |||
@@ -84,33 +87,15 @@ class SMB_OC extends \OC\Files\Storage\SMB { | |||
} | |||
return false; | |||
} else { | |||
$smb = new \smb(); | |||
$pu = $smb->parse_url($this->constructUrl('')); | |||
// Attempt to connect anonymously | |||
$pu['user'] = ''; | |||
$pu['pass'] = ''; | |||
// Share cannot be checked if dynamic | |||
if ($this->username_as_share) { | |||
if ($smb->look($pu)) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
if (!$pu['share']) { | |||
return false; | |||
} | |||
// The following error messages are expected due to anonymous login | |||
$regexp = array( | |||
'(NT_STATUS_ACCESS_DENIED)' => 'skip' | |||
) + $smb->getRegexp(); | |||
$server = new Server($this->server->getHost(), '', ''); | |||
if ($smb->client("-d 0 " . escapeshellarg('//' . $pu['host'] . '/' . $pu['share']) . " -c exit", $pu, $regexp)) { | |||
try { | |||
$server->listShares(); | |||
return true; | |||
} else { | |||
} catch (AccessDeniedException $e) { | |||
// expected due to anonymous login | |||
return true; | |||
} catch (Exception $e) { | |||
return false; | |||
} | |||
} |
@@ -27,7 +27,7 @@ class SMB extends Storage { | |||
protected function tearDown() { | |||
if ($this->instance) { | |||
\OCP\Files::rmdirr($this->instance->constructUrl('')); | |||
$this->instance->rmdir(''); | |||
} | |||
parent::tearDown(); |