mirror of
https://codeberg.org/icewind/SMB.git
synced 2026-06-03 09:14:06 +02:00
all the types
This commit is contained in:
parent
515b42586e
commit
84fa890ea7
28 changed files with 330 additions and 110 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<psalm
|
<psalm
|
||||||
errorLevel="4"
|
errorLevel="1"
|
||||||
resolveFromConfigFile="true"
|
resolveFromConfigFile="true"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns="https://getpsalm.org/schema/config"
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,18 @@ namespace Icewind\SMB;
|
||||||
use Icewind\SMB\Exception\InvalidPathException;
|
use Icewind\SMB\Exception\InvalidPathException;
|
||||||
|
|
||||||
abstract class AbstractShare implements IShare {
|
abstract class AbstractShare implements IShare {
|
||||||
|
/** @var string[] */
|
||||||
private $forbiddenCharacters;
|
private $forbiddenCharacters;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->forbiddenCharacters = ['?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r"];
|
$this->forbiddenCharacters = ['?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r"];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function verifyPath($path) {
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @throws InvalidPathException
|
||||||
|
*/
|
||||||
|
protected function verifyPath(string $path): void {
|
||||||
foreach ($this->forbiddenCharacters as $char) {
|
foreach ($this->forbiddenCharacters as $char) {
|
||||||
if (strpos($path, $char) !== false) {
|
if (strpos($path, $char) !== false) {
|
||||||
throw new InvalidPathException('Invalid path, "' . $char . '" is not allowed');
|
throw new InvalidPathException('Invalid path, "' . $char . '" is not allowed');
|
||||||
|
|
@ -24,7 +29,10 @@ abstract class AbstractShare implements IShare {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setForbiddenChars(array $charList) {
|
/**
|
||||||
|
* @param string[] $charList
|
||||||
|
*/
|
||||||
|
public function setForbiddenChars(array $charList): void {
|
||||||
$this->forbiddenCharacters = $charList;
|
$this->forbiddenCharacters = $charList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class AnonymousAuth implements IAuth {
|
||||||
return '-N';
|
return '-N';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setExtraSmbClientOptions($smbClientState) {
|
public function setExtraSmbClientOptions($smbClientState): void {
|
||||||
smbclient_option_set($smbClientState, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, true);
|
smbclient_option_set($smbClientState, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class BasicAuth implements IAuth {
|
||||||
return ($this->workgroup) ? '-W ' . escapeshellarg($this->workgroup) : '';
|
return ($this->workgroup) ? '-W ' . escapeshellarg($this->workgroup) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setExtraSmbClientOptions($smbClientState) {
|
public function setExtraSmbClientOptions($smbClientState): void {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,36 @@
|
||||||
|
|
||||||
namespace Icewind\SMB\Exception;
|
namespace Icewind\SMB\Exception;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-consistent-constructor
|
||||||
|
*/
|
||||||
class Exception extends \Exception {
|
class Exception extends \Exception {
|
||||||
public static function unknown(?string $path, $error) {
|
public function __construct(string $message = "", int $code = 0, Throwable $previous = null) {
|
||||||
$message = 'Unknown error (' . $error . ')';
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $path
|
||||||
|
* @param string|int|null $error
|
||||||
|
* @return Exception
|
||||||
|
*/
|
||||||
|
public static function unknown(?string $path, $error): Exception {
|
||||||
|
$message = 'Unknown error (' . (string)$error . ')';
|
||||||
if ($path) {
|
if ($path) {
|
||||||
$message .= ' for ' . $path;
|
$message .= ' for ' . $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Exception($message, is_string($error) ? 0 : $error);
|
return new Exception($message, is_int($error) ? $error : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int|string, class-string<Exception>> $exceptionMap
|
||||||
|
* @param string|int|null $error
|
||||||
|
* @param string|null $path
|
||||||
|
* @return Exception
|
||||||
|
*/
|
||||||
public static function fromMap(array $exceptionMap, $error, ?string $path): Exception {
|
public static function fromMap(array $exceptionMap, $error, ?string $path): Exception {
|
||||||
if (isset($exceptionMap[$error])) {
|
if (isset($exceptionMap[$error])) {
|
||||||
$exceptionClass = $exceptionMap[$error];
|
$exceptionClass = $exceptionMap[$error];
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,11 @@ class InvalidRequestException extends Exception {
|
||||||
*/
|
*/
|
||||||
protected $path;
|
protected $path;
|
||||||
|
|
||||||
/**
|
public function __construct(string $path = "", int $code = 0, \Throwable $previous = null) {
|
||||||
* @param string $path
|
|
||||||
* @param int $code
|
|
||||||
*/
|
|
||||||
public function __construct($path, $code = 0) {
|
|
||||||
$class = get_class($this);
|
$class = get_class($this);
|
||||||
$parts = explode('\\', $class);
|
$parts = explode('\\', $class);
|
||||||
$baseName = array_pop($parts);
|
$baseName = array_pop($parts);
|
||||||
parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code);
|
parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code, $previous);
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Icewind\SMB\Exception;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class RevisionMismatchException extends Exception {
|
class RevisionMismatchException extends Exception {
|
||||||
public function __construct($message = 'Protocol version mismatch', $code = 0, Throwable $previous = null) {
|
public function __construct(string $message = 'Protocol version mismatch', int $code = 0, Throwable $previous = null) {
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,5 +40,5 @@ interface IAuth {
|
||||||
*
|
*
|
||||||
* @param resource $smbClientState
|
* @param resource $smbClientState
|
||||||
*/
|
*/
|
||||||
public function setExtraSmbClientOptions($smbClientState);
|
public function setExtraSmbClientOptions($smbClientState): void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ interface INotifyHandler {
|
||||||
*
|
*
|
||||||
* Note that this is a blocking process and will cause the process to block forever if not explicitly terminated
|
* Note that this is a blocking process and will cause the process to block forever if not explicitly terminated
|
||||||
*
|
*
|
||||||
* @param callable $callback
|
* @param callable(Change):?bool $callback
|
||||||
*/
|
*/
|
||||||
public function listen(callable $callback): void;
|
public function listen(callable $callback): void;
|
||||||
|
|
||||||
|
|
@ -41,5 +41,5 @@ interface INotifyHandler {
|
||||||
*
|
*
|
||||||
* Note that any pending changes will be discarded
|
* Note that any pending changes will be discarded
|
||||||
*/
|
*/
|
||||||
public function stop();
|
public function stop(): void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class KerberosAuth implements IAuth {
|
||||||
return '-k';
|
return '-k';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setExtraSmbClientOptions($smbClientState) {
|
public function setExtraSmbClientOptions($smbClientState): void {
|
||||||
smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true);
|
smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true);
|
||||||
smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false);
|
smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
namespace Icewind\SMB\Native;
|
namespace Icewind\SMB\Native;
|
||||||
|
|
||||||
use Icewind\SMB\ACL;
|
use Icewind\SMB\ACL;
|
||||||
|
use Icewind\SMB\Exception\Exception;
|
||||||
use Icewind\SMB\IFileInfo;
|
use Icewind\SMB\IFileInfo;
|
||||||
|
|
||||||
class NativeFileInfo implements IFileInfo {
|
class NativeFileInfo implements IFileInfo {
|
||||||
|
|
@ -17,7 +18,7 @@ class NativeFileInfo implements IFileInfo {
|
||||||
protected $name;
|
protected $name;
|
||||||
/** @var NativeShare */
|
/** @var NativeShare */
|
||||||
protected $share;
|
protected $share;
|
||||||
/** @var array|null */
|
/** @var array{"mode": int, "size": int, "write_time": int}|null */
|
||||||
protected $attributeCache = null;
|
protected $attributeCache = null;
|
||||||
|
|
||||||
public function __construct(NativeShare $share, string $path, string $name) {
|
public function __construct(NativeShare $share, string $path, string $name) {
|
||||||
|
|
@ -34,19 +35,32 @@ class NativeFileInfo implements IFileInfo {
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{"mode": int, "size": int, "write_time": int}
|
||||||
|
*/
|
||||||
protected function stat(): array {
|
protected function stat(): array {
|
||||||
if (is_null($this->attributeCache)) {
|
if (is_null($this->attributeCache)) {
|
||||||
$rawAttributes = explode(',', $this->share->getAttribute($this->path, 'system.dos_attr.*'));
|
$rawAttributes = explode(',', $this->share->getAttribute($this->path, 'system.dos_attr.*'));
|
||||||
$this->attributeCache = [];
|
$attributes = [];
|
||||||
foreach ($rawAttributes as $rawAttribute) {
|
foreach ($rawAttributes as $rawAttribute) {
|
||||||
list($name, $value) = explode(':', $rawAttribute);
|
list($name, $value) = explode(':', $rawAttribute);
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
if ($name == 'mode') {
|
if ($name == 'mode') {
|
||||||
$this->attributeCache[$name] = (int)hexdec(substr($value, 2));
|
$attributes[$name] = (int)hexdec(substr($value, 2));
|
||||||
} else {
|
} else {
|
||||||
$this->attributeCache[$name] = (int)$value;
|
$attributes[$name] = (int)$value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isset($attributes['mode'])) {
|
||||||
|
throw new Exception("Invalid attribute response");
|
||||||
|
}
|
||||||
|
if (!isset($attributes['size'])) {
|
||||||
|
throw new Exception("Invalid attribute response");
|
||||||
|
}
|
||||||
|
if (!isset($attributes['write_time'])) {
|
||||||
|
throw new Exception("Invalid attribute response");
|
||||||
|
}
|
||||||
|
$this->attributeCache = $attributes;
|
||||||
}
|
}
|
||||||
return $this->attributeCache;
|
return $this->attributeCache;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,14 @@ class NativeReadStream extends NativeStream {
|
||||||
/** @var StringBuffer */
|
/** @var StringBuffer */
|
||||||
private $readBuffer;
|
private $readBuffer;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->readBuffer = new StringBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
private $pos = 0;
|
private $pos = 0;
|
||||||
|
|
||||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||||
$this->readBuffer = new StringBuffer();
|
|
||||||
|
|
||||||
return parent::stream_open($path, $mode, $options, $opened_path);
|
return parent::stream_open($path, $mode, $options, $opened_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +57,11 @@ class NativeReadStream extends NativeStream {
|
||||||
// however due to network latency etc, it's faster to read in larger chunks
|
// however due to network latency etc, it's faster to read in larger chunks
|
||||||
// and buffer the result
|
// and buffer the result
|
||||||
if (!parent::stream_eof() && $this->readBuffer->remaining() < $count) {
|
if (!parent::stream_eof() && $this->readBuffer->remaining() < $count) {
|
||||||
$this->readBuffer->push(parent::stream_read(self::CHUNK_SIZE));
|
$chunk = parent::stream_read(self::CHUNK_SIZE);
|
||||||
|
if ($chunk === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->readBuffer->push($chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->readBuffer->read($count);
|
$result = $this->readBuffer->read($count);
|
||||||
|
|
@ -69,7 +76,11 @@ class NativeReadStream extends NativeStream {
|
||||||
$result = parent::stream_seek($offset, $whence);
|
$result = parent::stream_seek($offset, $whence);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
$this->readBuffer->clear();
|
$this->readBuffer->clear();
|
||||||
$this->pos = parent::stream_tell();
|
$pos = parent::stream_tell();
|
||||||
|
if ($pos === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->pos = $pos;
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
namespace Icewind\SMB\Native;
|
namespace Icewind\SMB\Native;
|
||||||
|
|
||||||
use Icewind\SMB\AbstractServer;
|
use Icewind\SMB\AbstractServer;
|
||||||
|
use Icewind\SMB\Exception\AuthenticationException;
|
||||||
|
use Icewind\SMB\Exception\InvalidHostException;
|
||||||
use Icewind\SMB\IAuth;
|
use Icewind\SMB\IAuth;
|
||||||
use Icewind\SMB\IOptions;
|
use Icewind\SMB\IOptions;
|
||||||
use Icewind\SMB\IShare;
|
use Icewind\SMB\IShare;
|
||||||
|
|
@ -25,14 +27,14 @@ class NativeServer extends AbstractServer {
|
||||||
$this->state = new NativeState();
|
$this->state = new NativeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function connect() {
|
protected function connect(): void {
|
||||||
$this->state->init($this->getAuth(), $this->getOptions());
|
$this->state->init($this->getAuth(), $this->getOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Icewind\SMB\IShare[]
|
* @return IShare[]
|
||||||
* @throws \Icewind\SMB\Exception\AuthenticationException
|
* @throws AuthenticationException
|
||||||
* @throws \Icewind\SMB\Exception\InvalidHostException
|
* @throws InvalidHostException
|
||||||
*/
|
*/
|
||||||
public function listShares(): array {
|
public function listShares(): array {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,8 @@ class NativeShare extends AbstractShare {
|
||||||
*/
|
*/
|
||||||
private $name;
|
private $name;
|
||||||
|
|
||||||
/**
|
/** @var NativeState|null $state */
|
||||||
* @var ?NativeState $state
|
private $state = null;
|
||||||
*/
|
|
||||||
private $state;
|
|
||||||
|
|
||||||
public function __construct(IServer $server, string $name) {
|
public function __construct(IServer $server, string $name) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
@ -51,7 +49,7 @@ class NativeShare extends AbstractShare {
|
||||||
* @throws InvalidHostException
|
* @throws InvalidHostException
|
||||||
*/
|
*/
|
||||||
protected function getState(): NativeState {
|
protected function getState(): NativeState {
|
||||||
if ($this->state and $this->state instanceof NativeState) {
|
if ($this->state) {
|
||||||
return $this->state;
|
return $this->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +182,7 @@ class NativeShare extends AbstractShare {
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
* @throws AlreadyExistsException
|
* @throws AlreadyExistsException
|
||||||
*/
|
*/
|
||||||
public function rename(string $from, string $to): bool{
|
public function rename(string $from, string $to): bool {
|
||||||
return $this->getState()->rename($this->buildUrl($from), $this->buildUrl($to));
|
return $this->getState()->rename($this->buildUrl($from), $this->buildUrl($to));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@ use Icewind\SMB\IOptions;
|
||||||
* Low level wrapper for libsmbclient-php with error handling
|
* Low level wrapper for libsmbclient-php with error handling
|
||||||
*/
|
*/
|
||||||
class NativeState {
|
class NativeState {
|
||||||
/**
|
/** @var resource|null */
|
||||||
* @var resource
|
protected $state = null;
|
||||||
*/
|
|
||||||
protected $state;
|
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
protected $handlerSet = false;
|
protected $handlerSet = false;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
protected $connected = false;
|
protected $connected = false;
|
||||||
|
|
||||||
// see error.h
|
// see error.h
|
||||||
|
|
@ -58,7 +58,8 @@ class NativeState {
|
||||||
113 => NoRouteToHostException::class
|
113 => NoRouteToHostException::class
|
||||||
];
|
];
|
||||||
|
|
||||||
protected function handleError(?string $path) {
|
protected function handleError(?string $path): void {
|
||||||
|
/** @var int $error */
|
||||||
$error = smbclient_state_errno($this->state);
|
$error = smbclient_state_errno($this->state);
|
||||||
if ($error === 0) {
|
if ($error === 0) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -66,7 +67,12 @@ class NativeState {
|
||||||
throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
|
throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function testResult($result, ?string $uri) {
|
/**
|
||||||
|
* @param mixed $result
|
||||||
|
* @param string|null $uri
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function testResult($result, ?string $uri): void {
|
||||||
if ($result === false or $result === null) {
|
if ($result === false or $result === null) {
|
||||||
// smb://host/share/path
|
// smb://host/share/path
|
||||||
if (is_string($uri) && count(explode('/', $uri, 5)) > 4) {
|
if (is_string($uri) && count(explode('/', $uri, 5)) > 4) {
|
||||||
|
|
@ -88,7 +94,9 @@ class NativeState {
|
||||||
if ($this->connected) {
|
if ($this->connected) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$this->state = smbclient_state_new();
|
/** @var resource $state */
|
||||||
|
$state = smbclient_state_new();
|
||||||
|
$this->state = $state;
|
||||||
smbclient_option_set($this->state, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, false);
|
smbclient_option_set($this->state, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, false);
|
||||||
smbclient_option_set($this->state, SMBCLIENT_OPT_TIMEOUT, $options->getTimeout() * 1000);
|
smbclient_option_set($this->state, SMBCLIENT_OPT_TIMEOUT, $options->getTimeout() * 1000);
|
||||||
|
|
||||||
|
|
@ -100,6 +108,7 @@ class NativeState {
|
||||||
}
|
}
|
||||||
|
|
||||||
$auth->setExtraSmbClientOptions($this->state);
|
$auth->setExtraSmbClientOptions($this->state);
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());
|
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());
|
||||||
|
|
||||||
$this->testResult($result, '');
|
$this->testResult($result, '');
|
||||||
|
|
@ -112,6 +121,7 @@ class NativeState {
|
||||||
* @return resource
|
* @return resource
|
||||||
*/
|
*/
|
||||||
public function opendir(string $uri) {
|
public function opendir(string $uri) {
|
||||||
|
/** @var resource $result */
|
||||||
$result = @smbclient_opendir($this->state, $uri);
|
$result = @smbclient_opendir($this->state, $uri);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
|
|
@ -121,9 +131,10 @@ class NativeState {
|
||||||
/**
|
/**
|
||||||
* @param resource $dir
|
* @param resource $dir
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @return array|false
|
* @return array{"type": string, "comment": string, "name": string}|false
|
||||||
*/
|
*/
|
||||||
public function readdir($dir, string $path) {
|
public function readdir($dir, string $path) {
|
||||||
|
/** @var array{"type": string, "comment": string, "name": string}|false $result */
|
||||||
$result = @smbclient_readdir($this->state, $dir);
|
$result = @smbclient_readdir($this->state, $dir);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
|
|
@ -136,6 +147,7 @@ class NativeState {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function closedir($dir, string $path): bool {
|
public function closedir($dir, string $path): bool {
|
||||||
|
/** @var bool $result */
|
||||||
$result = smbclient_closedir($this->state, $dir);
|
$result = smbclient_closedir($this->state, $dir);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
|
|
@ -148,6 +160,7 @@ class NativeState {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function rename(string $old, string $new): bool {
|
public function rename(string $old, string $new): bool {
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_rename($this->state, $old, $this->state, $new);
|
$result = @smbclient_rename($this->state, $old, $this->state, $new);
|
||||||
|
|
||||||
$this->testResult($result, $new);
|
$this->testResult($result, $new);
|
||||||
|
|
@ -159,6 +172,7 @@ class NativeState {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function unlink(string $uri): bool {
|
public function unlink(string $uri): bool {
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_unlink($this->state, $uri);
|
$result = @smbclient_unlink($this->state, $uri);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
|
|
@ -171,6 +185,7 @@ class NativeState {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function mkdir(string $uri, int $mask = 0777): bool {
|
public function mkdir(string $uri, int $mask = 0777): bool {
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_mkdir($this->state, $uri, $mask);
|
$result = @smbclient_mkdir($this->state, $uri, $mask);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
|
|
@ -182,6 +197,7 @@ class NativeState {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function rmdir(string $uri): bool {
|
public function rmdir(string $uri): bool {
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_rmdir($this->state, $uri);
|
$result = @smbclient_rmdir($this->state, $uri);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
|
|
@ -193,13 +209,20 @@ class NativeState {
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function stat(string $uri): array {
|
public function stat(string $uri): array {
|
||||||
|
/** @var array $result */
|
||||||
$result = @smbclient_stat($this->state, $uri);
|
$result = @smbclient_stat($this->state, $uri);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $file
|
||||||
|
* @param string $path
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public function fstat($file, string $path): array {
|
public function fstat($file, string $path): array {
|
||||||
|
/** @var array $result */
|
||||||
$result = @smbclient_fstat($this->state, $file);
|
$result = @smbclient_fstat($this->state, $file);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
|
|
@ -213,6 +236,7 @@ class NativeState {
|
||||||
* @return resource
|
* @return resource
|
||||||
*/
|
*/
|
||||||
public function open(string $uri, string $mode, int $mask = 0666) {
|
public function open(string $uri, string $mode, int $mask = 0666) {
|
||||||
|
/** @var resource $result */
|
||||||
$result = @smbclient_open($this->state, $uri, $mode, $mask);
|
$result = @smbclient_open($this->state, $uri, $mode, $mask);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
|
|
@ -225,13 +249,21 @@ class NativeState {
|
||||||
* @return resource
|
* @return resource
|
||||||
*/
|
*/
|
||||||
public function create(string $uri, int $mask = 0666) {
|
public function create(string $uri, int $mask = 0666) {
|
||||||
|
/** @var resource $result */
|
||||||
$result = @smbclient_creat($this->state, $uri, $mask);
|
$result = @smbclient_creat($this->state, $uri, $mask);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $file
|
||||||
|
* @param int $bytes
|
||||||
|
* @param string $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function read($file, int $bytes, string $path): string {
|
public function read($file, int $bytes, string $path): string {
|
||||||
|
/** @var string $result */
|
||||||
$result = @smbclient_read($this->state, $file, $bytes);
|
$result = @smbclient_read($this->state, $file, $bytes);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
|
|
@ -246,6 +278,7 @@ class NativeState {
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function write($file, string $data, string $path, ?int $length = null): int {
|
public function write($file, string $data, string $path, ?int $length = null): int {
|
||||||
|
/** @var int $result */
|
||||||
$result = @smbclient_write($this->state, $file, $data, $length);
|
$result = @smbclient_write($this->state, $file, $data, $length);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
|
|
@ -257,23 +290,37 @@ class NativeState {
|
||||||
* @param int $offset
|
* @param int $offset
|
||||||
* @param int $whence SEEK_SET | SEEK_CUR | SEEK_END
|
* @param int $whence SEEK_SET | SEEK_CUR | SEEK_END
|
||||||
* @param string|null $path
|
* @param string|null $path
|
||||||
* @return int new file offset as measured from the start of the file on success.
|
* @return int|false new file offset as measured from the start of the file on success.
|
||||||
*/
|
*/
|
||||||
public function lseek($file, int $offset, int $whence = SEEK_SET, string $path = null) {
|
public function lseek($file, int $offset, int $whence = SEEK_SET, string $path = null) {
|
||||||
|
/** @var int|false $result */
|
||||||
$result = @smbclient_lseek($this->state, $file, $offset, $whence);
|
$result = @smbclient_lseek($this->state, $file, $offset, $whence);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $file
|
||||||
|
* @param int $size
|
||||||
|
* @param string $path
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
public function ftruncate($file, int $size, string $path): bool {
|
public function ftruncate($file, int $size, string $path): bool {
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_ftruncate($this->state, $file, $size);
|
$result = @smbclient_ftruncate($this->state, $file, $size);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function close($file, string $path) {
|
/**
|
||||||
|
* @param resource $file
|
||||||
|
* @param string $path
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close($file, string $path): bool {
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_close($this->state, $file);
|
$result = @smbclient_close($this->state, $file);
|
||||||
|
|
||||||
$this->testResult($result, $path);
|
$this->testResult($result, $path);
|
||||||
|
|
@ -286,6 +333,7 @@ class NativeState {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getxattr(string $uri, string $key) {
|
public function getxattr(string $uri, string $key) {
|
||||||
|
/** @var string $result */
|
||||||
$result = @smbclient_getxattr($this->state, $uri, $key);
|
$result = @smbclient_getxattr($this->state, $uri, $key);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
|
|
@ -297,9 +345,10 @@ class NativeState {
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param string $value
|
* @param string $value
|
||||||
* @param int $flags
|
* @param int $flags
|
||||||
* @return mixed
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function setxattr(string $uri, string $key, string $value, int $flags = 0) {
|
public function setxattr(string $uri, string $key, string $value, int $flags = 0) {
|
||||||
|
/** @var bool $result */
|
||||||
$result = @smbclient_setxattr($this->state, $uri, $key, $value, $flags);
|
$result = @smbclient_setxattr($this->state, $uri, $key, $value, $flags);
|
||||||
|
|
||||||
$this->testResult($result, $uri);
|
$this->testResult($result, $uri);
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,24 @@ namespace Icewind\SMB\Native;
|
||||||
use Icewind\SMB\Exception\Exception;
|
use Icewind\SMB\Exception\Exception;
|
||||||
use Icewind\SMB\Exception\InvalidRequestException;
|
use Icewind\SMB\Exception\InvalidRequestException;
|
||||||
use Icewind\Streams\File;
|
use Icewind\Streams\File;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class NativeStream implements File {
|
class NativeStream implements File {
|
||||||
/**
|
/**
|
||||||
* @var resource
|
* @var resource
|
||||||
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
*/
|
*/
|
||||||
public $context;
|
public $context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var NativeState
|
* @var NativeState
|
||||||
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
*/
|
*/
|
||||||
protected $state;
|
protected $state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var resource
|
* @var resource
|
||||||
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
*/
|
*/
|
||||||
protected $handle;
|
protected $handle;
|
||||||
|
|
||||||
|
|
@ -35,7 +39,7 @@ class NativeStream implements File {
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $url;
|
protected $url = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap a stream from libsmbclient-php into a regular php stream
|
* Wrap a stream from libsmbclient-php into a regular php stream
|
||||||
|
|
@ -79,9 +83,24 @@ class NativeStream implements File {
|
||||||
|
|
||||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||||
$context = stream_context_get_options($this->context);
|
$context = stream_context_get_options($this->context);
|
||||||
$this->state = $context['nativesmb']['state'];
|
if (!isset($context['nativesmb']) || !is_array($context['nativesmb'])) {
|
||||||
$this->handle = $context['nativesmb']['handle'];
|
throw new InvalidArgumentException("context not set");
|
||||||
$this->url = $context['nativesmb']['url'];
|
}
|
||||||
|
$state = $context['nativesmb']['state'];
|
||||||
|
if (!$state instanceof NativeState) {
|
||||||
|
throw new InvalidArgumentException("invalid context set");
|
||||||
|
}
|
||||||
|
$this->state = $state;
|
||||||
|
$handle = $context['nativesmb']['handle'];
|
||||||
|
if (!is_resource($handle)) {
|
||||||
|
throw new InvalidArgumentException("invalid context set");
|
||||||
|
}
|
||||||
|
$this->handle = $handle;
|
||||||
|
$url = $context['nativesmb']['url'];
|
||||||
|
if (!is_string($url)) {
|
||||||
|
throw new InvalidArgumentException("invalid context set");
|
||||||
|
}
|
||||||
|
$this->url = $url;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,14 @@ class NativeWriteStream extends NativeStream {
|
||||||
/** @var StringBuffer */
|
/** @var StringBuffer */
|
||||||
private $writeBuffer;
|
private $writeBuffer;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
private $pos = 0;
|
private $pos = 0;
|
||||||
|
|
||||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
public function __construct() {
|
||||||
$this->writeBuffer = new StringBuffer();
|
$this->writeBuffer = new StringBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path): bool {
|
||||||
return parent::stream_open($path, $mode, $options, $opened_path);
|
return parent::stream_open($path, $mode, $options, $opened_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,12 +56,16 @@ class NativeWriteStream extends NativeStream {
|
||||||
$this->flushWrite();
|
$this->flushWrite();
|
||||||
$result = parent::stream_seek($offset, $whence);
|
$result = parent::stream_seek($offset, $whence);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
$this->pos = parent::stream_tell();
|
$pos = parent::stream_tell();
|
||||||
|
if ($pos === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->pos = $pos;
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function flushWrite() {
|
private function flushWrite(): void {
|
||||||
$this->state->write($this->handle, $this->writeBuffer->flush(), $this->url);
|
$this->state->write($this->handle, $this->writeBuffer->flush(), $this->url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class Options implements IOptions {
|
||||||
return $this->timeout;
|
return $this->timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTimeout(int $timeout) {
|
public function setTimeout(int $timeout): void {
|
||||||
$this->timeout = $timeout;
|
$this->timeout = $timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,12 @@ declare(strict_types=1);
|
||||||
namespace Icewind\SMB;
|
namespace Icewind\SMB;
|
||||||
|
|
||||||
class StringBuffer {
|
class StringBuffer {
|
||||||
|
/** @var string */
|
||||||
private $buffer = "";
|
private $buffer = "";
|
||||||
|
/** @var int */
|
||||||
private $pos = 0;
|
private $pos = 0;
|
||||||
|
|
||||||
public function clear() {
|
public function clear(): void {
|
||||||
$this->buffer = "";
|
$this->buffer = "";
|
||||||
$this->pos = 0;
|
$this->pos = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -58,4 +60,4 @@ class StringBuffer {
|
||||||
|
|
||||||
return $remaining;
|
return $remaining;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class System implements ISystem {
|
||||||
return function_exists('smbclient_state_new');
|
return function_exists('smbclient_state_new');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getBinaryPath($binary): ?string {
|
protected function getBinaryPath(string $binary): ?string {
|
||||||
if (!isset($this->paths[$binary])) {
|
if (!isset($this->paths[$binary])) {
|
||||||
$result = null;
|
$result = null;
|
||||||
$output = [];
|
$output = [];
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,12 @@ class Connection extends RawConnection {
|
||||||
/** @var Parser */
|
/** @var Parser */
|
||||||
private $parser;
|
private $parser;
|
||||||
|
|
||||||
public function __construct($command, Parser $parser, $env = []) {
|
/**
|
||||||
|
* @param string $command
|
||||||
|
* @param Parser $parser
|
||||||
|
* @param array<string, string> $env
|
||||||
|
*/
|
||||||
|
public function __construct(string $command, Parser $parser, array $env = []) {
|
||||||
parent::__construct($command, $env);
|
parent::__construct($command, $env);
|
||||||
$this->parser = $parser;
|
$this->parser = $parser;
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +44,7 @@ class Connection extends RawConnection {
|
||||||
/**
|
/**
|
||||||
* @throws ConnectException
|
* @throws ConnectException
|
||||||
*/
|
*/
|
||||||
public function clearTillPrompt() {
|
public function clearTillPrompt(): void {
|
||||||
$this->write('');
|
$this->write('');
|
||||||
do {
|
do {
|
||||||
$promptLine = $this->readLine();
|
$promptLine = $this->readLine();
|
||||||
|
|
@ -57,7 +62,7 @@ class Connection extends RawConnection {
|
||||||
/**
|
/**
|
||||||
* get all unprocessed output from smbclient until the next prompt
|
* get all unprocessed output from smbclient until the next prompt
|
||||||
*
|
*
|
||||||
* @param callable|null $callback (optional) callback to call for every line read
|
* @param (callable(string):bool)|null $callback (optional) callback to call for every line read
|
||||||
* @return string[]
|
* @return string[]
|
||||||
* @throws AuthenticationException
|
* @throws AuthenticationException
|
||||||
* @throws ConnectException
|
* @throws ConnectException
|
||||||
|
|
@ -107,6 +112,7 @@ class Connection extends RawConnection {
|
||||||
/**
|
/**
|
||||||
* @param string|bool $promptLine (optional) prompt line that might contain some info about the error
|
* @param string|bool $promptLine (optional) prompt line that might contain some info about the error
|
||||||
* @throws ConnectException
|
* @throws ConnectException
|
||||||
|
* @return no-return
|
||||||
*/
|
*/
|
||||||
private function unknownError($promptLine = '') {
|
private function unknownError($promptLine = '') {
|
||||||
if ($promptLine) { //maybe we have some error we missed on the previous line
|
if ($promptLine) { //maybe we have some error we missed on the previous line
|
||||||
|
|
@ -121,7 +127,7 @@ class Connection extends RawConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function close(bool $terminate = true) {
|
public function close(bool $terminate = true): void {
|
||||||
if (get_resource_type($this->getInputStream()) === 'stream') {
|
if (get_resource_type($this->getInputStream()) === 'stream') {
|
||||||
// ignore any errors while trying to send the close command, the process might already be dead
|
// ignore any errors while trying to send the close command, the process might already be dead
|
||||||
@$this->write('close' . PHP_EOL);
|
@$this->write('close' . PHP_EOL);
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,17 @@ class FileInfo implements IFileInfo {
|
||||||
protected $time;
|
protected $time;
|
||||||
/** @var int */
|
/** @var int */
|
||||||
protected $mode;
|
protected $mode;
|
||||||
/** @var callable */
|
/** @var callable(): ACL[] */
|
||||||
protected $aclCallback;
|
protected $aclCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param string $name
|
||||||
|
* @param int $size
|
||||||
|
* @param int $time
|
||||||
|
* @param int $mode
|
||||||
|
* @param callable(): ACL[] $aclCallback
|
||||||
|
*/
|
||||||
public function __construct(string $path, string $name, int $size, int $time, int $mode, callable $aclCallback) {
|
public function __construct(string $path, string $name, int $size, int $time, int $mode, callable $aclCallback) {
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,13 @@ use Icewind\SMB\Exception\RevisionMismatchException;
|
||||||
use Icewind\SMB\INotifyHandler;
|
use Icewind\SMB\INotifyHandler;
|
||||||
|
|
||||||
class NotifyHandler implements INotifyHandler {
|
class NotifyHandler implements INotifyHandler {
|
||||||
/**
|
/** @var Connection */
|
||||||
* @var Connection
|
|
||||||
*/
|
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
/**
|
/** @var string */
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $path;
|
private $path;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
private $listening = true;
|
private $listening = true;
|
||||||
|
|
||||||
// see error.h
|
// see error.h
|
||||||
|
|
@ -64,15 +61,18 @@ class NotifyHandler implements INotifyHandler {
|
||||||
*
|
*
|
||||||
* Note that this is a blocking process and will cause the process to block forever if not explicitly terminated
|
* Note that this is a blocking process and will cause the process to block forever if not explicitly terminated
|
||||||
*
|
*
|
||||||
* @param callable $callback
|
* @param callable(Change):?bool $callback
|
||||||
*/
|
*/
|
||||||
public function listen(callable $callback): void {
|
public function listen(callable $callback): void {
|
||||||
if ($this->listening) {
|
if ($this->listening) {
|
||||||
$this->connection->read(function ($line) use ($callback) {
|
$this->connection->read(function (string $line) use ($callback): bool {
|
||||||
$this->checkForError($line);
|
$this->checkForError($line);
|
||||||
$change = $this->parseChangeLine($line);
|
$change = $this->parseChangeLine($line);
|
||||||
if ($change) {
|
if ($change) {
|
||||||
return $callback($change);
|
$result = $callback($change);
|
||||||
|
return $result === false ? false : true;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -91,14 +91,14 @@ class NotifyHandler implements INotifyHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkForError(string $line) {
|
private function checkForError(string $line): void {
|
||||||
if (substr($line, 0, 16) === 'notify returned ') {
|
if (substr($line, 0, 16) === 'notify returned ') {
|
||||||
$error = substr($line, 16);
|
$error = substr($line, 16);
|
||||||
throw Exception::fromMap(array_merge(self::EXCEPTION_MAP, Parser::EXCEPTION_MAP), $error, 'Notify is not supported with the used smb version');
|
throw Exception::fromMap(array_merge(self::EXCEPTION_MAP, Parser::EXCEPTION_MAP), $error, 'Notify is not supported with the used smb version');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stop() {
|
public function stop(): void {
|
||||||
$this->listening = false;
|
$this->listening = false;
|
||||||
$this->connection->close();
|
$this->connection->close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,14 @@ class Parser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $output
|
||||||
|
* @param string $path
|
||||||
|
* @return no-return
|
||||||
|
* @throws Exception
|
||||||
|
* @throws InvalidResourceException
|
||||||
|
* @throws NotFoundException
|
||||||
|
*/
|
||||||
public function checkForError(array $output, string $path): void {
|
public function checkForError(array $output, string $path): void {
|
||||||
if (strpos($output[0], 'does not exist')) {
|
if (strpos($output[0], 'does not exist')) {
|
||||||
throw new NotFoundException($path);
|
throw new NotFoundException($path);
|
||||||
|
|
@ -125,6 +133,11 @@ class Parser {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $output
|
||||||
|
* @return array{"mtime": int, "mode": int, "size": int}
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
public function parseStat(array $output): array {
|
public function parseStat(array $output): array {
|
||||||
$data = [];
|
$data = [];
|
||||||
foreach ($output as $line) {
|
foreach ($output as $line) {
|
||||||
|
|
@ -139,17 +152,21 @@ class Parser {
|
||||||
$data[$name] = $value;
|
$data[$name] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$attributeStart = strpos($data['attributes'], '(');
|
||||||
|
if ($attributeStart === false) {
|
||||||
|
throw new Exception("Malformed state response from server");
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
'mtime' => strtotime($data['write_time']),
|
'mtime' => strtotime($data['write_time']),
|
||||||
'mode' => hexdec(substr($data['attributes'], strpos($data['attributes'], '(') + 1, -1)),
|
'mode' => hexdec(substr($data['attributes'], $attributeStart + 1, -1)),
|
||||||
'size' => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0
|
'size' => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $output
|
* @param string[] $output
|
||||||
* @param string $basePath
|
* @param string $basePath
|
||||||
* @param callable $aclCallback
|
* @param callable(string):ACL[] $aclCallback
|
||||||
* @return FileInfo[]
|
* @return FileInfo[]
|
||||||
*/
|
*/
|
||||||
public function parseDir(array $output, string $basePath, callable $aclCallback): array {
|
public function parseDir(array $output, string $basePath, callable $aclCallback): array {
|
||||||
|
|
@ -165,7 +182,7 @@ class Parser {
|
||||||
$mode = $this->parseMode($mode);
|
$mode = $this->parseMode($mode);
|
||||||
$time = strtotime($time . ' ' . $this->timeZone);
|
$time = strtotime($time . ' ' . $this->timeZone);
|
||||||
$path = $basePath . '/' . $name;
|
$path = $basePath . '/' . $name;
|
||||||
$content[] = new FileInfo($path, $name, (int)$size, $time, $mode, function () use ($aclCallback, $path) {
|
$content[] = new FileInfo($path, $name, (int)$size, $time, $mode, function () use ($aclCallback, $path): array {
|
||||||
return $aclCallback($path);
|
return $aclCallback($path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -175,8 +192,8 @@ class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $output
|
* @param string[] $output
|
||||||
* @return string[]
|
* @return array<string, string>
|
||||||
*/
|
*/
|
||||||
public function parseListShares(array $output): array {
|
public function parseListShares(array $output): array {
|
||||||
$shareNames = [];
|
$shareNames = [];
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class RawConnection {
|
||||||
* $pipes[4] holds the stream for writing files
|
* $pipes[4] holds the stream for writing files
|
||||||
* $pipes[5] holds the stream for reading files
|
* $pipes[5] holds the stream for reading files
|
||||||
*/
|
*/
|
||||||
private $pipes;
|
private $pipes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var resource|null $process
|
* @var resource|null $process
|
||||||
|
|
@ -42,6 +42,10 @@ class RawConnection {
|
||||||
*/
|
*/
|
||||||
private $authStream = null;
|
private $authStream = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $command
|
||||||
|
* @param array<string, string> $env
|
||||||
|
*/
|
||||||
public function __construct(string $command, array $env = []) {
|
public function __construct(string $command, array $env = []) {
|
||||||
$this->command = $command;
|
$this->command = $command;
|
||||||
$this->env = $env;
|
$this->env = $env;
|
||||||
|
|
@ -49,8 +53,9 @@ class RawConnection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws ConnectException
|
* @throws ConnectException
|
||||||
|
* @psalm-assert resource $this->process
|
||||||
*/
|
*/
|
||||||
public function connect() {
|
public function connect(): void {
|
||||||
if (is_null($this->getAuthStream())) {
|
if (is_null($this->getAuthStream())) {
|
||||||
throw new ConnectException('Authentication not set before connecting');
|
throw new ConnectException('Authentication not set before connecting');
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +86,7 @@ class RawConnection {
|
||||||
* check if the connection is still active
|
* check if the connection is still active
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
|
* @psalm-assert-if-true resource $this->process
|
||||||
*/
|
*/
|
||||||
public function isValid(): bool {
|
public function isValid(): bool {
|
||||||
if (is_resource($this->process)) {
|
if (is_resource($this->process)) {
|
||||||
|
|
@ -118,7 +124,8 @@ class RawConnection {
|
||||||
* @return string|false
|
* @return string|false
|
||||||
*/
|
*/
|
||||||
public function readError() {
|
public function readError() {
|
||||||
return trim(stream_get_line($this->getErrorStream(), 4086));
|
$line = stream_get_line($this->getErrorStream(), 4086);
|
||||||
|
return $line !== false ? trim($line) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -134,40 +141,67 @@ class RawConnection {
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
public function getInputStream() {
|
public function getInputStream() {
|
||||||
return $this->pipes[0];
|
return $this->pipes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
public function getOutputStream() {
|
public function getOutputStream() {
|
||||||
return $this->pipes[1];
|
return $this->pipes[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
public function getErrorStream() {
|
public function getErrorStream() {
|
||||||
return $this->pipes[2];
|
return $this->pipes[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|null
|
||||||
|
*/
|
||||||
public function getAuthStream() {
|
public function getAuthStream() {
|
||||||
return $this->authStream;
|
return $this->authStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
public function getFileInputStream() {
|
public function getFileInputStream() {
|
||||||
return $this->pipes[4];
|
return $this->pipes[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
public function getFileOutputStream() {
|
public function getFileOutputStream() {
|
||||||
return $this->pipes[5];
|
return $this->pipes[5];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function writeAuthentication(?string $user, ?string $password) {
|
/**
|
||||||
|
* @param string|null $user
|
||||||
|
* @param string|null $password
|
||||||
|
* @psalm-assert resource $this->authStream
|
||||||
|
*/
|
||||||
|
public function writeAuthentication(?string $user, ?string $password): void {
|
||||||
$auth = ($password === null)
|
$auth = ($password === null)
|
||||||
? "username=$user"
|
? "username=$user"
|
||||||
: "username=$user\npassword=$password\n";
|
: "username=$user\npassword=$password\n";
|
||||||
|
|
||||||
$this->authStream = fopen('php://temp', 'w+');
|
$this->authStream = fopen('php://temp', 'w+');
|
||||||
fwrite($this->getAuthStream(), $auth);
|
fwrite($this->authStream, $auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function close(bool $terminate = true) {
|
/**
|
||||||
|
* @param bool $terminate
|
||||||
|
* @psalm-assert null $this->process
|
||||||
|
*/
|
||||||
|
public function close(bool $terminate = true): void {
|
||||||
if (!is_resource($this->process)) {
|
if (!is_resource($this->process)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -175,9 +209,10 @@ class RawConnection {
|
||||||
proc_terminate($this->process);
|
proc_terminate($this->process);
|
||||||
}
|
}
|
||||||
proc_close($this->process);
|
proc_close($this->process);
|
||||||
|
$this->process = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reconnect() {
|
public function reconnect(): void {
|
||||||
$this->close();
|
$this->close();
|
||||||
$this->connect();
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use Icewind\SMB\Exception\AuthenticationException;
|
||||||
use Icewind\SMB\Exception\ConnectException;
|
use Icewind\SMB\Exception\ConnectException;
|
||||||
use Icewind\SMB\Exception\ConnectionException;
|
use Icewind\SMB\Exception\ConnectionException;
|
||||||
use Icewind\SMB\Exception\ConnectionRefusedException;
|
use Icewind\SMB\Exception\ConnectionRefusedException;
|
||||||
|
use Icewind\SMB\Exception\Exception;
|
||||||
use Icewind\SMB\Exception\InvalidHostException;
|
use Icewind\SMB\Exception\InvalidHostException;
|
||||||
use Icewind\SMB\IShare;
|
use Icewind\SMB\IShare;
|
||||||
use Icewind\SMB\ISystem;
|
use Icewind\SMB\ISystem;
|
||||||
|
|
@ -45,9 +46,13 @@ class Server extends AbstractServer {
|
||||||
public function listShares(): array {
|
public function listShares(): array {
|
||||||
$maxProtocol = $this->options->getMaxProtocol();
|
$maxProtocol = $this->options->getMaxProtocol();
|
||||||
$minProtocol = $this->options->getMinProtocol();
|
$minProtocol = $this->options->getMinProtocol();
|
||||||
|
$smbClient = $this->system->getSmbclientPath();
|
||||||
|
if ($smbClient === null) {
|
||||||
|
throw new Exception("Backend not available");
|
||||||
|
}
|
||||||
$command = sprintf(
|
$command = sprintf(
|
||||||
'%s %s %s %s %s -L %s',
|
'%s %s %s %s %s -L %s',
|
||||||
$this->system->getSmbclientPath(),
|
$smbClient,
|
||||||
$this->getAuthFileArgument(),
|
$this->getAuthFileArgument(),
|
||||||
$this->getAuth()->getExtraCommandLineArguments(),
|
$this->getAuth()->getExtraCommandLineArguments(),
|
||||||
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "",
|
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "",
|
||||||
|
|
@ -58,7 +63,7 @@ class Server extends AbstractServer {
|
||||||
$connection->writeAuthentication($this->getAuth()->getUsername(), $this->getAuth()->getPassword());
|
$connection->writeAuthentication($this->getAuth()->getUsername(), $this->getAuth()->getPassword());
|
||||||
$connection->connect();
|
$connection->connect();
|
||||||
if (!$connection->isValid()) {
|
if (!$connection->isValid()) {
|
||||||
throw new ConnectionException($connection->readLine());
|
throw new ConnectionException((string)$connection->readLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
$parser = new Parser($this->timezoneProvider->get($this->host));
|
$parser = new Parser($this->timezoneProvider->get($this->host));
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ use Icewind\SMB\AbstractShare;
|
||||||
use Icewind\SMB\ACL;
|
use Icewind\SMB\ACL;
|
||||||
use Icewind\SMB\Exception\AlreadyExistsException;
|
use Icewind\SMB\Exception\AlreadyExistsException;
|
||||||
use Icewind\SMB\Exception\AuthenticationException;
|
use Icewind\SMB\Exception\AuthenticationException;
|
||||||
|
use Icewind\SMB\Exception\ConnectException;
|
||||||
use Icewind\SMB\Exception\ConnectionException;
|
use Icewind\SMB\Exception\ConnectionException;
|
||||||
use Icewind\SMB\Exception\DependencyException;
|
use Icewind\SMB\Exception\DependencyException;
|
||||||
|
use Icewind\SMB\Exception\Exception;
|
||||||
use Icewind\SMB\Exception\FileInUseException;
|
use Icewind\SMB\Exception\FileInUseException;
|
||||||
use Icewind\SMB\Exception\InvalidHostException;
|
use Icewind\SMB\Exception\InvalidHostException;
|
||||||
use Icewind\SMB\Exception\InvalidTypeException;
|
use Icewind\SMB\Exception\InvalidTypeException;
|
||||||
|
|
@ -38,9 +40,9 @@ class Share extends AbstractShare {
|
||||||
private $name;
|
private $name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Connection $connection
|
* @var Connection|null $connection
|
||||||
*/
|
*/
|
||||||
public $connection;
|
public $connection = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Parser
|
* @var Parser
|
||||||
|
|
@ -85,11 +87,16 @@ class Share extends AbstractShare {
|
||||||
protected function getConnection(): Connection {
|
protected function getConnection(): Connection {
|
||||||
$maxProtocol = $this->server->getOptions()->getMaxProtocol();
|
$maxProtocol = $this->server->getOptions()->getMaxProtocol();
|
||||||
$minProtocol = $this->server->getOptions()->getMinProtocol();
|
$minProtocol = $this->server->getOptions()->getMinProtocol();
|
||||||
|
$smbClient = $this->system->getSmbclientPath();
|
||||||
|
$stdBuf = $this->system->getStdBufPath();
|
||||||
|
if ($smbClient === null) {
|
||||||
|
throw new Exception("Backend not available");
|
||||||
|
}
|
||||||
$command = sprintf(
|
$command = sprintf(
|
||||||
'%s %s%s -t %s %s %s %s %s %s',
|
'%s %s%s -t %s %s %s %s %s %s',
|
||||||
self::EXEC_CMD,
|
self::EXEC_CMD,
|
||||||
$this->system->getStdBufPath() ? $this->system->getStdBufPath() . ' -o0 ' : '',
|
$stdBuf ? $stdBuf . ' -o0 ' : '',
|
||||||
$this->system->getSmbclientPath(),
|
$smbClient,
|
||||||
$this->server->getOptions()->getTimeout(),
|
$this->server->getOptions()->getTimeout(),
|
||||||
$this->getAuthFileArgument(),
|
$this->getAuthFileArgument(),
|
||||||
$this->server->getAuth()->getExtraCommandLineArguments(),
|
$this->server->getAuth()->getExtraCommandLineArguments(),
|
||||||
|
|
@ -101,7 +108,7 @@ class Share extends AbstractShare {
|
||||||
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
|
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
|
||||||
$connection->connect();
|
$connection->connect();
|
||||||
if (!$connection->isValid()) {
|
if (!$connection->isValid()) {
|
||||||
throw new ConnectionException($connection->readLine());
|
throw new ConnectionException((string)$connection->readLine());
|
||||||
}
|
}
|
||||||
// some versions of smbclient add a help message in first of the first prompt
|
// some versions of smbclient add a help message in first of the first prompt
|
||||||
$connection->clearTillPrompt();
|
$connection->clearTillPrompt();
|
||||||
|
|
@ -112,18 +119,30 @@ class Share extends AbstractShare {
|
||||||
* @throws ConnectionException
|
* @throws ConnectionException
|
||||||
* @throws AuthenticationException
|
* @throws AuthenticationException
|
||||||
* @throws InvalidHostException
|
* @throws InvalidHostException
|
||||||
|
* @psalm-assert Connection $this->connection
|
||||||
*/
|
*/
|
||||||
protected function connect() {
|
protected function connect(): Connection {
|
||||||
if ($this->connection and $this->connection->isValid()) {
|
if ($this->connection and $this->connect()->isValid()) {
|
||||||
return;
|
return $this->connection;
|
||||||
}
|
}
|
||||||
$this->connection = $this->getConnection();
|
$this->connection = $this->getConnection();
|
||||||
|
return $this->connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function reconnect() {
|
/**
|
||||||
$this->connection->reconnect();
|
* @throws ConnectionException
|
||||||
if (!$this->connection->isValid()) {
|
* @throws AuthenticationException
|
||||||
throw new ConnectionException();
|
* @throws InvalidHostException
|
||||||
|
* @psalm-assert Connection $this->connection
|
||||||
|
*/
|
||||||
|
protected function reconnect(): void {
|
||||||
|
if ($this->connection === null) {
|
||||||
|
$this->connect();
|
||||||
|
} else {
|
||||||
|
$this->connection->reconnect();
|
||||||
|
if (!$this->connection->isValid()) {
|
||||||
|
throw new ConnectionException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +180,7 @@ class Share extends AbstractShare {
|
||||||
|
|
||||||
$this->execute('cd /');
|
$this->execute('cd /');
|
||||||
|
|
||||||
return $this->parser->parseDir($output, $path, function ($path) {
|
return $this->parser->parseDir($output, $path, function (string $path) {
|
||||||
return $this->getAcls($path);
|
return $this->getAcls($path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -424,12 +443,11 @@ class Share extends AbstractShare {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $command
|
* @param string $command
|
||||||
* @return array
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
protected function execute(string $command): array {
|
protected function execute(string $command): array {
|
||||||
$this->connect();
|
$this->connect()->write($command . PHP_EOL);
|
||||||
$this->connection->write($command . PHP_EOL);
|
return $this->connect()->read();
|
||||||
return $this->connection->read();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -451,7 +469,6 @@ class Share extends AbstractShare {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
$this->parser->checkForError($lines, $path);
|
$this->parser->checkForError($lines, $path);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -487,6 +504,12 @@ class Share extends AbstractShare {
|
||||||
return '"' . $path . '"';
|
return '"' . $path . '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return ACL[]
|
||||||
|
* @throws ConnectionException
|
||||||
|
* @throws ConnectException
|
||||||
|
*/
|
||||||
protected function getAcls(string $path): array {
|
protected function getAcls(string $path): array {
|
||||||
$commandPath = $this->system->getSmbcAclsPath();
|
$commandPath = $this->system->getSmbcAclsPath();
|
||||||
if (!$commandPath) {
|
if (!$commandPath) {
|
||||||
|
|
@ -506,7 +529,7 @@ class Share extends AbstractShare {
|
||||||
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
|
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
|
||||||
$connection->connect();
|
$connection->connect();
|
||||||
if (!$connection->isValid()) {
|
if (!$connection->isValid()) {
|
||||||
throw new ConnectionException($connection->readLine());
|
throw new ConnectionException((string)$connection->readLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
$rawAcls = $connection->readAll();
|
$rawAcls = $connection->readAll();
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,4 @@ class StringBufferTest extends TestCase {
|
||||||
$buffer->push("foobar");
|
$buffer->push("foobar");
|
||||||
$this->assertEquals("foobar", $buffer->flush());
|
$this->assertEquals("foobar", $buffer->flush());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue