Merge pull request #14064 from nextcloud/smb-3.1.0

update icewind/smb to 3.1.0
This commit is contained in:
Morris Jobke 2019-02-07 16:46:31 +01:00 committed by GitHub
commit c5dc9b57ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 547 additions and 198 deletions

View file

@ -1,4 +1,5 @@
example.php
.editorconfig
icewind/smb/tests
icewind/smb/install_libsmbclient.sh
icewind/smb/Makefile

View file

@ -9,6 +9,6 @@
},
"require": {
"icewind/streams": "0.6.1",
"icewind/smb": "3.0.0"
"icewind/smb": "3.1.0"
}
}

View file

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1465c8a4b4139e514086d5803d90af2d",
"content-hash": "ea11a8c6e4979d35dd3bdf1f057ef0e8",
"packages": [
{
"name": "icewind/smb",
"version": "v3.0.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
"reference": "0d31da4757a37d322e1e181f2286e8a4c89fbc0c"
"reference": "db16d4430cb75f0196eaa6377c5766b19f744c8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/0d31da4757a37d322e1e181f2286e8a4c89fbc0c",
"reference": "0d31da4757a37d322e1e181f2286e8a4c89fbc0c",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/db16d4430cb75f0196eaa6377c5766b19f744c8d",
"reference": "db16d4430cb75f0196eaa6377c5766b19f744c8d",
"shasum": ""
},
"require": {
@ -25,6 +25,7 @@
"php": ">=5.6"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.13",
"phpunit/phpunit": "^5.7"
},
"type": "library",
@ -45,7 +46,7 @@
}
],
"description": "php wrapper for smbclient and libsmbclient-php",
"time": "2018-05-24T09:48:51+00:00"
"time": "2019-02-06T14:17:35+00:00"
},
{
"name": "icewind/streams",

View file

@ -279,7 +279,7 @@ class ClassLoader
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
@ -377,7 +377,7 @@ class ClassLoader
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\';
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {

View file

@ -39,8 +39,11 @@ return array(
'Icewind\\SMB\\IAuth' => $vendorDir . '/icewind/smb/src/IAuth.php',
'Icewind\\SMB\\IFileInfo' => $vendorDir . '/icewind/smb/src/IFileInfo.php',
'Icewind\\SMB\\INotifyHandler' => $vendorDir . '/icewind/smb/src/INotifyHandler.php',
'Icewind\\SMB\\IOptions' => $vendorDir . '/icewind/smb/src/IOptions.php',
'Icewind\\SMB\\IServer' => $vendorDir . '/icewind/smb/src/IServer.php',
'Icewind\\SMB\\IShare' => $vendorDir . '/icewind/smb/src/IShare.php',
'Icewind\\SMB\\ISystem' => $vendorDir . '/icewind/smb/src/ISystem.php',
'Icewind\\SMB\\ITimeZoneProvider' => $vendorDir . '/icewind/smb/src/ITimeZoneProvider.php',
'Icewind\\SMB\\KerberosAuth' => $vendorDir . '/icewind/smb/src/KerberosAuth.php',
'Icewind\\SMB\\Native\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/Native/NativeFileInfo.php',
'Icewind\\SMB\\Native\\NativeReadStream' => $vendorDir . '/icewind/smb/src/Native/NativeReadStream.php',
@ -49,6 +52,7 @@ return array(
'Icewind\\SMB\\Native\\NativeState' => $vendorDir . '/icewind/smb/src/Native/NativeState.php',
'Icewind\\SMB\\Native\\NativeStream' => $vendorDir . '/icewind/smb/src/Native/NativeStream.php',
'Icewind\\SMB\\Native\\NativeWriteStream' => $vendorDir . '/icewind/smb/src/Native/NativeWriteStream.php',
'Icewind\\SMB\\Options' => $vendorDir . '/icewind/smb/src/Options.php',
'Icewind\\SMB\\ServerFactory' => $vendorDir . '/icewind/smb/src/ServerFactory.php',
'Icewind\\SMB\\System' => $vendorDir . '/icewind/smb/src/System.php',
'Icewind\\SMB\\TimeZoneProvider' => $vendorDir . '/icewind/smb/src/TimeZoneProvider.php',

View file

@ -69,8 +69,11 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
'Icewind\\SMB\\IAuth' => __DIR__ . '/..' . '/icewind/smb/src/IAuth.php',
'Icewind\\SMB\\IFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/IFileInfo.php',
'Icewind\\SMB\\INotifyHandler' => __DIR__ . '/..' . '/icewind/smb/src/INotifyHandler.php',
'Icewind\\SMB\\IOptions' => __DIR__ . '/..' . '/icewind/smb/src/IOptions.php',
'Icewind\\SMB\\IServer' => __DIR__ . '/..' . '/icewind/smb/src/IServer.php',
'Icewind\\SMB\\IShare' => __DIR__ . '/..' . '/icewind/smb/src/IShare.php',
'Icewind\\SMB\\ISystem' => __DIR__ . '/..' . '/icewind/smb/src/ISystem.php',
'Icewind\\SMB\\ITimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/ITimeZoneProvider.php',
'Icewind\\SMB\\KerberosAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosAuth.php',
'Icewind\\SMB\\Native\\NativeFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeFileInfo.php',
'Icewind\\SMB\\Native\\NativeReadStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeReadStream.php',
@ -79,6 +82,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
'Icewind\\SMB\\Native\\NativeState' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeState.php',
'Icewind\\SMB\\Native\\NativeStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeStream.php',
'Icewind\\SMB\\Native\\NativeWriteStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeWriteStream.php',
'Icewind\\SMB\\Options' => __DIR__ . '/..' . '/icewind/smb/src/Options.php',
'Icewind\\SMB\\ServerFactory' => __DIR__ . '/..' . '/icewind/smb/src/ServerFactory.php',
'Icewind\\SMB\\System' => __DIR__ . '/..' . '/icewind/smb/src/System.php',
'Icewind\\SMB\\TimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/TimeZoneProvider.php',

View file

@ -1,17 +1,17 @@
[
{
"name": "icewind/smb",
"version": "v3.0.0",
"version_normalized": "3.0.0.0",
"version": "v3.1.0",
"version_normalized": "3.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
"reference": "0d31da4757a37d322e1e181f2286e8a4c89fbc0c"
"reference": "db16d4430cb75f0196eaa6377c5766b19f744c8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/0d31da4757a37d322e1e181f2286e8a4c89fbc0c",
"reference": "0d31da4757a37d322e1e181f2286e8a4c89fbc0c",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/db16d4430cb75f0196eaa6377c5766b19f744c8d",
"reference": "db16d4430cb75f0196eaa6377c5766b19f744c8d",
"shasum": ""
},
"require": {
@ -19,9 +19,10 @@
"php": ">=5.6"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.13",
"phpunit/phpunit": "^5.7"
},
"time": "2018-05-24T09:48:51+00:00",
"time": "2019-02-06T14:17:35+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {

View file

@ -1,3 +1,5 @@
.idea
vendor
composer.lock
.php_cs.cache

View file

@ -0,0 +1,15 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
;
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'braces' => ['position_after_functions_and_oop_constructs' => 'same'],
'binary_operator_spaces' => ['align_double_arrow' => true, 'align_equals' => false],
])
->setIndent("\t")
->setFinder($finder)
;

View file

@ -102,6 +102,15 @@ fwrite($fh, 'bar');
fclose($fh);
```
**Note**: write() will truncate your file to 0bytes. You may open a writeable stream with append() which will point
the cursor to the end of the file or create it if it does not exists yet. (append() is only compatible with libsmbclient-php)
```php
$fh = $share->append('test.txt');
fwrite($fh, 'bar');
fclose($fh);
```
### Using notify
```php
@ -110,6 +119,22 @@ $share->notify('')->listen(function (\Icewind\SMB\Change $change) {
});
```
### Changing network timeouts
```php
$options = new Options();
$options->setTimeout(5);
$serverFactory = new ServerFactory($options);
```
### Customizing system integration
The `smbclient` backend needs to get various information about the system it's running on to function
such as the paths of various binaries or the system timezone.
While the default logic for getting this information should work on most systems, it possible to customize this behaviour.
In order to customize the integration you provide a custom implementation of `ITimezoneProvider` and/or `ISystem` and pass them as arguments to the `ServerFactory`.
## Testing SMB
Use the following steps to check if the library can connect to your SMB share.

View file

@ -13,7 +13,8 @@
"icewind/streams": ">=0.2.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7"
"phpunit/phpunit": "^5.7",
"friendsofphp/php-cs-fixer": "^2.13"
},
"autoload" : {
"psr-4": {

View file

@ -21,7 +21,6 @@
namespace Icewind\SMB;
abstract class AbstractServer implements IServer {
const LOCALE = 'en_US.UTF-8';
@ -36,7 +35,7 @@ abstract class AbstractServer implements IServer {
protected $auth;
/**
* @var \Icewind\SMB\System
* @var ISystem
*/
protected $system;
@ -45,41 +44,41 @@ abstract class AbstractServer implements IServer {
*/
protected $timezoneProvider;
/** @var IOptions */
protected $options;
/**
* @param string $host
* @param IAuth $auth
* @param System $system
* @param ISystem $system
* @param TimeZoneProvider $timeZoneProvider
* @param IOptions $options
*/
public function __construct($host, IAuth $auth, System $system, TimeZoneProvider $timeZoneProvider) {
public function __construct($host, IAuth $auth, ISystem $system, TimeZoneProvider $timeZoneProvider, IOptions $options) {
$this->host = $host;
$this->auth = $auth;
$this->system = $system;
$this->timezoneProvider = $timeZoneProvider;
$this->options = $options;
}
/**
* @return IAuth
*/
public function getAuth() {
return $this->auth;
}
/**
* return string
*/
public function getHost() {
return $this->host;
}
/**
* @return string
*/
public function getTimeZone() {
return $this->timezoneProvider->get();
return $this->timezoneProvider->get($this->host);
}
public function getSystem() {
return $this->system;
}
public function getOptions() {
return $this->options;
}
}

View file

@ -13,7 +13,7 @@ abstract class AbstractShare implements IShare {
private $forbiddenCharacters;
public function __construct() {
$this->forbiddenCharacters = array('?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r");
$this->forbiddenCharacters = ['?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r"];
}
protected function verifyPath($path) {

View file

@ -21,7 +21,6 @@
namespace Icewind\SMB;
class AnonymousAuth implements IAuth {
public function getUsername() {
return null;

View file

@ -21,7 +21,6 @@
namespace Icewind\SMB;
class BasicAuth implements IAuth {
/** @var string */
private $username;
@ -62,5 +61,4 @@ class BasicAuth implements IAuth {
public function setExtraSmbClientOptions($smbClientState) {
// noop
}
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class AccessDeniedException extends ConnectException {}
class AccessDeniedException extends ConnectException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class AlreadyExistsException extends InvalidRequestException {}
class AlreadyExistsException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class AuthenticationException extends ConnectException{}
class AuthenticationException extends ConnectException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class ConnectException extends Exception {}
class ConnectException extends Exception {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class ConnectionException extends ConnectException {}
class ConnectionException extends ConnectException {
}

View file

@ -8,7 +8,7 @@
namespace Icewind\SMB\Exception;
class Exception extends \Exception {
static public function unknown($path, $error) {
public static function unknown($path, $error) {
$message = 'Unknown error (' . $error . ')';
if ($path) {
$message .= ' for ' . $path;
@ -23,7 +23,7 @@ class Exception extends \Exception {
* @param string $path
* @return Exception
*/
static public function fromMap(array $exceptionMap, $error, $path) {
public static function fromMap(array $exceptionMap, $error, $path) {
if (isset($exceptionMap[$error])) {
$exceptionClass = $exceptionMap[$error];
if (is_numeric($error)) {

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class FileInUseException extends InvalidRequestException {}
class FileInUseException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class ForbiddenException extends InvalidRequestException {}
class ForbiddenException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class InvalidArgumentException extends InvalidRequestException {}
class InvalidArgumentException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class InvalidHostException extends ConnectException {}
class InvalidHostException extends ConnectException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class InvalidParameterException extends InvalidRequestException {}
class InvalidParameterException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class InvalidPathException extends InvalidRequestException {}
class InvalidPathException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class InvalidTypeException extends InvalidRequestException {}
class InvalidTypeException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class NoLoginServerException extends ConnectException {}
class NoLoginServerException extends ConnectException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class NotEmptyException extends InvalidRequestException {}
class NotEmptyException extends InvalidRequestException {
}

View file

@ -7,4 +7,5 @@
namespace Icewind\SMB\Exception;
class NotFoundException extends InvalidRequestException {}
class NotFoundException extends InvalidRequestException {
}

View file

@ -8,7 +8,6 @@
namespace Icewind\SMB;
interface INotifyHandler {
// https://msdn.microsoft.com/en-us/library/dn392331.aspx
const NOTIFY_ADDED = 1;

View file

@ -0,0 +1,29 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
interface IOptions {
/**
* @return int
*/
public function getTimeout();
}

View file

@ -52,13 +52,18 @@ interface IServer {
public function getTimeZone();
/**
* @return System
* @return ISystem
*/
public function getSystem();
/**
* @param System $system
* @return IOptions
*/
public function getOptions();
/**
* @param ISystem $system
* @return bool
*/
public static function available(System $system);
public static function available(ISystem $system);
}

View file

@ -52,6 +52,7 @@ interface IShare {
/**
* Open a writable stream to a remote file
* Note: This method will truncate the file to 0bytes
*
* @param string $target
* @return resource a write only stream to upload a remote file
@ -61,6 +62,18 @@ interface IShare {
*/
public function write($target);
/**
* Open a writable stream to a remote file and set the cursor to the end of the file
*
* @param string $target
* @return resource a write only stream to upload a remote file
*
* @throws \Icewind\SMB\Exception\NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException
* @throws \Icewind\SMB\Exception\InvalidRequestException
*/
public function append($target);
/**
* Rename a remote file
*

View file

@ -0,0 +1,71 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
/**
* The `ISystem` interface provides a way to access system dependent information
* such as the availability and location of certain binaries.
*/
interface ISystem {
/**
* Get the path to a file descriptor of the current process
*
* @param int $num the file descriptor id
* @return string
*/
public function getFD($num);
/**
* Get the full path to the `smbclient` binary of false if the binary is not available
*
* @return string|bool
*/
public function getSmbclientPath();
/**
* Get the full path to the `net` binary of false if the binary is not available
*
* @return string|bool
*/
public function getNetPath();
/**
* Get the full path to the `stdbuf` binary of false if the binary is not available
*
* @return string|bool
*/
public function getStdBufPath();
/**
* Get the full path to the `date` binary of false if the binary is not available
*
* @return string|bool
*/
public function getDatePath();
/**
* Whether or not the smbclient php extension is enabled
*
* @return bool
*/
public function libSmbclientAvailable();
}

View file

@ -0,0 +1,32 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
interface ITimeZoneProvider {
/**
* Get the timezone of the smb server
*
* @param string $host
* @return string
*/
public function get($host);
}

View file

@ -45,5 +45,4 @@ class KerberosAuth implements IAuth {
smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true);
smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false);
}
}

View file

@ -30,7 +30,10 @@ class NativeFileInfo implements IFileInfo {
/**
* @var array|null
*/
protected $statCache;
protected $statCache = null;
/** @var callable|null */
protected $statCallback = null;
/**
* @var int
@ -41,13 +44,20 @@ class NativeFileInfo implements IFileInfo {
* @param NativeShare $share
* @param string $path
* @param string $name
* @param array $stat
* @param array|callable $stat
*/
public function __construct($share, $path, $name, $stat = null) {
public function __construct($share, $path, $name, $stat) {
$this->share = $share;
$this->path = $path;
$this->name = $name;
$this->statCache = $stat;
if (is_array($stat)) {
$this->statCache = $stat;
} elseif (is_callable($stat)) {
$this->statCallback = $stat;
} else {
throw new \InvalidArgumentException('$stat needs to be an array or callback');
}
}
/**
@ -69,7 +79,7 @@ class NativeFileInfo implements IFileInfo {
*/
protected function stat() {
if (is_null($this->statCache)) {
$this->statCache = $this->share->getStat($this->getPath());
$this->statCache = call_user_func($this->statCallback);
}
return $this->statCache;
}

View file

@ -25,7 +25,6 @@ class NativeReadStream extends NativeStream {
$this->readBuffer = fopen('php://memory', 'r+');
return parent::stream_open($path, $mode, $options, $opened_path);
}
/**
@ -39,13 +38,13 @@ class NativeReadStream extends NativeStream {
*/
public static function wrap($state, $smbStream, $mode, $url) {
stream_wrapper_register('nativesmb', NativeReadStream::class);
$context = stream_context_create(array(
'nativesmb' => array(
$context = stream_context_create([
'nativesmb' => [
'state' => $state,
'handle' => $smbStream,
'url' => $url
)
));
]
]);
$fh = fopen('nativesmb://', $mode, false, $context);
stream_wrapper_unregister('nativesmb');
return $fh;

View file

@ -9,7 +9,8 @@ namespace Icewind\SMB\Native;
use Icewind\SMB\AbstractServer;
use Icewind\SMB\IAuth;
use Icewind\SMB\System;
use Icewind\SMB\IOptions;
use Icewind\SMB\ISystem;
use Icewind\SMB\TimeZoneProvider;
class NativeServer extends AbstractServer {
@ -18,19 +19,13 @@ class NativeServer extends AbstractServer {
*/
protected $state;
/**
* @param string $host
* @param IAuth $auth
* @param System $system
* @param TimeZoneProvider $timeZoneProvider
*/
public function __construct($host, IAuth $auth, System $system, TimeZoneProvider $timeZoneProvider) {
parent::__construct($host, $auth, $system, $timeZoneProvider);
public function __construct($host, IAuth $auth, ISystem $system, TimeZoneProvider $timeZoneProvider, IOptions $options) {
parent::__construct($host, $auth, $system, $timeZoneProvider, $options);
$this->state = new NativeState();
}
protected function connect() {
$this->state->init($this->getAuth());
$this->state->init($this->getAuth(), $this->getOptions());
}
/**
@ -40,7 +35,7 @@ class NativeServer extends AbstractServer {
*/
public function listShares() {
$this->connect();
$shares = array();
$shares = [];
$dh = $this->state->opendir('smb://' . $this->getHost());
while ($share = $this->state->readdir($dh)) {
if ($share['type'] === 'file share') {
@ -62,10 +57,10 @@ class NativeServer extends AbstractServer {
/**
* Check if the smbclient php extension is available
*
* @param System $system
* @param ISystem $system
* @return bool
*/
public static function available(System $system) {
return function_exists('smbclient_state_new');
public static function available(ISystem $system) {
return $system->libSmbclientAvailable();
}
}

View file

@ -53,7 +53,7 @@ class NativeShare extends AbstractShare {
}
$this->state = new NativeState();
$this->state->init($this->server->getAuth());
$this->state->init($this->server->getAuth(), $this->server->getOptions());
return $this->state;
}
@ -87,13 +87,16 @@ class NativeShare extends AbstractShare {
* @throws \Icewind\SMB\Exception\InvalidTypeException
*/
public function dir($path) {
$files = array();
$files = [];
$dh = $this->getState()->opendir($this->buildUrl($path));
while ($file = $this->getState()->readdir($dh)) {
$name = $file['name'];
if ($name !== '.' and $name !== '..') {
$files [] = new NativeFileInfo($this, $path . '/' . $name, $name);
$fullPath = $path . '/' . $name;
$files [] = new NativeFileInfo($this, $fullPath, $name, function () use ($fullPath) {
return $this->getStat($fullPath);
});
}
}
@ -106,10 +109,27 @@ class NativeShare extends AbstractShare {
* @return \Icewind\SMB\IFileInfo
*/
public function stat($path) {
return new NativeFileInfo($this, $path, basename($path), $this->getStat($path));
return new NativeFileInfo($this, $path, self::mb_basename($path), $this->getStat($path));
}
public function getStat($path) {
/**
* Multibyte unicode safe version of basename()
*
* @param string $path
* @link http://php.net/manual/en/function.basename.php#121405
* @return string
*/
protected static function mb_basename($path) {
if (preg_match('@^.*[\\\\/]([^\\\\/]+)$@s', $path, $matches)) {
return $matches[1];
} elseif (preg_match('@^([^\\\\/]+)$@s', $path, $matches)) {
return $matches[1];
}
return '';
}
private function getStat($path) {
return $this->getState()->stat($this->buildUrl($path));
}
@ -228,7 +248,7 @@ class NativeShare extends AbstractShare {
}
/**
* Open a readable stream top a remote file
* Open a readable stream to a remote file
*
* @param string $source
* @return resource a read only stream with the contents of the remote file
@ -243,10 +263,11 @@ class NativeShare extends AbstractShare {
}
/**
* Open a readable stream top a remote file
* Open a writeable stream to a remote file
* Note: This method will truncate the file to 0bytes first
*
* @param string $source
* @return resource a read only stream with the contents of the remote file
* @return resource a writeable stream
*
* @throws \Icewind\SMB\Exception\NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException
@ -257,6 +278,21 @@ class NativeShare extends AbstractShare {
return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url);
}
/**
* Open a writeable stream and set the cursor to the end of the stream
*
* @param string $source
* @return resource a writeable stream
*
* @throws \Icewind\SMB\Exception\NotFoundException
* @throws \Icewind\SMB\Exception\InvalidTypeException
*/
public function append($source) {
$url = $this->buildUrl($source);
$handle = $this->getState()->open($url, "a");
return NativeWriteStream::wrap($this->getState(), $handle, "a", $url);
}
/**
* Get extended attributes for the path
*
@ -269,15 +305,14 @@ class NativeShare extends AbstractShare {
}
/**
* Get extended attributes for the path
* Set extended attributes for the given path
*
* @param string $path
* @param string $attribute attribute to get the info
* @param mixed $value
* @return string the attribute value
* @param string|int $value
* @return mixed the attribute value
*/
public function setAttribute($path, $attribute, $value) {
if ($attribute === 'system.dos_attr.mode' and is_int($value)) {
$value = '0x' . dechex($value);
}
@ -286,6 +321,8 @@ class NativeShare extends AbstractShare {
}
/**
* Set DOS comaptible node mode
*
* @param string $path
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
* @return mixed
@ -295,16 +332,19 @@ class NativeShare extends AbstractShare {
}
/**
* Start smb notify listener
* Note: This is a blocking call
*
* @param string $path
* @return INotifyHandler
*/
public function notify($path) {
// php-smbclient does support notify (https://github.com/eduardok/libsmbclient-php/issues/29)
// php-smbclient does not support notify (https://github.com/eduardok/libsmbclient-php/issues/29)
// so we use the smbclient based backend for this
if (!Server::available($this->server->getSystem())) {
throw new DependencyException('smbclient not found in path for notify command');
}
$share = new Share($this->server, $this->getName());
$share = new Share($this->server, $this->getName(), $this->server->getSystem());
return $share->notify($path);
}

View file

@ -21,6 +21,7 @@ use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\Exception\OutOfSpaceException;
use Icewind\SMB\Exception\TimedOutException;
use Icewind\SMB\IAuth;
use Icewind\SMB\IOptions;
/**
* Low level wrapper for libsmbclient-php with error handling
@ -76,14 +77,16 @@ class NativeState {
/**
* @param IAuth $auth
* @param IOptions $options
* @return bool
*/
public function init(IAuth $auth) {
public function init(IAuth $auth, IOptions $options) {
if ($this->connected) {
return true;
}
$this->state = smbclient_state_new();
smbclient_option_set($this->state, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, false);
smbclient_option_set($this->state, SMBCLIENT_OPT_TIMEOUT, $options->getTimeout() * 1000);
$auth->setExtraSmbClientOptions($this->state);
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());

View file

@ -48,13 +48,13 @@ class NativeStream implements File {
*/
public static function wrap($state, $smbStream, $mode, $url) {
stream_wrapper_register('nativesmb', NativeStream::class);
$context = stream_context_create(array(
'nativesmb' => array(
$context = stream_context_create([
'nativesmb' => [
'state' => $state,
'handle' => $smbStream,
'url' => $url
)
));
]
]);
$fh = fopen('nativesmb://', $mode, false, $context);
stream_wrapper_unregister('nativesmb');
return $fh;

View file

@ -25,7 +25,6 @@ class NativeWriteStream extends NativeStream {
$this->writeBuffer = fopen('php://memory', 'r+');
return parent::stream_open($path, $mode, $options, $opened_path);
}
/**
@ -39,13 +38,13 @@ class NativeWriteStream extends NativeStream {
*/
public static function wrap($state, $smbStream, $mode, $url) {
stream_wrapper_register('nativesmb', NativeWriteStream::class);
$context = stream_context_create(array(
'nativesmb' => array(
$context = stream_context_create([
'nativesmb' => [
'state' => $state,
'handle' => $smbStream,
'url' => $url
)
));
]
]);
$fh = fopen('nativesmb://', $mode, false, $context);
stream_wrapper_unregister('nativesmb');
return $fh;

View file

@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
class Options implements IOptions {
/** @var int */
private $timeout = 20;
public function getTimeout() {
return $this->timeout;
}
public function setTimeout($timeout) {
$this->timeout = $timeout;
}
}

View file

@ -21,7 +21,6 @@
namespace Icewind\SMB;
use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Native\NativeServer;
use Icewind\SMB\Wrapped\Server;
@ -32,8 +31,41 @@ class ServerFactory {
Server::class
];
/** @var System|null */
private $system = null;
/** @var System */
private $system;
/** @var IOptions */
private $options;
/** @var ITimeZoneProvider */
private $timeZoneProvider;
/**
* ServerFactory constructor.
*
* @param IOptions|null $options
* @param ISystem|null $system
* @param ITimeZoneProvider|null $timeZoneProvider
*/
public function __construct(
IOptions $options = null,
ISystem $system = null,
ITimeZoneProvider $timeZoneProvider = null
) {
if (is_null($options)) {
$options = new Options();
}
if (is_null($system)) {
$system = new System();
}
if (is_null($timeZoneProvider)) {
$timeZoneProvider = new TimeZoneProvider($system);
}
$this->options = $options;
$this->system = $system;
$this->timeZoneProvider = $timeZoneProvider;
}
/**
* @param $host
@ -43,22 +75,11 @@ class ServerFactory {
*/
public function createServer($host, IAuth $credentials) {
foreach (self::BACKENDS as $backend) {
if (call_user_func("$backend::available", $this->getSystem())) {
return new $backend($host, $credentials, $this->getSystem(), new TimeZoneProvider($host, $this->getSystem()));
if (call_user_func("$backend::available", $this->system)) {
return new $backend($host, $credentials, $this->system, $this->timeZoneProvider, $this->options);
}
}
throw new DependencyException('No valid backend available, ensure smbclient is in the path or php-smbclient is installed');
}
/**
* @return System
*/
private function getSystem() {
if (is_null($this->system)) {
$this->system = new System();
}
return $this->system;
}
}

View file

@ -9,14 +9,18 @@ namespace Icewind\SMB;
use Icewind\SMB\Exception\Exception;
class System {
private $smbclient;
class System implements ISystem {
/** @var (string|bool)[] */
private $paths = [];
private $net;
private $stdbuf;
public static function getFD($num) {
/**
* Get the path to a file descriptor of the current process
*
* @param int $num the file descriptor id
* @return string
* @throws Exception
*/
public function getFD($num) {
$folders = [
'/proc/self/fd',
'/dev/fd'
@ -30,26 +34,32 @@ class System {
}
public function getSmbclientPath() {
if (!$this->smbclient) {
$this->smbclient = trim(`which smbclient`);
}
return $this->smbclient;
return $this->getBinaryPath('smbclient');
}
public function getNetPath() {
if (!$this->net) {
$this->net = trim(`which net`);
}
return $this->net;
return $this->getBinaryPath('net');
}
public function hasStdBuf() {
if (!$this->stdbuf) {
public function getStdBufPath() {
return $this->getBinaryPath('stdbuf');
}
public function getDatePath() {
return $this->getBinaryPath('date');
}
public function libSmbclientAvailable() {
return function_exists('smbclient_state_new');
}
protected function getBinaryPath($binary) {
if (!isset($this->paths[$binary])) {
$result = null;
$output = array();
exec('which stdbuf 2>&1', $output, $result);
$this->stdbuf = $result === 0;
$output = [];
exec("which $binary 2>&1", $output, $result);
$this->paths[$binary] = $result === 0 ? trim(implode('', $output)) : false;
}
return $this->stdbuf;
return $this->paths[$binary];
}
}

View file

@ -7,48 +7,48 @@
namespace Icewind\SMB;
class TimeZoneProvider {
class TimeZoneProvider implements ITimeZoneProvider {
/**
* @var string
* @var string[]
*/
private $host;
private $timeZones = [];
/**
* @var string
*/
private $timeZone;
/**
* @var System
* @var ISystem
*/
private $system;
/**
* @param string $host
* @param System $system
* @param ISystem $system
*/
public function __construct($host, System $system) {
$this->host = $host;
public function __construct(ISystem $system) {
$this->system = $system;
}
public function get() {
if (!$this->timeZone) {
public function get($host) {
if (!isset($this->timeZones[$host])) {
$timeZone = null;
$net = $this->system->getNetPath();
// for local domain names we can assume same timezone
if ($net && strpos($this->host, '.') !== false) {
$command = sprintf('%s time zone -S %s',
if ($net && $host && strpos($host, '.') !== false) {
$command = sprintf(
'%s time zone -S %s',
$net,
escapeshellarg($this->host)
escapeshellarg($host)
);
$this->timeZone = exec($command);
$timeZone = exec($command);
}
if ($this->timeZone) {
// fallback to server timezone
$this->timeZone = date_default_timezone_get();
if (!$timeZone) {
$date = $this->system->getDatePath();
if ($date) {
$timeZone = exec($date . " +%z");
} else {
$timeZone = date_default_timezone_get();
}
}
$this->timeZones[$host] = $timeZone;
}
return $this->timeZone;
return $this->timeZones[$host];
}
}

View file

@ -20,7 +20,7 @@ class Connection extends RawConnection {
/** @var Parser */
private $parser;
public function __construct($command, Parser $parser, $env = array()) {
public function __construct($command, Parser $parser, $env = []) {
parent::__construct($command, $env);
$this->parser = $parser;
}
@ -65,7 +65,7 @@ class Connection extends RawConnection {
$promptLine = $this->readLine(); //first line is prompt
$this->parser->checkConnectionError($promptLine);
$output = array();
$output = [];
$line = $this->readLine();
if ($line === false) {
$this->unknownError($promptLine);

View file

@ -8,7 +8,6 @@
namespace Icewind\SMB\Wrapped;
use Icewind\SMB\Change;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\RevisionMismatchException;

View file

@ -19,15 +19,19 @@ use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NoLoginServerException;
use Icewind\SMB\Exception\NotEmptyException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\TimeZoneProvider;
class Parser {
const MSG_NOT_FOUND = 'Error opening local file ';
/**
* @var \Icewind\SMB\TimeZoneProvider
* @var string
*/
protected $timeZoneProvider;
protected $timeZone;
/**
* @var string
*/
private $host;
// todo replace with static once <5.6 support is dropped
// see error.h
@ -55,10 +59,10 @@ class Parser {
];
/**
* @param TimeZoneProvider $timeZoneProvider
* @param string $timeZone
*/
public function __construct(TimeZoneProvider $timeZoneProvider) {
$this->timeZoneProvider = $timeZoneProvider;
public function __construct($timeZone) {
$this->timeZone = $timeZone;
}
private function getErrorCode($line) {
@ -135,7 +139,10 @@ class Parser {
$name = isset($words[0]) ? $words[0] : '';
$value = isset($words[1]) ? $words[1] : '';
$value = trim($value);
$data[$name] = $value;
if (!isset($data[$name])) {
$data[$name] = $value;
}
}
return [
'mtime' => strtotime($data['write_time']),
@ -149,13 +156,13 @@ class Parser {
array_pop($output);
$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
//2 spaces, filename, optional type, size, date
$content = array();
$content = [];
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->timeZoneProvider->get());
$time = strtotime($time . ' ' . $this->timeZone);
$content[] = new FileInfo($basePath . '/' . $name, $name, $size, $time, $mode);
}
}
@ -164,14 +171,14 @@ class Parser {
}
public function parseListShares($output) {
$shareNames = array();
$shareNames = [];
foreach ($output as $line) {
if (strpos($line, '|')) {
list($type, $name, $description) = explode('|', $line);
if (strtolower($type) === 'disk') {
$shareNames[$name] = $description;
}
} else if (strpos($line, 'Disk')) {
} elseif (strpos($line, 'Disk')) {
// new output format
list($name, $description) = explode('Disk', $line);
$shareNames[trim($name)] = trim($description);

View file

@ -13,22 +13,22 @@ use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\IShare;
use Icewind\SMB\System;
use Icewind\SMB\ISystem;
class Server extends AbstractServer {
/**
* Check if the smbclient php extension is available
*
* @param System $system
* @param ISystem $system
* @return bool
*/
public static function available(System $system) {
public static function available(ISystem $system) {
return $system->getSmbclientPath();
}
private function getAuthFileArgument() {
if ($this->getAuth()->getUsername()) {
return '--authentication-file=' . System::getFD(3);
return '--authentication-file=' . $this->system->getFD(3);
} else {
return '';
}
@ -42,7 +42,8 @@ class Server extends AbstractServer {
* @throws ConnectException
*/
public function listShares() {
$command = sprintf('%s %s %s -L %s',
$command = sprintf(
'%s %s %s -L %s',
$this->system->getSmbclientPath(),
$this->getAuthFileArgument(),
$this->getAuth()->getExtraCommandLineArguments(),
@ -73,7 +74,7 @@ class Server extends AbstractServer {
$shareNames = $parser->parseListShares($output);
$shares = array();
$shares = [];
foreach ($shareNames as $name => $description) {
$shares[] = $this->getShare($name);
}

View file

@ -13,12 +13,14 @@ use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Exception\FileInUseException;
use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\Exception\InvalidRequestException;
use Icewind\SMB\IFileInfo;
use Icewind\SMB\INotifyHandler;
use Icewind\SMB\IServer;
use Icewind\SMB\System;
use Icewind\SMB\TimeZoneProvider;
use Icewind\SMB\ISystem;
use Icewind\Streams\CallbackWrapper;
use Icewind\SMB\Native\NativeShare;
use Icewind\SMB\Native\NativeServer;
class Share extends AbstractShare {
/**
@ -42,7 +44,7 @@ class Share extends AbstractShare {
protected $parser;
/**
* @var System
* @var ISystem
*/
private $system;
@ -56,28 +58,30 @@ class Share extends AbstractShare {
/**
* @param IServer $server
* @param string $name
* @param System $system
* @param ISystem $system
*/
public function __construct(IServer $server, $name, System $system = null) {
public function __construct(IServer $server, $name, ISystem $system) {
parent::__construct();
$this->server = $server;
$this->name = $name;
$this->system = (!is_null($system)) ? $system : new System();
$this->parser = new Parser(new TimeZoneProvider($this->server->getHost(), $this->system));
$this->system = $system;
$this->parser = new Parser($server->getTimeZone());
}
private function getAuthFileArgument() {
if ($this->server->getAuth()->getUsername()) {
return '--authentication-file=' . System::getFD(3);
return '--authentication-file=' . $this->system->getFD(3);
} else {
return '';
}
}
protected function getConnection() {
$command = sprintf('%s%s %s %s %s',
$this->system->hasStdBuf() ? 'stdbuf -o0 ' : '',
$command = sprintf(
'%s%s -t %s %s %s %s',
$this->system->getStdBufPath() ? $this->system->getStdBufPath() . ' -o0 ' : '',
$this->system->getSmbclientPath(),
$this->server->getOptions()->getTimeout(),
$this->getAuthFileArgument(),
$this->server->getAuth()->getExtraCommandLineArguments(),
escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
@ -159,13 +163,14 @@ class Share extends AbstractShare {
if ($path !== "" && $path !== "/") {
$parent = dirname($path);
$dir = $this->dir($parent);
$file = array_values(array_filter($dir, function(IFileInfo $info) use ($path) {
$file = array_values(array_filter($dir, function (IFileInfo $info) use ($path) {
return $info->getPath() === $path;
}));
if ($file) {
return $file[0];
}
}
$escapedPath = $this->escapePath($path);
$output = $this->execute('allinfo ' . $escapedPath);
// Windows and non Windows Fileserver may respond different
@ -307,7 +312,7 @@ class Share extends AbstractShare {
// since we can't re-use the same file descriptor over multiple calls
$connection = $this->getConnection();
$connection->write('get ' . $source . ' ' . System::getFD(5));
$connection->write('get ' . $source . ' ' . $this->system->getFD(5));
$connection->write('exit');
$fh = $connection->getFileOutputStream();
stream_context_set_option($fh, 'file', 'connection', $connection);
@ -330,7 +335,7 @@ class Share extends AbstractShare {
$connection = $this->getConnection();
$fh = $connection->getFileInputStream();
$connection->write('put ' . System::getFD(4) . ' ' . $target);
$connection->write('put ' . $this->system->getFD(4) . ' ' . $target);
$connection->write('exit');
// use a close callback to ensure the upload is finished before continuing
@ -340,6 +345,18 @@ class Share extends AbstractShare {
});
}
/**
* Append to stream
* Note: smbclient does not support this (Use php-libsmbclient)
*
* @param string $target
*
* @throws \Icewind\SMB\Exception\DependencyException
*/
public function append($target) {
throw new DependencyException('php-libsmbclient is required for append');
}
/**
* @param string $path
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
@ -376,7 +393,7 @@ class Share extends AbstractShare {
* @throws DependencyException
*/
public function notify($path) {
if (!$this->system->hasStdBuf()) { //stdbuf is required to disable smbclient's output buffering
if (!$this->system->getStdBufPath()) { //stdbuf is required to disable smbclient's output buffering
throw new DependencyException('stdbuf is required for usage of the notify command');
}
$connection = $this->getConnection(); // use a fresh connection since the notify command blocks the process