Skip to content

Commit

Permalink
Disconnect the SFTP connection if UnexpectedValueException thrown i…
Browse files Browse the repository at this point in the history
…n the SFTP class.

- This exception indicated that the request/response packets are mismatched.
  • Loading branch information
TingSong Xu committed Dec 1, 2023
1 parent 4213068 commit 52787b6
Showing 1 changed file with 138 additions and 70 deletions.
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

0 comments on commit 52787b6

Please sign in to comment.