mirror of
https://codeberg.org/icewind/SMB.git
synced 2026-06-03 17:24:07 +02:00
Merge pull request #108 from icewind1991/disable-readline
fix support for smbclient compiled without readline support
This commit is contained in:
commit
9132f32aa0
8 changed files with 93 additions and 46 deletions
33
.github/workflows/ci.yaml
vendored
33
.github/workflows/ci.yaml
vendored
|
|
@ -138,6 +138,39 @@ jobs:
|
||||||
with:
|
with:
|
||||||
files: ./coverage.xml
|
files: ./coverage.xml
|
||||||
|
|
||||||
|
alpine-test:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
name: Unit tests (alpine)
|
||||||
|
|
||||||
|
services:
|
||||||
|
samba:
|
||||||
|
image: "servercontainers/samba"
|
||||||
|
env:
|
||||||
|
ACCOUNT_test: test
|
||||||
|
UID_test: 1000
|
||||||
|
SAMBA_VOLUME_CONFIG_test: "[test]; path=/tmp; valid users = test; guest ok = no; read only = no; browseable = yes"
|
||||||
|
ports:
|
||||||
|
- 139:139
|
||||||
|
- 445:445
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: 8.0
|
||||||
|
- name: Composer
|
||||||
|
run: composer install
|
||||||
|
- name: Pull images
|
||||||
|
run: |
|
||||||
|
docker pull icewind1991/smbclient-php-alpine
|
||||||
|
- name: Config
|
||||||
|
run: |
|
||||||
|
echo '{"host": "localhost","user": "test","password": "test","share": "test","root": ""}' > tests/config.json
|
||||||
|
- name: PHPUnit Tests
|
||||||
|
run: |
|
||||||
|
docker run --network "host" --rm -v $PWD:/smb icewind1991/smbclient-php-alpine /smb/vendor/bin/phpunit -c /smb/tests/phpunit.xml /smb/tests
|
||||||
|
|
||||||
kerberos-sso:
|
kerberos-sso:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
name: Kerberos SSO tests
|
name: Kerberos SSO tests
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ interface IShare {
|
||||||
public function put(string $source, string $target): bool;
|
public function put(string $source, string $target): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a readable stream top a remote file
|
* Open a readable stream to a remote file
|
||||||
*
|
*
|
||||||
* @param string $source
|
* @param string $source
|
||||||
* @return resource a read only stream with the contents of the remote file
|
* @return resource a read only stream with the contents of the remote file
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class Connection extends RawConnection {
|
||||||
public function clearTillPrompt(): void {
|
public function clearTillPrompt(): void {
|
||||||
$this->write('');
|
$this->write('');
|
||||||
do {
|
do {
|
||||||
$promptLine = $this->readLine();
|
$promptLine = $this->readTillPrompt();
|
||||||
if ($promptLine === false) {
|
if ($promptLine === false) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -56,13 +56,12 @@ class Connection extends RawConnection {
|
||||||
if ($this->write('') === false) {
|
if ($this->write('') === false) {
|
||||||
throw new ConnectionRefusedException();
|
throw new ConnectionRefusedException();
|
||||||
}
|
}
|
||||||
$this->readLine();
|
$this->readTillPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get all unprocessed output from smbclient until the next prompt
|
* get all unprocessed output from smbclient until the next prompt
|
||||||
*
|
*
|
||||||
* @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
|
||||||
|
|
@ -71,42 +70,22 @@ class Connection extends RawConnection {
|
||||||
* @throws NoLoginServerException
|
* @throws NoLoginServerException
|
||||||
* @throws AccessDeniedException
|
* @throws AccessDeniedException
|
||||||
*/
|
*/
|
||||||
public function read(callable $callback = null): array {
|
public function read(): array {
|
||||||
if (!$this->isValid()) {
|
if (!$this->isValid()) {
|
||||||
throw new ConnectionException('Connection not valid');
|
throw new ConnectionException('Connection not valid');
|
||||||
}
|
}
|
||||||
$promptLine = $this->readLine(); //first line is prompt
|
$output = $this->readTillPrompt();
|
||||||
if ($promptLine === false) {
|
if ($output === false) {
|
||||||
$this->unknownError($promptLine);
|
$this->unknownError(false);
|
||||||
}
|
|
||||||
$this->parser->checkConnectionError($promptLine);
|
|
||||||
|
|
||||||
$output = [];
|
|
||||||
if (!$this->isPrompt($promptLine)) {
|
|
||||||
$line = $promptLine;
|
|
||||||
} else {
|
|
||||||
$line = $this->readLine();
|
|
||||||
}
|
|
||||||
if ($line === false) {
|
|
||||||
$this->unknownError($promptLine);
|
|
||||||
}
|
|
||||||
while ($line !== false && !$this->isPrompt($line)) { //next prompt functions as delimiter
|
|
||||||
if (is_callable($callback)) {
|
|
||||||
$result = $callback($line);
|
|
||||||
if ($result === false) { // allow the callback to close the connection for infinite running commands
|
|
||||||
$this->close(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$output[] = $line;
|
|
||||||
}
|
|
||||||
$line = $this->readLine();
|
|
||||||
}
|
}
|
||||||
|
$output = explode("\n", $output);
|
||||||
|
// last line contains the prompt
|
||||||
|
array_pop($output);
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isPrompt(string $line): bool {
|
private function isPrompt(string $line): bool {
|
||||||
return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER;
|
return substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -65,16 +65,20 @@ class NotifyHandler implements INotifyHandler {
|
||||||
*/
|
*/
|
||||||
public function listen(callable $callback): void {
|
public function listen(callable $callback): void {
|
||||||
if ($this->listening) {
|
if ($this->listening) {
|
||||||
$this->connection->read(function (string $line) use ($callback): bool {
|
while (true) {
|
||||||
|
$line = $this->connection->readLine();
|
||||||
|
if ($line === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
$this->checkForError($line);
|
$this->checkForError($line);
|
||||||
$change = $this->parseChangeLine($line);
|
$change = $this->parseChangeLine($line);
|
||||||
if ($change) {
|
if ($change) {
|
||||||
$result = $callback($change);
|
$result = $callback($change);
|
||||||
return $result === false ? false : true;
|
if ($result === false) {
|
||||||
} else {
|
break;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,8 @@ class RawConnection {
|
||||||
|
|
||||||
setlocale(LC_ALL, Server::LOCALE);
|
setlocale(LC_ALL, Server::LOCALE);
|
||||||
$env = array_merge($this->env, [
|
$env = array_merge($this->env, [
|
||||||
'CLI_FORCE_INTERACTIVE' => 'y', // Needed or the prompt isn't displayed!!
|
'CLI_FORCE_INTERACTIVE' => 'y', // Make sure the prompt is displayed
|
||||||
|
'CLI_NO_READLINE' => 1, // Not all distros build smbclient with readline, disable it to get consistent behaviour
|
||||||
'LC_ALL' => Server::LOCALE,
|
'LC_ALL' => Server::LOCALE,
|
||||||
'LANG' => Server::LOCALE,
|
'LANG' => Server::LOCALE,
|
||||||
'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output
|
'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output
|
||||||
|
|
@ -109,13 +110,30 @@ class RawConnection {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read output till the next prompt
|
||||||
|
*
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
public function readTillPrompt() {
|
||||||
|
$output = "";
|
||||||
|
do {
|
||||||
|
$chunk = $this->readLine('\> ');
|
||||||
|
if ($chunk === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$output .= $chunk;
|
||||||
|
} while (strlen($chunk) == 4096 && strpos($chunk, "smb:") === false);
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* read a line of output
|
* read a line of output
|
||||||
*
|
*
|
||||||
* @return string|false
|
* @return string|false
|
||||||
*/
|
*/
|
||||||
public function readLine() {
|
public function readLine(string $end = "\n") {
|
||||||
return stream_get_line($this->getOutputStream(), 4086, "\n");
|
return stream_get_line($this->getOutputStream(), 4096, $end);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -345,11 +345,17 @@ class Share extends AbstractShare {
|
||||||
// since returned stream is closed by the caller we need to create a new instance
|
// since returned stream is closed by the caller we need to create a new instance
|
||||||
// since we can't re-use the same file descriptor over multiple calls
|
// since we can't re-use the same file descriptor over multiple calls
|
||||||
$connection = $this->getConnection();
|
$connection = $this->getConnection();
|
||||||
|
stream_set_blocking($connection->getOutputStream(), false);
|
||||||
|
|
||||||
$connection->write('get ' . $source . ' ' . $this->system->getFD(5));
|
$connection->write('get ' . $source . ' ' . $this->system->getFD(5));
|
||||||
$connection->write('exit');
|
$connection->write('exit');
|
||||||
$fh = $connection->getFileOutputStream();
|
$fh = $connection->getFileOutputStream();
|
||||||
stream_context_set_option($fh, 'file', 'connection', $connection);
|
$fh = CallbackWrapper::wrap($fh, function() use ($connection) {
|
||||||
|
$connection->write('');
|
||||||
|
});
|
||||||
|
if (!is_resource($fh)) {
|
||||||
|
throw new Exception("Failed to wrap file output");
|
||||||
|
}
|
||||||
return $fh;
|
return $fh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,7 +380,9 @@ class Share extends AbstractShare {
|
||||||
|
|
||||||
// use a close callback to ensure the upload is finished before continuing
|
// use a close callback to ensure the upload is finished before continuing
|
||||||
// this also serves as a way to keep the connection in scope
|
// this also serves as a way to keep the connection in scope
|
||||||
$stream = CallbackWrapper::wrap($fh, null, null, function () use ($connection) {
|
$stream = CallbackWrapper::wrap($fh, function() use ($connection) {
|
||||||
|
$connection->write('');
|
||||||
|
}, null, function () use ($connection) {
|
||||||
$connection->close(false); // dont terminate, give the upload some time
|
$connection->close(false); // dont terminate, give the upload some time
|
||||||
});
|
});
|
||||||
if (is_resource($stream)) {
|
if (is_resource($stream)) {
|
||||||
|
|
@ -446,7 +454,7 @@ class Share extends AbstractShare {
|
||||||
* @return string[]
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
protected function execute(string $command): array {
|
protected function execute(string $command): array {
|
||||||
$this->connect()->write($command . PHP_EOL);
|
$this->connect()->write($command);
|
||||||
return $this->connect()->read();
|
return $this->connect()->read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ use Icewind\SMB\ACL;
|
||||||
use Icewind\SMB\BasicAuth;
|
use Icewind\SMB\BasicAuth;
|
||||||
use Icewind\SMB\Exception\AccessDeniedException;
|
use Icewind\SMB\Exception\AccessDeniedException;
|
||||||
use Icewind\SMB\Exception\AlreadyExistsException;
|
use Icewind\SMB\Exception\AlreadyExistsException;
|
||||||
|
use Icewind\SMB\Exception\ConnectException;
|
||||||
|
use Icewind\SMB\Exception\ConnectionException;
|
||||||
|
use Icewind\SMB\Exception\ConnectionRefusedException;
|
||||||
use Icewind\SMB\Exception\FileInUseException;
|
use Icewind\SMB\Exception\FileInUseException;
|
||||||
use Icewind\SMB\Exception\ForbiddenException;
|
use Icewind\SMB\Exception\ForbiddenException;
|
||||||
use Icewind\SMB\Exception\InvalidPathException;
|
use Icewind\SMB\Exception\InvalidPathException;
|
||||||
|
|
@ -46,6 +49,8 @@ abstract class AbstractShareTest extends TestCase {
|
||||||
abstract public function getServerClass(): string;
|
abstract public function getServerClass(): string;
|
||||||
|
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
|
// ob_end_flush();
|
||||||
|
// var_dump($this->getName());
|
||||||
$this->config = json_decode(file_get_contents(__DIR__ . '/config.json'));
|
$this->config = json_decode(file_get_contents(__DIR__ . '/config.json'));
|
||||||
$options = new Options();
|
$options = new Options();
|
||||||
$options->setMinProtocol(IOptions::PROTOCOL_SMB2);
|
$options->setMinProtocol(IOptions::PROTOCOL_SMB2);
|
||||||
|
|
@ -784,10 +789,10 @@ abstract class AbstractShareTest extends TestCase {
|
||||||
$share = $server->getShare($this->config->share);
|
$share = $server->getShare($this->config->share);
|
||||||
$share->dir("");
|
$share->dir("");
|
||||||
$this->fail("Expected exception");
|
$this->fail("Expected exception");
|
||||||
} catch (AccessDeniedException $e) {
|
|
||||||
$this->assertTrue(true);
|
|
||||||
} catch (ForbiddenException $e) {
|
} catch (ForbiddenException $e) {
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
} catch (ConnectException $e) {
|
||||||
|
$this->assertTrue(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"host": "localhost",
|
"host": "skybox.icewind.link",
|
||||||
"user": "test",
|
"user": "test",
|
||||||
"password": "test",
|
"password": "test",
|
||||||
"share": "test",
|
"share": "test",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue