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

Fix CVE-2023-49316 - Create new adapter for phpseclib V.3 #711

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
242 changes: 242 additions & 0 deletions src/Gaufrette/Adapter/Phpseclib3Sftp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
<?php

namespace Gaufrette\Adapter;

use Gaufrette\Adapter;
use Gaufrette\File;
use Gaufrette\Filesystem;
use phpseclib3\Net\SFTP as SecLibSFTP;

class Phpseclib3Sftp implements Adapter, FileFactory, ListKeysAware
{
protected $sftp;
protected $directory;
protected $create;
protected $initialized = false;

/**
* @param SecLibSFTP $sftp An Sftp instance
* @param string $directory The distant directory
* @param bool $create Whether to create the remote directory if it
* does not exist
*/
public function __construct(SecLibSFTP $sftp, $directory = null, $create = false)
{
if (!class_exists(SecLibSFTP::class)) {
throw new \LogicException('You need to install package "phpseclib/phpseclib" to use this adapter');
}
$this->sftp = $sftp;
$this->directory = $directory;
$this->create = $create;
}

/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->sftp->get($this->computePath($key));
}

/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->initialize();

$sourcePath = $this->computePath($sourceKey);
$targetPath = $this->computePath($targetKey);

$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true);

return $this->sftp->rename($sourcePath, $targetPath);
}

/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->initialize();

$path = $this->computePath($key);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($path), true);
if ($this->sftp->put($path, $content)) {
return $this->sftp->filesize($path);
}

return false;
}

/**
* {@inheritdoc}
*/
public function exists($key)
{
$this->initialize();

return false !== $this->sftp->stat($this->computePath($key));
}

/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$this->initialize();

$pwd = $this->sftp->pwd();
if ($this->sftp->chdir($this->computePath($key))) {
$this->sftp->chdir($pwd);

return true;
}

return false;
}

/**
* {@inheritdoc}
*/
public function keys()
{
$keys = $this->fetchKeys();

return $keys['keys'];
}

/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
preg_match('/(.*?)[^\/]*$/', $prefix, $match);
$directory = rtrim($match[1], '/');

$keys = $this->fetchKeys($directory, false);

if ($directory === $prefix) {
return $keys;
}

$filteredKeys = [];
foreach (['keys', 'dirs'] as $hash) {
$filteredKeys[$hash] = [];
foreach ($keys[$hash] as $key) {
if (0 === strpos($key, $prefix)) {
$filteredKeys[$hash][] = $key;
}
}
}

return $filteredKeys;
}

/**
* {@inheritdoc}
*/
public function mtime($key)
{
$this->initialize();

$stat = $this->sftp->stat($this->computePath($key));

return $stat['mtime'] ?? false;
}

/**
* {@inheritdoc}
*/
public function delete($key)
{
return $this->sftp->delete($this->computePath($key), false);
}

/**
* {@inheritdoc}
*/
public function createFile($key, Filesystem $filesystem)
{
$file = new File($key, $filesystem);

$stat = $this->sftp->stat($this->computePath($key));
if (isset($stat['size'])) {
$file->setSize($stat['size']);
}

return $file;
}

/**
* Performs the adapter's initialization.
*
* It will ensure the root directory exists
*/
protected function initialize()
{
if ($this->initialized) {
return;
}

$this->ensureDirectoryExists($this->directory, $this->create);

$this->initialized = true;
}

protected function ensureDirectoryExists($directory, $create)
{
$pwd = $this->sftp->pwd();
if ($this->sftp->chdir($directory)) {
$this->sftp->chdir($pwd);
} elseif ($create) {
if (!$this->sftp->mkdir($directory, 0777, true)) {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist and could not be created (%s).', $this->directory, $this->sftp->getLastSFTPError()));
}
} else {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $this->directory));
}
}

protected function computePath($key)
{
return $this->directory . '/' . ltrim($key, '/');
}

protected function fetchKeys($directory = '', $onlyKeys = true)
{
$keys = ['keys' => [], 'dirs' => []];
$computedPath = $this->computePath($directory);

if (!$this->sftp->file_exists($computedPath)) {
return $keys;
}

$list = $this->sftp->rawlist($computedPath);
foreach ((array) $list as $filename => $stat) {
if ('.' === $filename || '..' === $filename) {
continue;
}

$path = ltrim($directory . '/' . $filename, '/');
if (isset($stat['type']) && $stat['type'] === NET_SFTP_TYPE_DIRECTORY) {
$keys['dirs'][] = $path;
} else {
$keys['keys'][] = $path;
}
}

$dirs = $keys['dirs'];

if ($onlyKeys && !empty($dirs)) {
$keys['keys'] = array_merge($keys['keys'], $dirs);
$keys['dirs'] = [];
}

foreach ($dirs as $dir) {
$keys = array_merge_recursive($keys, $this->fetchKeys($dir, $onlyKeys));
}

return $keys;
}
}