Skip to content

Commit

Permalink
Merge pull request #108 from icewind1991/disable-readline
Browse files Browse the repository at this point in the history
fix support for smbclient compiled without readline support
  • Loading branch information
icewind1991 authored Nov 3, 2021
2 parents 9f6b8f1 + 9db846f commit 9132f32
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 46 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,39 @@ jobs:
with:
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:
runs-on: ubuntu-20.04
name: Kerberos SSO tests
Expand Down
2 changes: 1 addition & 1 deletion src/IShare.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function get(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
* @return resource a read only stream with the contents of the remote file
Expand Down
41 changes: 10 additions & 31 deletions src/Wrapped/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function write(string $input) {
public function clearTillPrompt(): void {
$this->write('');
do {
$promptLine = $this->readLine();
$promptLine = $this->readTillPrompt();
if ($promptLine === false) {
break;
}
Expand All @@ -56,13 +56,12 @@ public function clearTillPrompt(): void {
if ($this->write('') === false) {
throw new ConnectionRefusedException();
}
$this->readLine();
$this->readTillPrompt();
}

/**
* 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[]
* @throws AuthenticationException
* @throws ConnectException
Expand All @@ -71,42 +70,22 @@ public function clearTillPrompt(): void {
* @throws NoLoginServerException
* @throws AccessDeniedException
*/
public function read(callable $callback = null): array {
public function read(): array {
if (!$this->isValid()) {
throw new ConnectionException('Connection not valid');
}
$promptLine = $this->readLine(); //first line is prompt
if ($promptLine === false) {
$this->unknownError($promptLine);
}
$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 = $this->readTillPrompt();
if ($output === false) {
$this->unknownError(false);
}
$output = explode("\n", $output);
// last line contains the prompt
array_pop($output);
return $output;
}

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;
}

/**
Expand Down
14 changes: 9 additions & 5 deletions src/Wrapped/NotifyHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,20 @@ public function getChanges(): array {
*/
public function listen(callable $callback): void {
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);
$change = $this->parseChangeLine($line);
if ($change) {
$result = $callback($change);
return $result === false ? false : true;
} else {
return true;
if ($result === false) {
break;
}
}
});
};
}
}

Expand Down
24 changes: 21 additions & 3 deletions src/Wrapped/RawConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public function connect(): void {

setlocale(LC_ALL, Server::LOCALE);
$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,
'LANG' => Server::LOCALE,
'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output
Expand Down Expand Up @@ -109,13 +110,30 @@ public function write(string $input) {
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
*
* @return string|false
*/
public function readLine() {
return stream_get_line($this->getOutputStream(), 4086, "\n");
public function readLine(string $end = "\n") {
return stream_get_line($this->getOutputStream(), 4096, $end);
}

/**
Expand Down
14 changes: 11 additions & 3 deletions src/Wrapped/Share.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,17 @@ public function read(string $source) {
// since returned stream is closed by the caller we need to create a new instance
// since we can't re-use the same file descriptor over multiple calls
$connection = $this->getConnection();
stream_set_blocking($connection->getOutputStream(), false);

$connection->write('get ' . $source . ' ' . $this->system->getFD(5));
$connection->write('exit');
$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;
}

Expand All @@ -374,7 +380,9 @@ public function write(string $target) {

// use a close callback to ensure the upload is finished before continuing
// 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
});
if (is_resource($stream)) {
Expand Down Expand Up @@ -446,7 +454,7 @@ public function notify(string $path): INotifyHandler {
* @return string[]
*/
protected function execute(string $command): array {
$this->connect()->write($command . PHP_EOL);
$this->connect()->write($command);
return $this->connect()->read();
}

Expand Down
9 changes: 7 additions & 2 deletions tests/AbstractShareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
use Icewind\SMB\BasicAuth;
use Icewind\SMB\Exception\AccessDeniedException;
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\ForbiddenException;
use Icewind\SMB\Exception\InvalidPathException;
Expand Down Expand Up @@ -46,6 +49,8 @@ abstract class AbstractShareTest extends TestCase {
abstract public function getServerClass(): string;

public function setUp(): void {
// ob_end_flush();
// var_dump($this->getName());
$this->config = json_decode(file_get_contents(__DIR__ . '/config.json'));
$options = new Options();
$options->setMinProtocol(IOptions::PROTOCOL_SMB2);
Expand Down Expand Up @@ -784,10 +789,10 @@ public function testWrongUserName() {
$share = $server->getShare($this->config->share);
$share->dir("");
$this->fail("Expected exception");
} catch (AccessDeniedException $e) {
$this->assertTrue(true);
} catch (ForbiddenException $e) {
$this->assertTrue(true);
} catch (ConnectException $e) {
$this->assertTrue(true);
}
}
}
2 changes: 1 addition & 1 deletion tests/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"host": "localhost",
"host": "skybox.icewind.link",
"user": "test",
"password": "test",
"share": "test",
Expand Down

0 comments on commit 9132f32

Please sign in to comment.