all the types

This commit is contained in:
Robin Appelman 2021-03-09 20:14:31 +01:00
commit 84fa890ea7
28 changed files with 330 additions and 110 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"

View file

@ -10,13 +10,18 @@ namespace Icewind\SMB;
use Icewind\SMB\Exception\InvalidPathException;
abstract class AbstractShare implements IShare {
/** @var string[] */
private $forbiddenCharacters;
public function __construct() {
$this->forbiddenCharacters = ['?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r"];
}
protected function verifyPath($path) {
/**
* @param string $path
* @throws InvalidPathException
*/
protected function verifyPath(string $path): void {
foreach ($this->forbiddenCharacters as $char) {
if (strpos($path, $char) !== false) {
throw new InvalidPathException('Invalid path, "' . $char . '" is not allowed');
@ -24,7 +29,10 @@ abstract class AbstractShare implements IShare {
}
}
public function setForbiddenChars(array $charList) {
/**
* @param string[] $charList
*/
public function setForbiddenChars(array $charList): void {
$this->forbiddenCharacters = $charList;
}
}

View file

@ -38,7 +38,7 @@ class AnonymousAuth implements IAuth {
return '-N';
}
public function setExtraSmbClientOptions($smbClientState) {
public function setExtraSmbClientOptions($smbClientState): void {
smbclient_option_set($smbClientState, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, true);
}
}

View file

@ -51,7 +51,7 @@ class BasicAuth implements IAuth {
return ($this->workgroup) ? '-W ' . escapeshellarg($this->workgroup) : '';
}
public function setExtraSmbClientOptions($smbClientState) {
public function setExtraSmbClientOptions($smbClientState): void {
// noop
}
}

View file

@ -7,16 +7,36 @@
namespace Icewind\SMB\Exception;
use Throwable;
/**
* @psalm-consistent-constructor
*/
class Exception extends \Exception {
public static function unknown(?string $path, $error) {
$message = 'Unknown error (' . $error . ')';
public function __construct(string $message = "", int $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
/**
* @param string|null $path
* @param string|int|null $error
* @return Exception
*/
public static function unknown(?string $path, $error): Exception {
$message = 'Unknown error (' . (string)$error . ')';
if ($path) {
$message .= ' for ' . $path;
}
return new Exception($message, is_string($error) ? 0 : $error);
return new Exception($message, is_int($error) ? $error : 0);
}
/**
* @param array<int|string, class-string<Exception>> $exceptionMap
* @param string|int|null $error
* @param string|null $path
* @return Exception
*/
public static function fromMap(array $exceptionMap, $error, ?string $path): Exception {
if (isset($exceptionMap[$error])) {
$exceptionClass = $exceptionMap[$error];

View file

@ -13,15 +13,11 @@ class InvalidRequestException extends Exception {
*/
protected $path;
/**
* @param string $path
* @param int $code
*/
public function __construct($path, $code = 0) {
public function __construct(string $path = "", int $code = 0, \Throwable $previous = null) {
$class = get_class($this);
$parts = explode('\\', $class);
$baseName = array_pop($parts);
parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code);
parent::__construct('Invalid request for ' . $path . ' (' . $baseName . ')', $code, $previous);
$this->path = $path;
}

View file

@ -10,7 +10,7 @@ namespace Icewind\SMB\Exception;
use Throwable;
class RevisionMismatchException extends Exception {
public function __construct($message = 'Protocol version mismatch', $code = 0, Throwable $previous = null) {
public function __construct(string $message = 'Protocol version mismatch', int $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}

View file

@ -40,5 +40,5 @@ interface IAuth {
*
* @param resource $smbClientState
*/
public function setExtraSmbClientOptions($smbClientState);
public function setExtraSmbClientOptions($smbClientState): void;
}

View file

@ -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
*
* @param callable $callback
* @param callable(Change):?bool $callback
*/
public function listen(callable $callback): void;
@ -41,5 +41,5 @@ interface INotifyHandler {
*
* Note that any pending changes will be discarded
*/
public function stop();
public function stop(): void;
}

View file

@ -41,7 +41,7 @@ class KerberosAuth implements IAuth {
return '-k';
}
public function setExtraSmbClientOptions($smbClientState) {
public function setExtraSmbClientOptions($smbClientState): void {
smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true);
smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false);
}

View file

@ -8,6 +8,7 @@
namespace Icewind\SMB\Native;
use Icewind\SMB\ACL;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\IFileInfo;
class NativeFileInfo implements IFileInfo {
@ -17,7 +18,7 @@ class NativeFileInfo implements IFileInfo {
protected $name;
/** @var NativeShare */
protected $share;
/** @var array|null */
/** @var array{"mode": int, "size": int, "write_time": int}|null */
protected $attributeCache = null;
public function __construct(NativeShare $share, string $path, string $name) {
@ -34,19 +35,32 @@ class NativeFileInfo implements IFileInfo {
return $this->name;
}
/**
* @return array{"mode": int, "size": int, "write_time": int}
*/
protected function stat(): array {
if (is_null($this->attributeCache)) {
$rawAttributes = explode(',', $this->share->getAttribute($this->path, 'system.dos_attr.*'));
$this->attributeCache = [];
$attributes = [];
foreach ($rawAttributes as $rawAttribute) {
list($name, $value) = explode(':', $rawAttribute);
$name = strtolower($name);
if ($name == 'mode') {
$this->attributeCache[$name] = (int)hexdec(substr($value, 2));
$attributes[$name] = (int)hexdec(substr($value, 2));
} else {
$this->attributeCache[$name] = (int)$value;
$attributes[$name] = (int)$value;
}
}
if (!isset($attributes['mode'])) {
throw new Exception("Invalid attribute response");
}
if (!isset($attributes['size'])) {
throw new Exception("Invalid attribute response");
}
if (!isset($attributes['write_time'])) {
throw new Exception("Invalid attribute response");
}
$this->attributeCache = $attributes;
}
return $this->attributeCache;
}

View file

@ -18,11 +18,14 @@ class NativeReadStream extends NativeStream {
/** @var StringBuffer */
private $readBuffer;
public function __construct() {
$this->readBuffer = new StringBuffer();
}
/** @var int */
private $pos = 0;
public function stream_open($path, $mode, $options, &$opened_path) {
$this->readBuffer = new StringBuffer();
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
// and buffer the result
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);
@ -69,7 +76,11 @@ class NativeReadStream extends NativeStream {
$result = parent::stream_seek($offset, $whence);
if ($result) {
$this->readBuffer->clear();
$this->pos = parent::stream_tell();
$pos = parent::stream_tell();
if ($pos === false) {
return false;
}
$this->pos = $pos;
}
return $result;
}

View file

@ -8,6 +8,8 @@
namespace Icewind\SMB\Native;
use Icewind\SMB\AbstractServer;
use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\IAuth;
use Icewind\SMB\IOptions;
use Icewind\SMB\IShare;
@ -25,14 +27,14 @@ class NativeServer extends AbstractServer {
$this->state = new NativeState();
}
protected function connect() {
protected function connect(): void {
$this->state->init($this->getAuth(), $this->getOptions());
}
/**
* @return \Icewind\SMB\IShare[]
* @throws \Icewind\SMB\Exception\AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidHostException
* @return IShare[]
* @throws AuthenticationException
* @throws InvalidHostException
*/
public function listShares(): array {
$this->connect();

View file

@ -34,10 +34,8 @@ class NativeShare extends AbstractShare {
*/
private $name;
/**
* @var ?NativeState $state
*/
private $state;
/** @var NativeState|null $state */
private $state = null;
public function __construct(IServer $server, string $name) {
parent::__construct();
@ -51,7 +49,7 @@ class NativeShare extends AbstractShare {
* @throws InvalidHostException
*/
protected function getState(): NativeState {
if ($this->state and $this->state instanceof NativeState) {
if ($this->state) {
return $this->state;
}
@ -184,7 +182,7 @@ class NativeShare extends AbstractShare {
* @throws NotFoundException
* @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));
}

View file

@ -29,13 +29,13 @@ use Icewind\SMB\IOptions;
* Low level wrapper for libsmbclient-php with error handling
*/
class NativeState {
/**
* @var resource
*/
protected $state;
/** @var resource|null */
protected $state = null;
/** @var bool */
protected $handlerSet = false;
/** @var bool */
protected $connected = false;
// see error.h
@ -58,7 +58,8 @@ class NativeState {
113 => NoRouteToHostException::class
];
protected function handleError(?string $path) {
protected function handleError(?string $path): void {
/** @var int $error */
$error = smbclient_state_errno($this->state);
if ($error === 0) {
return;
@ -66,7 +67,12 @@ class NativeState {
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) {
// smb://host/share/path
if (is_string($uri) && count(explode('/', $uri, 5)) > 4) {
@ -88,7 +94,9 @@ class NativeState {
if ($this->connected) {
return true;
}
$this->state = smbclient_state_new();
/** @var resource $state */
$state = smbclient_state_new();
$this->state = $state;
smbclient_option_set($this->state, SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN, false);
smbclient_option_set($this->state, SMBCLIENT_OPT_TIMEOUT, $options->getTimeout() * 1000);
@ -100,6 +108,7 @@ class NativeState {
}
$auth->setExtraSmbClientOptions($this->state);
/** @var bool $result */
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());
$this->testResult($result, '');
@ -112,6 +121,7 @@ class NativeState {
* @return resource
*/
public function opendir(string $uri) {
/** @var resource $result */
$result = @smbclient_opendir($this->state, $uri);
$this->testResult($result, $uri);
@ -121,9 +131,10 @@ class NativeState {
/**
* @param resource $dir
* @param string $path
* @return array|false
* @return array{"type": string, "comment": string, "name": string}|false
*/
public function readdir($dir, string $path) {
/** @var array{"type": string, "comment": string, "name": string}|false $result */
$result = @smbclient_readdir($this->state, $dir);
$this->testResult($result, $path);
@ -136,6 +147,7 @@ class NativeState {
* @return bool
*/
public function closedir($dir, string $path): bool {
/** @var bool $result */
$result = smbclient_closedir($this->state, $dir);
$this->testResult($result, $path);
@ -148,6 +160,7 @@ class NativeState {
* @return bool
*/
public function rename(string $old, string $new): bool {
/** @var bool $result */
$result = @smbclient_rename($this->state, $old, $this->state, $new);
$this->testResult($result, $new);
@ -159,6 +172,7 @@ class NativeState {
* @return bool
*/
public function unlink(string $uri): bool {
/** @var bool $result */
$result = @smbclient_unlink($this->state, $uri);
$this->testResult($result, $uri);
@ -171,6 +185,7 @@ class NativeState {
* @return bool
*/
public function mkdir(string $uri, int $mask = 0777): bool {
/** @var bool $result */
$result = @smbclient_mkdir($this->state, $uri, $mask);
$this->testResult($result, $uri);
@ -182,6 +197,7 @@ class NativeState {
* @return bool
*/
public function rmdir(string $uri): bool {
/** @var bool $result */
$result = @smbclient_rmdir($this->state, $uri);
$this->testResult($result, $uri);
@ -193,13 +209,20 @@ class NativeState {
* @return array
*/
public function stat(string $uri): array {
/** @var array $result */
$result = @smbclient_stat($this->state, $uri);
$this->testResult($result, $uri);
return $result;
}
/**
* @param resource $file
* @param string $path
* @return array
*/
public function fstat($file, string $path): array {
/** @var array $result */
$result = @smbclient_fstat($this->state, $file);
$this->testResult($result, $path);
@ -213,6 +236,7 @@ class NativeState {
* @return resource
*/
public function open(string $uri, string $mode, int $mask = 0666) {
/** @var resource $result */
$result = @smbclient_open($this->state, $uri, $mode, $mask);
$this->testResult($result, $uri);
@ -225,13 +249,21 @@ class NativeState {
* @return resource
*/
public function create(string $uri, int $mask = 0666) {
/** @var resource $result */
$result = @smbclient_creat($this->state, $uri, $mask);
$this->testResult($result, $uri);
return $result;
}
/**
* @param resource $file
* @param int $bytes
* @param string $path
* @return string
*/
public function read($file, int $bytes, string $path): string {
/** @var string $result */
$result = @smbclient_read($this->state, $file, $bytes);
$this->testResult($result, $path);
@ -246,6 +278,7 @@ class NativeState {
* @return int
*/
public function write($file, string $data, string $path, ?int $length = null): int {
/** @var int $result */
$result = @smbclient_write($this->state, $file, $data, $length);
$this->testResult($result, $path);
@ -257,23 +290,37 @@ class NativeState {
* @param int $offset
* @param int $whence SEEK_SET | SEEK_CUR | SEEK_END
* @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) {
/** @var int|false $result */
$result = @smbclient_lseek($this->state, $file, $offset, $whence);
$this->testResult($result, $path);
return $result;
}
/**
* @param resource $file
* @param int $size
* @param string $path
* @return bool
*/
public function ftruncate($file, int $size, string $path): bool {
/** @var bool $result */
$result = @smbclient_ftruncate($this->state, $file, $size);
$this->testResult($result, $path);
return $result;
}
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);
$this->testResult($result, $path);
@ -286,6 +333,7 @@ class NativeState {
* @return string
*/
public function getxattr(string $uri, string $key) {
/** @var string $result */
$result = @smbclient_getxattr($this->state, $uri, $key);
$this->testResult($result, $uri);
@ -297,9 +345,10 @@ class NativeState {
* @param string $key
* @param string $value
* @param int $flags
* @return mixed
* @return bool
*/
public function setxattr(string $uri, string $key, string $value, int $flags = 0) {
/** @var bool $result */
$result = @smbclient_setxattr($this->state, $uri, $key, $value, $flags);
$this->testResult($result, $uri);

View file

@ -10,20 +10,24 @@ namespace Icewind\SMB\Native;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\InvalidRequestException;
use Icewind\Streams\File;
use InvalidArgumentException;
class NativeStream implements File {
/**
* @var resource
* @psalm-suppress PropertyNotSetInConstructor
*/
public $context;
/**
* @var NativeState
* @psalm-suppress PropertyNotSetInConstructor
*/
protected $state;
/**
* @var resource
* @psalm-suppress PropertyNotSetInConstructor
*/
protected $handle;
@ -35,7 +39,7 @@ class NativeStream implements File {
/**
* @var string
*/
protected $url;
protected $url = '';
/**
* 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) {
$context = stream_context_get_options($this->context);
$this->state = $context['nativesmb']['state'];
$this->handle = $context['nativesmb']['handle'];
$this->url = $context['nativesmb']['url'];
if (!isset($context['nativesmb']) || !is_array($context['nativesmb'])) {
throw new InvalidArgumentException("context not set");
}
$state = $context['nativesmb']['state'];
if (!$state instanceof NativeState) {
throw new InvalidArgumentException("invalid context set");
}
$this->state = $state;
$handle = $context['nativesmb']['handle'];
if (!is_resource($handle)) {
throw new InvalidArgumentException("invalid context set");
}
$this->handle = $handle;
$url = $context['nativesmb']['url'];
if (!is_string($url)) {
throw new InvalidArgumentException("invalid context set");
}
$this->url = $url;
return true;
}

View file

@ -18,11 +18,14 @@ class NativeWriteStream extends NativeStream {
/** @var StringBuffer */
private $writeBuffer;
/** @var int */
private $pos = 0;
public function stream_open($path, $mode, $options, &$opened_path) {
public function __construct() {
$this->writeBuffer = new StringBuffer();
}
public function stream_open($path, $mode, $options, &$opened_path): bool {
return parent::stream_open($path, $mode, $options, $opened_path);
}
@ -53,12 +56,16 @@ class NativeWriteStream extends NativeStream {
$this->flushWrite();
$result = parent::stream_seek($offset, $whence);
if ($result) {
$this->pos = parent::stream_tell();
$pos = parent::stream_tell();
if ($pos === false) {
return false;
}
$this->pos = $pos;
}
return $result;
}
private function flushWrite() {
private function flushWrite(): void {
$this->state->write($this->handle, $this->writeBuffer->flush(), $this->url);
}

View file

@ -34,7 +34,7 @@ class Options implements IOptions {
return $this->timeout;
}
public function setTimeout(int $timeout) {
public function setTimeout(int $timeout): void {
$this->timeout = $timeout;
}

View file

@ -24,10 +24,12 @@ declare(strict_types=1);
namespace Icewind\SMB;
class StringBuffer {
/** @var string */
private $buffer = "";
/** @var int */
private $pos = 0;
public function clear() {
public function clear(): void {
$this->buffer = "";
$this->pos = 0;
}

View file

@ -57,7 +57,7 @@ class System implements ISystem {
return function_exists('smbclient_state_new');
}
protected function getBinaryPath($binary): ?string {
protected function getBinaryPath(string $binary): ?string {
if (!isset($this->paths[$binary])) {
$result = null;
$output = [];

View file

@ -22,7 +22,12 @@ class Connection extends RawConnection {
/** @var Parser */
private $parser;
public function __construct($command, Parser $parser, $env = []) {
/**
* @param string $command
* @param Parser $parser
* @param array<string, string> $env
*/
public function __construct(string $command, Parser $parser, array $env = []) {
parent::__construct($command, $env);
$this->parser = $parser;
}
@ -39,7 +44,7 @@ class Connection extends RawConnection {
/**
* @throws ConnectException
*/
public function clearTillPrompt() {
public function clearTillPrompt(): void {
$this->write('');
do {
$promptLine = $this->readLine();
@ -57,7 +62,7 @@ class Connection extends RawConnection {
/**
* 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[]
* @throws AuthenticationException
* @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
* @throws ConnectException
* @return no-return
*/
private function unknownError($promptLine = '') {
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') {
// ignore any errors while trying to send the close command, the process might already be dead
@$this->write('close' . PHP_EOL);

View file

@ -21,9 +21,17 @@ class FileInfo implements IFileInfo {
protected $time;
/** @var int */
protected $mode;
/** @var callable */
/** @var callable(): ACL[] */
protected $aclCallback;
/**
* @param string $path
* @param string $name
* @param int $size
* @param int $time
* @param int $mode
* @param callable(): ACL[] $aclCallback
*/
public function __construct(string $path, string $name, int $size, int $time, int $mode, callable $aclCallback) {
$this->path = $path;
$this->name = $name;

View file

@ -14,16 +14,13 @@ use Icewind\SMB\Exception\RevisionMismatchException;
use Icewind\SMB\INotifyHandler;
class NotifyHandler implements INotifyHandler {
/**
* @var Connection
*/
/** @var Connection */
private $connection;
/**
* @var string
*/
/** @var string */
private $path;
/** @var bool */
private $listening = true;
// see error.h
@ -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
*
* @param callable $callback
* @param callable(Change):?bool $callback
*/
public function listen(callable $callback): void {
if ($this->listening) {
$this->connection->read(function ($line) use ($callback) {
$this->connection->read(function (string $line) use ($callback): bool {
$this->checkForError($line);
$change = $this->parseChangeLine($line);
if ($change) {
return $callback($change);
$result = $callback($change);
return $result === false ? false : true;
} else {
return true;
}
});
}
@ -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 ') {
$error = substr($line, 16);
throw Exception::fromMap(array_merge(self::EXCEPTION_MAP, Parser::EXCEPTION_MAP), $error, 'Notify is not supported with the used smb version');
}
}
public function stop() {
public function stop(): void {
$this->listening = false;
$this->connection->close();
}

View file

@ -70,6 +70,14 @@ class Parser {
return null;
}
/**
* @param string[] $output
* @param string $path
* @return no-return
* @throws Exception
* @throws InvalidResourceException
* @throws NotFoundException
*/
public function checkForError(array $output, string $path): void {
if (strpos($output[0], 'does not exist')) {
throw new NotFoundException($path);
@ -125,6 +133,11 @@ class Parser {
return $result;
}
/**
* @param string[] $output
* @return array{"mtime": int, "mode": int, "size": int}
* @throws Exception
*/
public function parseStat(array $output): array {
$data = [];
foreach ($output as $line) {
@ -139,17 +152,21 @@ class Parser {
$data[$name] = $value;
}
}
$attributeStart = strpos($data['attributes'], '(');
if ($attributeStart === false) {
throw new Exception("Malformed state response from server");
}
return [
'mtime' => strtotime($data['write_time']),
'mode' => hexdec(substr($data['attributes'], strpos($data['attributes'], '(') + 1, -1)),
'mode' => hexdec(substr($data['attributes'], $attributeStart + 1, -1)),
'size' => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0
];
}
/**
* @param array $output
* @param string[] $output
* @param string $basePath
* @param callable $aclCallback
* @param callable(string):ACL[] $aclCallback
* @return FileInfo[]
*/
public function parseDir(array $output, string $basePath, callable $aclCallback): array {
@ -165,7 +182,7 @@ class Parser {
$mode = $this->parseMode($mode);
$time = strtotime($time . ' ' . $this->timeZone);
$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);
});
}
@ -175,8 +192,8 @@ class Parser {
}
/**
* @param array $output
* @return string[]
* @param string[] $output
* @return array<string, string>
*/
public function parseListShares(array $output): array {
$shareNames = [];

View file

@ -30,7 +30,7 @@ class RawConnection {
* $pipes[4] holds the stream for writing files
* $pipes[5] holds the stream for reading files
*/
private $pipes;
private $pipes = [];
/**
* @var resource|null $process
@ -42,6 +42,10 @@ class RawConnection {
*/
private $authStream = null;
/**
* @param string $command
* @param array<string, string> $env
*/
public function __construct(string $command, array $env = []) {
$this->command = $command;
$this->env = $env;
@ -49,8 +53,9 @@ class RawConnection {
/**
* @throws ConnectException
* @psalm-assert resource $this->process
*/
public function connect() {
public function connect(): void {
if (is_null($this->getAuthStream())) {
throw new ConnectException('Authentication not set before connecting');
}
@ -81,6 +86,7 @@ class RawConnection {
* check if the connection is still active
*
* @return bool
* @psalm-assert-if-true resource $this->process
*/
public function isValid(): bool {
if (is_resource($this->process)) {
@ -118,7 +124,8 @@ class RawConnection {
* @return string|false
*/
public function readError() {
return trim(stream_get_line($this->getErrorStream(), 4086));
$line = stream_get_line($this->getErrorStream(), 4086);
return $line !== false ? trim($line) : false;
}
/**
@ -134,40 +141,67 @@ class RawConnection {
return $output;
}
/**
* @return resource
*/
public function getInputStream() {
return $this->pipes[0];
}
/**
* @return resource
*/
public function getOutputStream() {
return $this->pipes[1];
}
/**
* @return resource
*/
public function getErrorStream() {
return $this->pipes[2];
}
/**
* @return resource|null
*/
public function getAuthStream() {
return $this->authStream;
}
/**
* @return resource
*/
public function getFileInputStream() {
return $this->pipes[4];
}
/**
* @return resource
*/
public function getFileOutputStream() {
return $this->pipes[5];
}
public function writeAuthentication(?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)
? "username=$user"
: "username=$user\npassword=$password\n";
$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)) {
return;
}
@ -175,9 +209,10 @@ class RawConnection {
proc_terminate($this->process);
}
proc_close($this->process);
$this->process = null;
}
public function reconnect() {
public function reconnect(): void {
$this->close();
$this->connect();
}

View file

@ -12,6 +12,7 @@ use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\ConnectionRefusedException;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\IShare;
use Icewind\SMB\ISystem;
@ -45,9 +46,13 @@ class Server extends AbstractServer {
public function listShares(): array {
$maxProtocol = $this->options->getMaxProtocol();
$minProtocol = $this->options->getMinProtocol();
$smbClient = $this->system->getSmbclientPath();
if ($smbClient === null) {
throw new Exception("Backend not available");
}
$command = sprintf(
'%s %s %s %s %s -L %s',
$this->system->getSmbclientPath(),
$smbClient,
$this->getAuthFileArgument(),
$this->getAuth()->getExtraCommandLineArguments(),
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "",
@ -58,7 +63,7 @@ class Server extends AbstractServer {
$connection->writeAuthentication($this->getAuth()->getUsername(), $this->getAuth()->getPassword());
$connection->connect();
if (!$connection->isValid()) {
throw new ConnectionException($connection->readLine());
throw new ConnectionException((string)$connection->readLine());
}
$parser = new Parser($this->timezoneProvider->get($this->host));

View file

@ -11,8 +11,10 @@ use Icewind\SMB\AbstractShare;
use Icewind\SMB\ACL;
use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\FileInUseException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\Exception\InvalidTypeException;
@ -38,9 +40,9 @@ class Share extends AbstractShare {
private $name;
/**
* @var Connection $connection
* @var Connection|null $connection
*/
public $connection;
public $connection = null;
/**
* @var Parser
@ -85,11 +87,16 @@ class Share extends AbstractShare {
protected function getConnection(): Connection {
$maxProtocol = $this->server->getOptions()->getMaxProtocol();
$minProtocol = $this->server->getOptions()->getMinProtocol();
$smbClient = $this->system->getSmbclientPath();
$stdBuf = $this->system->getStdBufPath();
if ($smbClient === null) {
throw new Exception("Backend not available");
}
$command = sprintf(
'%s %s%s -t %s %s %s %s %s %s',
self::EXEC_CMD,
$this->system->getStdBufPath() ? $this->system->getStdBufPath() . ' -o0 ' : '',
$this->system->getSmbclientPath(),
$stdBuf ? $stdBuf . ' -o0 ' : '',
$smbClient,
$this->server->getOptions()->getTimeout(),
$this->getAuthFileArgument(),
$this->server->getAuth()->getExtraCommandLineArguments(),
@ -101,7 +108,7 @@ class Share extends AbstractShare {
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
$connection->connect();
if (!$connection->isValid()) {
throw new ConnectionException($connection->readLine());
throw new ConnectionException((string)$connection->readLine());
}
// some versions of smbclient add a help message in first of the first prompt
$connection->clearTillPrompt();
@ -112,18 +119,30 @@ class Share extends AbstractShare {
* @throws ConnectionException
* @throws AuthenticationException
* @throws InvalidHostException
* @psalm-assert Connection $this->connection
*/
protected function connect() {
if ($this->connection and $this->connection->isValid()) {
return;
protected function connect(): Connection {
if ($this->connection and $this->connect()->isValid()) {
return $this->connection;
}
$this->connection = $this->getConnection();
return $this->connection;
}
protected function reconnect() {
$this->connection->reconnect();
if (!$this->connection->isValid()) {
throw new ConnectionException();
/**
* @throws ConnectionException
* @throws AuthenticationException
* @throws InvalidHostException
* @psalm-assert Connection $this->connection
*/
protected function reconnect(): void {
if ($this->connection === null) {
$this->connect();
} else {
$this->connection->reconnect();
if (!$this->connection->isValid()) {
throw new ConnectionException();
}
}
}
@ -161,7 +180,7 @@ class Share extends AbstractShare {
$this->execute('cd /');
return $this->parser->parseDir($output, $path, function ($path) {
return $this->parser->parseDir($output, $path, function (string $path) {
return $this->getAcls($path);
});
}
@ -424,12 +443,11 @@ class Share extends AbstractShare {
/**
* @param string $command
* @return array
* @return string[]
*/
protected function execute(string $command): array {
$this->connect();
$this->connection->write($command . PHP_EOL);
return $this->connection->read();
$this->connect()->write($command . PHP_EOL);
return $this->connect()->read();
}
/**
@ -451,7 +469,6 @@ class Share extends AbstractShare {
return true;
} else {
$this->parser->checkForError($lines, $path);
return false;
}
}
@ -487,6 +504,12 @@ class Share extends AbstractShare {
return '"' . $path . '"';
}
/**
* @param string $path
* @return ACL[]
* @throws ConnectionException
* @throws ConnectException
*/
protected function getAcls(string $path): array {
$commandPath = $this->system->getSmbcAclsPath();
if (!$commandPath) {
@ -506,7 +529,7 @@ class Share extends AbstractShare {
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
$connection->connect();
if (!$connection->isValid()) {
throw new ConnectionException($connection->readLine());
throw new ConnectionException((string)$connection->readLine());
}
$rawAcls = $connection->readAll();