Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disconnect the SFTP connection if UnexpectedValueException thrown #1719

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 138 additions & 70 deletions src/PhpseclibV3/SftpAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
use League\Flysystem\UnableToCheckFileExistence;
use League\Flysystem\UnableToCopyFile;
use League\Flysystem\UnableToCreateDirectory;
use League\Flysystem\UnableToDeleteDirectory;
use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToListContents;
use League\Flysystem\UnableToMoveFile;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToRetrieveMetadata;
Expand All @@ -27,20 +30,22 @@
use phpseclib3\Net\SFTP;
use Throwable;

use UnexpectedValueException;
use function rtrim;

class SftpAdapter implements FilesystemAdapter
{
private const UNEXPECTED_SFTP_PACKET_MESSAGE = 'SFTP server respond an unexpected packet.';
private VisibilityConverter $visibilityConverter;
private PathPrefixer $prefixer;
private MimeTypeDetector $mimeTypeDetector;
private PathPrefixer $prefixer;
private MimeTypeDetector $mimeTypeDetector;

public function __construct(
private ConnectionProvider $connectionProvider,
string $root,
VisibilityConverter $visibilityConverter = null,
MimeTypeDetector $mimeTypeDetector = null,
private bool $detectMimeTypeUsingPath = false,
string $root,
VisibilityConverter $visibilityConverter = null,
MimeTypeDetector $mimeTypeDetector = null,
private bool $detectMimeTypeUsingPath = false,
) {
$this->prefixer = new PathPrefixer($root);
$this->visibilityConverter = $visibilityConverter ?? new PortableVisibilityConverter();
Expand All @@ -50,9 +55,12 @@ public function __construct(
public function fileExists(string $path): bool
{
$location = $this->prefixer->prefixPath($path);

$connection = $this->connectionProvider->provideConnection();
try {
return $this->connectionProvider->provideConnection()->is_file($location);
return $connection->is_file($location);
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToCheckFileExistence::forLocation($path, $exception);
} catch (Throwable $exception) {
throw UnableToCheckFileExistence::forLocation($path, $exception);
}
Expand All @@ -61,9 +69,12 @@ public function fileExists(string $path): bool
public function directoryExists(string $path): bool
{
$location = $this->prefixer->prefixDirectoryPath($path);

$connection = $this->connectionProvider->provideConnection();
try {
return $this->connectionProvider->provideConnection()->is_dir($location);
return $connection->is_dir($location);
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToCheckDirectoryExistence::forLocation($path, $exception);
} catch (Throwable $exception) {
throw UnableToCheckDirectoryExistence::forLocation($path, $exception);
}
Expand All @@ -80,14 +91,19 @@ private function upload(string $path, $contents, Config $config): void
{
$this->ensureParentDirectoryExists($path, $config);
$connection = $this->connectionProvider->provideConnection();
$location = $this->prefixer->prefixPath($path);
try {
$location = $this->prefixer->prefixPath($path);

if ( ! $connection->put($location, $contents, SFTP::SOURCE_STRING)) {
throw UnableToWriteFile::atLocation($path, 'not able to write the file');
}
if ( ! $connection->put($location, $contents, SFTP::SOURCE_STRING)) {
throw UnableToWriteFile::atLocation($path, $connection->getLastSFTPError());
}

if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($path, $visibility);
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($path, $visibility);
}
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToWriteFile::atLocation($path, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
}
}

Expand All @@ -109,16 +125,21 @@ private function makeDirectory(string $directory, ?string $visibility): void
$location = $this->prefixer->prefixPath($directory);
$connection = $this->connectionProvider->provideConnection();

if ($connection->is_dir($location)) {
return;
}
try {
if ($connection->is_dir($location)) {
return;
}

$mode = $visibility ? $this->visibilityConverter->forDirectory(
$visibility
) : $this->visibilityConverter->defaultForDirectories();
$mode = $visibility ? $this->visibilityConverter->forDirectory(
$visibility
) : $this->visibilityConverter->defaultForDirectories();

if ( ! $connection->mkdir($location, $mode, true) && ! $connection->is_dir($location)) {
throw UnableToCreateDirectory::atLocation($directory);
if ( ! $connection->mkdir($location, $mode, true) && ! $connection->is_dir($location)) {
throw UnableToCreateDirectory::atLocation($directory, $connection->getLastSFTPError());
}
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToCreateDirectory::atLocation($directory, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
}
}

Expand Down Expand Up @@ -148,13 +169,20 @@ public function read(string $path): string
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$contents = $connection->get($location);
try {
$contents = $connection->get($location);

if ( ! is_string($contents)) {
throw UnableToReadFile::fromLocation($path);
}
if ( ! is_string($contents)) {
throw UnableToReadFile::fromLocation($path, $connection->getLastSFTPError());
}

return $contents;
return $contents;
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToReadFile::fromLocation($path, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
} catch (Throwable $exception) {
throw UnableToReadFile::fromLocation($path, $exception->getMessage(), $exception);
}
}

public function readStream(string $path)
Expand All @@ -164,29 +192,49 @@ public function readStream(string $path)
/** @var resource $readStream */
$readStream = fopen('php://temp', 'w+');

if ( ! $connection->get($location, $readStream)) {
fclose($readStream);
throw UnableToReadFile::fromLocation($path);
}
try {
if ( ! $connection->get($location, $readStream)) {
fclose($readStream);
throw UnableToReadFile::fromLocation($path, $connection->getLastSFTPError());
}

rewind($readStream);
rewind($readStream);

return $readStream;
return $readStream;
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
fclose($readStream);
throw UnableToReadFile::fromLocation($path, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
} catch (Throwable $exception) {
throw UnableToReadFile::fromLocation($path, $exception->getMessage(), $exception);
}
}

public function delete(string $path): void
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$connection->delete($location);
try {
$connection->delete($location);
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToDeleteFile::atLocation($path, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
} catch (Throwable $exception) {
throw UnableToDeleteFile::atLocation($path, $exception->getMessage(), $exception);
}
}

public function deleteDirectory(string $path): void
{
$location = rtrim($this->prefixer->prefixPath($path), '/') . '/';
$connection = $this->connectionProvider->provideConnection();
$connection->delete($location);
$connection->rmdir($location);
try {
$connection->delete($location);
$connection->rmdir($location);
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToDeleteDirectory::atLocation($path, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
}
}

public function createDirectory(string $path, Config $config): void
Expand All @@ -200,28 +248,38 @@ public function setVisibility(string $path, string $visibility): void
$connection = $this->connectionProvider->provideConnection();
$mode = $this->visibilityConverter->forFile($visibility);

if ( ! $connection->chmod($mode, $location, false)) {
throw UnableToSetVisibility::atLocation($path);
try {
if ( ! $connection->chmod($mode, $location, false)) {
throw UnableToSetVisibility::atLocation($path, $connection->getLastSFTPError());
}
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToSetVisibility::atLocation($path, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
}
}

private function fetchFileMetadata(string $path, string $type): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$stat = $connection->stat($location);
try {
$stat = $connection->stat($location);

if ( ! is_array($stat)) {
throw UnableToRetrieveMetadata::create($path, $type);
}
if ( ! is_array($stat)) {
throw UnableToRetrieveMetadata::create($path, $type);
}

$attributes = $this->convertListingToAttributes($path, $stat);
$attributes = $this->convertListingToAttributes($path, $stat);

if ( ! $attributes instanceof FileAttributes) {
throw UnableToRetrieveMetadata::create($path, $type, 'path is not a file');
}
if ( ! $attributes instanceof FileAttributes) {
throw UnableToRetrieveMetadata::create($path, $type, 'path is not a file');
}

return $attributes;
return $attributes;
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToRetrieveMetadata::create($path, $type, self::UNEXPECTED_SFTP_PACKET_MESSAGE, $exception);
}
}

public function mimeType(string $path): FileAttributes
Expand Down Expand Up @@ -259,29 +317,34 @@ public function visibility(string $path): FileAttributes
public function listContents(string $path, bool $deep): iterable
{
$connection = $this->connectionProvider->provideConnection();
$location = $this->prefixer->prefixPath(rtrim($path, '/')) . '/';
$listing = $connection->rawlist($location, false);

if (false === $listing) {
return;
}
try {
$location = $this->prefixer->prefixPath(rtrim($path, '/')) . '/';
$listing = $connection->rawlist($location, false);

foreach ($listing as $filename => $attributes) {
if ($filename === '.' || $filename === '..') {
continue;
if (false === $listing) {
return;
}

// Ensure numeric keys are strings.
$filename = (string) $filename;
$path = $this->prefixer->stripPrefix($location . ltrim($filename, '/'));
$attributes = $this->convertListingToAttributes($path, $attributes);
yield $attributes;
foreach ($listing as $filename => $attributes) {
if ($filename === '.' || $filename === '..') {
continue;
}

// Ensure numeric keys are strings.
$filename = (string) $filename;
$path = $this->prefixer->stripPrefix($location . ltrim($filename, '/'));
$attributes = $this->convertListingToAttributes($path, $attributes);
yield $attributes;

if ($deep && $attributes->isDir()) {
foreach ($this->listContents($attributes->path(), true) as $child) {
yield $child;
if ($deep && $attributes->isDir()) {
foreach ($this->listContents($attributes->path(), true) as $child) {
yield $child;
}
}
}
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToListContents::atLocation($path, $deep, $exception);
}
}

Expand Down Expand Up @@ -314,13 +377,18 @@ public function move(string $source, string $destination, Config $config): void

try {
$this->ensureParentDirectoryExists($destination, $config);

if ( ! $connection->rename($sourceLocation, $destinationLocation)) {
throw UnableToMoveFile::fromLocationTo($source, $destination);
}
} catch (UnableToMoveFile $exception) {
throw $exception;
} catch (UnexpectedValueException $exception) {
$connection->disconnect();
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
} catch (Throwable $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}

if ( ! $connection->rename($sourceLocation, $destinationLocation)) {
throw UnableToMoveFile::fromLocationTo($source, $destination);
}
}

public function copy(string $source, string $destination, Config $config): void
Expand Down
Loading