msd Backup hinzugefügt

This commit is contained in:
aschwarz
2023-07-25 19:16:12 +02:00
parent 50297c1d25
commit 1d3ed789b5
680 changed files with 103120 additions and 0 deletions

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use phpseclib\Net\SFTP;
interface ConnectionProvider
{
public function provideConnection(): SFTP;
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use phpseclib\Net\SFTP;
interface ConnectivityChecker
{
public function isConnected(SFTP $connection): bool;
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use phpseclib\Net\SFTP;
class FixatedConnectivityChecker implements ConnectivityChecker
{
/**
* @var int
*/
private $succeedAfter;
/**
* @var int
*/
private $numberOfTimesChecked = 0;
public function __construct(int $succeedAfter = 0)
{
$this->succeedAfter = $succeedAfter;
}
public function isConnected(SFTP $connection): bool
{
if ($this->numberOfTimesChecked >= $this->succeedAfter) {
return true;
}
$this->numberOfTimesChecked++;
return false;
}
}

View File

@ -0,0 +1,7 @@
## Sub-split of Flysystem for SFTP using phpseclib2.
```bash
composer require league/flysystem-sftp
```
View the [documentation](https://flysystem.thephpleague.com/v2/docs/adapter/sftp/).

View File

@ -0,0 +1,334 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use League\Flysystem\Config;
use League\Flysystem\DirectoryAttributes;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\FilesystemException;
use League\Flysystem\PathPrefixer;
use League\Flysystem\StorageAttributes;
use League\Flysystem\UnableToCopyFile;
use League\Flysystem\UnableToCreateDirectory;
use League\Flysystem\UnableToMoveFile;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToRetrieveMetadata;
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\UnixVisibility\VisibilityConverter;
use League\MimeTypeDetection\FinfoMimeTypeDetector;
use League\MimeTypeDetection\MimeTypeDetector;
use phpseclib\Net\SFTP;
use Throwable;
class SftpAdapter implements FilesystemAdapter
{
/**
* @var ConnectionProvider
*/
private $connectionProvider;
/**
* @var VisibilityConverter
*/
private $visibilityConverter;
/**
* @var PathPrefixer
*/
private $prefixer;
/**
* @var MimeTypeDetector
*/
private $mimeTypeDetector;
public function __construct(
ConnectionProvider $connectionProvider,
string $root,
VisibilityConverter $visibilityConverter = null,
MimeTypeDetector $mimeTypeDetector = null
) {
$this->connectionProvider = $connectionProvider;
$this->prefixer = new PathPrefixer($root);
$this->visibilityConverter = $visibilityConverter ?: new PortableVisibilityConverter();
$this->mimeTypeDetector = $mimeTypeDetector ?: new FinfoMimeTypeDetector();
}
public function fileExists(string $path): bool
{
$location = $this->prefixer->prefixPath($path);
return $this->connectionProvider->provideConnection()->is_file($location);
}
/**
* @param string $path
* @param string|resource $contents
* @param Config $config
*
* @throws FilesystemException
*/
private function upload(string $path, $contents, Config $config): void
{
$this->ensureParentDirectoryExists($path, $config);
$connection = $this->connectionProvider->provideConnection();
$location = $this->prefixer->prefixPath($path);
if ( ! $connection->put($location, $contents, SFTP::SOURCE_STRING)) {
throw UnableToWriteFile::atLocation($path, 'not able to write the file');
}
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($path, $visibility);
}
}
private function ensureParentDirectoryExists(string $path, Config $config): void
{
$parentDirectory = dirname($path);
if ($parentDirectory === '' || $parentDirectory === '.') {
return;
}
/** @var string $visibility */
$visibility = $config->get(Config::OPTION_DIRECTORY_VISIBILITY);
$this->makeDirectory($parentDirectory, $visibility);
}
private function makeDirectory(string $directory, ?string $visibility): void
{
$location = $this->prefixer->prefixPath($directory);
$connection = $this->connectionProvider->provideConnection();
if ($connection->is_dir($location)) {
return;
}
$mode = $visibility ? $this->visibilityConverter->forDirectory(
$visibility
) : $this->visibilityConverter->defaultForDirectories();
if ( ! $connection->mkdir($location, $mode, true)) {
throw UnableToCreateDirectory::atLocation($directory);
}
}
public function write(string $path, string $contents, Config $config): void
{
try {
$this->upload($path, $contents, $config);
} catch (UnableToWriteFile $exception) {
throw $exception;
} catch (Throwable $exception) {
throw UnableToWriteFile::atLocation($path, '', $exception);
}
}
public function writeStream(string $path, $contents, Config $config): void
{
try {
$this->upload($path, $contents, $config);
} catch (UnableToWriteFile $exception) {
throw $exception;
} catch (Throwable $exception) {
throw UnableToWriteFile::atLocation($path, '', $exception);
}
}
public function read(string $path): string
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$contents = $connection->get($location);
if ( ! is_string($contents)) {
throw UnableToReadFile::fromLocation($path);
}
return $contents;
}
public function readStream(string $path)
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
/** @var resource $readStream */
$readStream = fopen('php://temp', 'w+');
if ( ! $connection->get($location, $readStream)) {
fclose($readStream);
throw UnableToReadFile::fromLocation($path);
}
rewind($readStream);
return $readStream;
}
public function delete(string $path): void
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$connection->delete($location);
}
public function deleteDirectory(string $path): void
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$connection->delete(rtrim($location, '/') . '/');
}
public function createDirectory(string $path, Config $config): void
{
$this->makeDirectory($path, $config->get(Config::OPTION_DIRECTORY_VISIBILITY));
}
public function setVisibility(string $path, string $visibility): void
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$mode = $this->visibilityConverter->forFile($visibility);
if ( ! $connection->chmod($mode, $location, false)) {
throw UnableToSetVisibility::atLocation($path);
}
}
private function fetchFileMetadata(string $path, string $type): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connectionProvider->provideConnection();
$stat = $connection->stat($location);
if ( ! is_array($stat)) {
throw UnableToRetrieveMetadata::create($path, $type);
}
$attributes = $this->convertListingToAttributes($path, $stat);
if ( ! $attributes instanceof FileAttributes) {
throw UnableToRetrieveMetadata::create($path, $type, 'path is not a file');
}
return $attributes;
}
public function mimeType(string $path): FileAttributes
{
try {
$contents = $this->read($path);
$mimetype = $this->mimeTypeDetector->detectMimeType($path, $contents);
} catch (Throwable $exception) {
throw UnableToRetrieveMetadata::mimeType($path, '', $exception);
}
if ($mimetype === null) {
throw UnableToRetrieveMetadata::mimeType($path, 'Unknown.');
}
return new FileAttributes($path, null, null, null, $mimetype);
}
public function lastModified(string $path): FileAttributes
{
return $this->fetchFileMetadata($path, FileAttributes::ATTRIBUTE_LAST_MODIFIED);
}
public function fileSize(string $path): FileAttributes
{
return $this->fetchFileMetadata($path, FileAttributes::ATTRIBUTE_FILE_SIZE);
}
public function visibility(string $path): FileAttributes
{
return $this->fetchFileMetadata($path, FileAttributes::ATTRIBUTE_VISIBILITY);
}
public function listContents(string $path, bool $deep): iterable
{
$connection = $this->connectionProvider->provideConnection();
$location = $this->prefixer->prefixPath(rtrim($path, '/')) . '/';
$listing = $connection->rawlist($location, false);
if ($listing === false) {
return;
}
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;
}
}
}
}
private function convertListingToAttributes(string $path, array $attributes): StorageAttributes
{
$permissions = $attributes['permissions'] & 0777;
$lastModified = $attributes['mtime'] ?? null;
if ($attributes['type'] === NET_SFTP_TYPE_DIRECTORY) {
return new DirectoryAttributes(
ltrim($path, '/'),
$this->visibilityConverter->inverseForDirectory($permissions),
$lastModified
);
}
return new FileAttributes(
$path,
$attributes['size'],
$this->visibilityConverter->inverseForFile($permissions),
$lastModified
);
}
public function move(string $source, string $destination, Config $config): void
{
$sourceLocation = $this->prefixer->prefixPath($source);
$destinationLocation = $this->prefixer->prefixPath($destination);
$connection = $this->connectionProvider->provideConnection();
try {
$this->ensureParentDirectoryExists($destination, $config);
} 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
{
try {
$readStream = $this->readStream($source);
$visibility = $this->visibility($source)->visibility();
$this->writeStream($destination, $readStream, new Config(compact('visibility')));
} catch (Throwable $exception) {
if (isset($readStream) && is_resource($readStream)) {
@fclose($readStream);
}
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}
}

View File

@ -0,0 +1,236 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use phpseclib\Crypt\RSA;
use phpseclib\Net\SFTP;
use phpseclib\System\SSH\Agent;
use Throwable;
class SftpConnectionProvider implements ConnectionProvider
{
/**
* @var string
*/
private $host;
/**
* @var string
*/
private $username;
/**
* @var string|null
*/
private $password;
/**
* @var bool
*/
private $useAgent;
/**
* @var int
*/
private $port;
/**
* @var int
*/
private $timeout;
/**
* @var SFTP|null
*/
private $connection;
/**
* @var ConnectivityChecker
*/
private $connectivityChecker;
/**
* @var string|null
*/
private $hostFingerprint;
/**
* @var string|null
*/
private $privateKey;
/**
* @var string|null
*/
private $passphrase;
/**
* @var int
*/
private $maxTries;
public function __construct(
string $host,
string $username,
string $password = null,
string $privateKey = null,
string $passphrase = null,
int $port = 22,
bool $useAgent = false,
int $timeout = 10,
int $maxTries = 4,
string $hostFingerprint = null,
ConnectivityChecker $connectivityChecker = null
) {
$this->host = $host;
$this->username = $username;
$this->password = $password;
$this->privateKey = $privateKey;
$this->passphrase = $passphrase;
$this->useAgent = $useAgent;
$this->port = $port;
$this->timeout = $timeout;
$this->hostFingerprint = $hostFingerprint;
$this->connectivityChecker = $connectivityChecker ?: new SimpleConnectivityChecker();
$this->maxTries = $maxTries;
}
public function provideConnection(): SFTP
{
$tries = 0;
start:
$connection = $this->connection instanceof SFTP
? $this->connection
: $this->setupConnection();
if ( ! $this->connectivityChecker->isConnected($connection)) {
$connection->disconnect();
$this->connection = null;
if ($tries < $this->maxTries) {
$tries++;
goto start;
}
throw UnableToConnectToSftpHost::atHostname($this->host);
}
return $this->connection = $connection;
}
private function setupConnection(): SFTP
{
$connection = new SFTP($this->host, $this->port, $this->timeout);
$connection->disableStatCache();
try {
$this->checkFingerprint($connection);
$this->authenticate($connection);
} catch (Throwable $exception) {
$connection->disconnect();
throw $exception;
}
return $connection;
}
private function checkFingerprint(SFTP $connection): void
{
if ( ! $this->hostFingerprint) {
return;
}
$publicKey = $connection->getServerPublicHostKey();
if ($publicKey === false) {
throw UnableToEstablishAuthenticityOfHost::becauseTheAuthenticityCantBeEstablished($this->host);
}
$fingerprint = $this->getFingerprintFromPublicKey($publicKey);
if (0 !== strcasecmp($this->hostFingerprint, $fingerprint)) {
throw UnableToEstablishAuthenticityOfHost::becauseTheAuthenticityCantBeEstablished($this->host);
}
}
private function getFingerprintFromPublicKey(string $publicKey): string
{
$content = explode(' ', $publicKey, 3);
return implode(':', str_split(md5(base64_decode($content[1])), 2));
}
private function authenticate(SFTP $connection): void
{
if ($this->privateKey !== null) {
$this->authenticateWithPrivateKey($connection);
} elseif ($this->useAgent) {
$this->authenticateWithAgent($connection);
} elseif ( ! $connection->login($this->username, $this->password)) {
throw UnableToAuthenticate::withPassword();
}
}
public static function fromArray(array $options): SftpConnectionProvider
{
return new SftpConnectionProvider(
$options['host'],
$options['username'],
$options['password'] ?? null,
$options['privateKey'] ?? null,
$options['passphrase'] ?? null,
$options['port'] ?? 22,
$options['useAgent'] ?? false,
$options['timeout'] ?? 10,
$options['maxTries'] ?? 4,
$options['hostFingerprint'] ?? null,
$options['connectivityChecker'] ?? null
);
}
private function authenticateWithPrivateKey(SFTP $connection): void
{
$privateKey = $this->loadPrivateKey();
if ($connection->login($this->username, $privateKey)) {
return;
}
if ($this->password !== null && $connection->login($this->username, $this->password)) {
return;
}
throw UnableToAuthenticate::withPrivateKey();
}
private function loadPrivateKey(): RSA
{
if ("---" !== substr($this->privateKey, 0, 3) && is_file($this->privateKey)) {
$this->privateKey = file_get_contents($this->privateKey);
}
$key = new RSA();
if ($this->passphrase !== null) {
$key->setPassword($this->passphrase);
}
if ( ! $key->loadKey($this->privateKey)) {
throw new UnableToLoadPrivateKey();
}
return $key;
}
private function authenticateWithAgent(SFTP $connection): void
{
$agent = new Agent();
if ( ! $connection->login($this->username, $agent)) {
throw UnableToAuthenticate::withSshAgent();
}
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use phpseclib\Net\SFTP;
class SimpleConnectivityChecker implements ConnectivityChecker
{
public function isConnected(SFTP $connection): bool
{
return $connection->isConnected();
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use phpseclib\Net\SFTP;
class StubSftpConnectionProvider implements ConnectionProvider
{
/**
* @var string
*/
private $host;
/**
* @var string
*/
private $username;
/**
* @var string|null
*/
private $password;
/**
* @var int
*/
private $port;
/**
* @var SftpStub
*/
private $connection;
public function __construct(
string $host,
string $username,
string $password = null,
int $port = 22
) {
$this->host = $host;
$this->username = $username;
$this->password = $password;
$this->port = $port;
}
public function provideConnection(): SFTP
{
if ( ! $this->connection instanceof SFTP) {
$connection = new SftpStub($this->host, $this->port);
$connection->login($this->username, $this->password);
$this->connection = $connection;
}
return $this->connection;
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use League\Flysystem\FilesystemException;
use RuntimeException;
class UnableToAuthenticate extends RuntimeException implements FilesystemException
{
public static function withPassword(): UnableToAuthenticate
{
return new UnableToAuthenticate('Unable to authenticate using a password.');
}
public static function withPrivateKey(): UnableToAuthenticate
{
return new UnableToAuthenticate('Unable to authenticate using a private key.');
}
public static function withSshAgent(): UnableToAuthenticate
{
return new UnableToAuthenticate('Unable to authenticate using an SSH agent.');
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use League\Flysystem\FilesystemException;
use RuntimeException;
class UnableToConnectToSftpHost extends RuntimeException implements FilesystemException
{
public static function atHostname(string $host): UnableToConnectToSftpHost
{
return new UnableToConnectToSftpHost("Unable to connect to host: $host");
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use League\Flysystem\FilesystemException;
use RuntimeException;
class UnableToEstablishAuthenticityOfHost extends RuntimeException implements FilesystemException
{
public static function becauseTheAuthenticityCantBeEstablished(string $host): UnableToEstablishAuthenticityOfHost
{
return new UnableToEstablishAuthenticityOfHost("The authenticity of host $host can't be established.");
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\PhpseclibV2;
use League\Flysystem\FilesystemException;
use RuntimeException;
class UnableToLoadPrivateKey extends RuntimeException implements FilesystemException
{
public function __construct(string $message = "Unable to load private key.")
{
parent::__construct($message);
}
}

View File

@ -0,0 +1,23 @@
{
"name": "league/flysystem-sftp",
"description": "SFTP filesystem adapter for Flysystem.",
"keywords": ["flysystem", "filesystem", "sftp", "files", "file"],
"autoload": {
"psr-4": {
"League\\Flysystem\\PhpseclibV2\\": ""
}
},
"require": {
"php": "^7.2 || ^8.0",
"league/flysystem": "^2.0.0",
"league/mime-type-detection": "^1.0.0",
"phpseclib/phpseclib": "^2.0"
},
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
]
}