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"
}
]
}

2
msd/vendor/league/flysystem/INFO.md vendored Normal file
View File

@ -0,0 +1,2 @@
View the docs at: https://flysystem.thephpleague.com/v2/
Changelog at: https://github.com/thephpleague/flysystem/blob/2.x/CHANGELOG.md

19
msd/vendor/league/flysystem/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2013-2022 Frank de Jonge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,48 @@
{
"name": "league/flysystem",
"description": "File storage abstraction for PHP",
"keywords": [
"filesystem", "filesystems", "files", "storage", "aws",
"s3", "ftp", "sftp", "webdav", "file", "cloud"
],
"scripts": {
"phpstan": "vendor/bin/phpstan analyse -l 6 src"
},
"type": "library",
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src"
}
},
"require": {
"php": "^7.2 || ^8.0",
"ext-json": "*",
"league/mime-type-detection": "^1.0.0"
},
"require-dev": {
"ext-fileinfo": "*",
"ext-ftp": "*",
"phpunit/phpunit": "^8.5 || ^9.4",
"phpstan/phpstan": "^0.12.26",
"phpseclib/phpseclib": "^2.0",
"aws/aws-sdk-php": "^3.132.4",
"composer/semver": "^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"google/cloud-storage": "^1.23",
"async-aws/s3": "^1.5",
"async-aws/simple-s3": "^1.0",
"sabre/dav": "^4.1"
},
"conflict": {
"guzzlehttp/ringphp": "<1.1.1"
},
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
]
}

View File

@ -0,0 +1,49 @@
{
"sub-splits": [
{
"name": "ftp",
"directory": "src/Ftp",
"target": "git@github.com:thephpleague/flysystem-ftp.git"
},
{
"name": "sftp",
"directory": "src/PhpseclibV2",
"target": "git@github.com:thephpleague/flysystem-sftp.git"
},
{
"name": "sftp-v3",
"directory": "src/PhpseclibV3",
"target": "git@github.com:thephpleague/flysystem-sftp-v3.git"
},
{
"name": "memory",
"directory": "src/InMemory",
"target": "git@github.com:thephpleague/flysystem-memory.git"
},
{
"name": "ziparchive",
"directory": "src/ZipArchive",
"target": "git@github.com:thephpleague/flysystem-ziparchive.git"
},
{
"name": "aws-s3-v3",
"directory": "src/AwsS3V3",
"target": "git@github.com:thephpleague/flysystem-aws-s3-v3.git"
},
{
"name": "async-aws-s3",
"directory": "src/AsyncAwsS3",
"target": "git@github.com:thephpleague/flysystem-async-aws-s3.git"
},
{
"name": "google-cloud-storage",
"directory": "src/GoogleCloudStorage",
"target": "git@github.com:thephpleague/flysystem-google-cloud-storage.git"
},
{
"name": "adapter-test-utilities",
"directory": "src/AdapterTestUtilities",
"target": "git@github.com:thephpleague/flysystem-adapter-test-utilities.git"
}
]
}

View File

@ -0,0 +1,58 @@
---
version: "3"
services:
webdav:
image: bytemark/webdav
restart: always
ports:
- "80:80"
environment:
AUTH_TYPE: Digest
USERNAME: alice
PASSWORD: secret1234
sftp:
container_name: sftp
restart: always
image: atmoz/sftp
volumes:
- ./test_files/sftp/users.conf:/etc/sftp/users.conf
- ./test_files/sftp/ssh_host_ed25519_key:/etc/ssh/ssh_host_ed25519_key
- ./test_files/sftp/ssh_host_rsa_key:/etc/ssh/ssh_host_rsa_key
- ./test_files/sftp/id_rsa.pub:/home/bar/.ssh/keys/id_rsa.pub
ports:
- "2222:22"
ftp:
container_name: ftp
restart: always
image: delfer/alpine-ftp-server
environment:
USERS: 'foo|pass|/home/foo/upload'
ADDRESS: 'localhost'
ports:
- "2121:21"
- "21000-21010:21000-21010"
ftpd:
container_name: ftpd
restart: always
environment:
PUBLICHOST: localhost
FTP_USER_NAME: foo
FTP_USER_PASS: pass
FTP_USER_HOME: /home/foo
image: stilliard/pure-ftpd
ports:
- "2122:21"
- "30000-30009:30000-30009"
command: "/run.sh -l puredb:/etc/pure-ftpd/pureftpd.pdb -E -j -P localhost"
toxiproxy:
container_name: toxiproxy
restart: unless-stopped
image: ghcr.io/shopify/toxiproxy
command: "-host 0.0.0.0 -config /opt/toxiproxy/config.json"
volumes:
- ./test_files/toxiproxy/toxiproxy.json:/opt/toxiproxy/config.json:ro
ports:
- "8474:8474" # HTTP API
- "8222:8222" # SFTP
- "8121:8121" # FTP
- "8122:8122" # FTPD

45
msd/vendor/league/flysystem/readme.md vendored Normal file
View File

@ -0,0 +1,45 @@
# League\Flysystem
[![Author](https://img.shields.io/badge/author-@frankdejonge-blue.svg)](https://twitter.com/frankdejonge)
[![Source Code](https://img.shields.io/badge/source-thephpleague/flysystem-blue.svg)](https://github.com/thephpleague/flysystem)
[![Latest Version](https://img.shields.io/github/tag/thephpleague/flysystem.svg)](https://github.com/thephpleague/flysystem/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/thephpleague/flysystem/blob/master/LICENSE)
[![Quality Assurance](https://github.com/thephpleague/flysystem/workflows/Quality%20Assurance/badge.svg?branch=2.x)](https://github.com/thephpleague/flysystem/actions?query=workflow%3A%22Quality+Assurance%22)
[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem.svg)](https://packagist.org/packages/league/flysystem)
![php 7.2+](https://img.shields.io/badge/php-min%207.2-red.svg)
## About Flysystem
Flysystem is a file storage library for PHP. It provides one interface to
interact with many types of filesystems. When you use Flysystem, you're
not only protected from vendor lock-in, you'll also have a consistent experience
for which ever storage is right for you.
## Getting Started
* **[New in V2](https://flysystem.thephpleague.com/v2/docs/what-is-new/)**: What it new in Flysystem V2?
* **[Architecture](https://flysystem.thephpleague.com/v2/docs/architecture/)**: Flysystem's internal architecture
* **[Flysystem API](https://flysystem.thephpleague.com/v2/docs/usage/filesystem-api/)**: How to interact with your Flysystem instance
* **[Upgrade to V2](https://flysystem.thephpleague.com/v2/docs/advanced/upgrade-to-2.0.0/)**: How to upgrade your Flysystem V1 instance to V2
### Commonly-Used Adapters
* **[AsyncAws S3](https://flysystem.thephpleague.com/v2/docs/adapter/async-aws-s3/)**
* **[AWS S3](https://flysystem.thephpleague.com/v2/docs/adapter/aws-s3-v3/)**
* **[Local](https://flysystem.thephpleague.com/v2/docs/adapter/local/)**
* **[Memory](https://flysystem.thephpleague.com/v2/docs/adapter/in-memory/)**
### Third party Adapters
* **[Gitlab](https://github.com/RoyVoetman/flysystem-gitlab-storage)**
* **[Google Drive (using regular paths)](https://github.com/masbug/flysystem-google-drive-ext)**
You can always [create an adapter](https://flysystem.thephpleague.com/v2/docs/advanced/creating-an-adapter/) yourself.
## Security
If you discover any security related issues, please email info@frankdejonge.nl instead of using the issue tracker.
## Enjoy
Oh, and if you've come down this far, you might as well follow me on [twitter](https://twitter.com/frankdejonge).

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use function array_merge;
class Config
{
public const OPTION_VISIBILITY = 'visibility';
public const OPTION_DIRECTORY_VISIBILITY = 'directory_visibility';
/**
* @var array
*/
private $options;
public function __construct(array $options = [])
{
$this->options = $options;
}
/**
* @param mixed $default
*
* @return mixed
*/
public function get(string $property, $default = null)
{
return $this->options[$property] ?? $default;
}
public function extend(array $options): Config
{
return new Config(array_merge($this->options, $options));
}
public function withDefaults(array $defaults): Config
{
return new Config($this->options + $defaults);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace League\Flysystem;
use RuntimeException;
final class CorruptedPathDetected extends RuntimeException implements FilesystemException
{
public static function forPath(string $path): CorruptedPathDetected
{
return new CorruptedPathDetected("Corrupted path detected: " . $path);
}
}

View File

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class DirectoryAttributes implements StorageAttributes
{
use ProxyArrayAccessToProperties;
/**
* @var string
*/
private $type = StorageAttributes::TYPE_DIRECTORY;
/**
* @var string
*/
private $path;
/**
* @var string|null
*/
private $visibility;
/**
* @var int|null
*/
private $lastModified;
/**
* @var array
*/
private $extraMetadata;
public function __construct(string $path, ?string $visibility = null, ?int $lastModified = null, array $extraMetadata = [])
{
$this->path = $path;
$this->visibility = $visibility;
$this->lastModified = $lastModified;
$this->extraMetadata = $extraMetadata;
}
public function path(): string
{
return $this->path;
}
public function type(): string
{
return StorageAttributes::TYPE_DIRECTORY;
}
public function visibility(): ?string
{
return $this->visibility;
}
public function lastModified(): ?int
{
return $this->lastModified;
}
public function extraMetadata(): array
{
return $this->extraMetadata;
}
public function isFile(): bool
{
return false;
}
public function isDir(): bool
{
return true;
}
public function withPath(string $path): StorageAttributes
{
$clone = clone $this;
$clone->path = $path;
return $clone;
}
public static function fromArray(array $attributes): StorageAttributes
{
return new DirectoryAttributes(
$attributes[StorageAttributes::ATTRIBUTE_PATH],
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
);
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
return [
StorageAttributes::ATTRIBUTE_TYPE => $this->type,
StorageAttributes::ATTRIBUTE_PATH => $this->path,
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
];
}
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use ArrayIterator;
use Generator;
use IteratorAggregate;
use Traversable;
/**
* @template T
*/
class DirectoryListing implements IteratorAggregate
{
/**
* @var iterable<T>
*/
private $listing;
/**
* @param iterable<T> $listing
*/
public function __construct(iterable $listing)
{
$this->listing = $listing;
}
public function filter(callable $filter): DirectoryListing
{
$generator = (static function (iterable $listing) use ($filter): Generator {
foreach ($listing as $item) {
if ($filter($item)) {
yield $item;
}
}
})($this->listing);
return new DirectoryListing($generator);
}
public function map(callable $mapper): DirectoryListing
{
$generator = (static function (iterable $listing) use ($mapper): Generator {
foreach ($listing as $item) {
yield $mapper($item);
}
})($this->listing);
return new DirectoryListing($generator);
}
public function sortByPath(): DirectoryListing
{
$listing = $this->toArray();
usort($listing, function (StorageAttributes $a, StorageAttributes $b) {
return $a->path() <=> $b->path();
});
return new DirectoryListing($listing);
}
/**
* @return Traversable<T>
*/
public function getIterator(): Traversable
{
return $this->listing instanceof Traversable
? $this->listing
: new ArrayIterator($this->listing);
}
/**
* @return T[]
*/
public function toArray(): array
{
return $this->listing instanceof Traversable
? iterator_to_array($this->listing, false)
: (array) $this->listing;
}
}

View File

@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class FileAttributes implements StorageAttributes
{
use ProxyArrayAccessToProperties;
/**
* @var string
*/
private $type = StorageAttributes::TYPE_FILE;
/**
* @var string
*/
private $path;
/**
* @var int|null
*/
private $fileSize;
/**
* @var string|null
*/
private $visibility;
/**
* @var int|null
*/
private $lastModified;
/**
* @var string|null
*/
private $mimeType;
/**
* @var array
*/
private $extraMetadata;
public function __construct(
string $path,
?int $fileSize = null,
?string $visibility = null,
?int $lastModified = null,
?string $mimeType = null,
array $extraMetadata = []
) {
$this->path = $path;
$this->fileSize = $fileSize;
$this->visibility = $visibility;
$this->lastModified = $lastModified;
$this->mimeType = $mimeType;
$this->extraMetadata = $extraMetadata;
}
public function type(): string
{
return $this->type;
}
public function path(): string
{
return $this->path;
}
public function fileSize(): ?int
{
return $this->fileSize;
}
public function visibility(): ?string
{
return $this->visibility;
}
public function lastModified(): ?int
{
return $this->lastModified;
}
public function mimeType(): ?string
{
return $this->mimeType;
}
public function extraMetadata(): array
{
return $this->extraMetadata;
}
public function isFile(): bool
{
return true;
}
public function isDir(): bool
{
return false;
}
public function withPath(string $path): StorageAttributes
{
$clone = clone $this;
$clone->path = $path;
return $clone;
}
public static function fromArray(array $attributes): StorageAttributes
{
return new FileAttributes(
$attributes[StorageAttributes::ATTRIBUTE_PATH],
$attributes[StorageAttributes::ATTRIBUTE_FILE_SIZE] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_MIME_TYPE] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
);
}
public function jsonSerialize(): array
{
return [
StorageAttributes::ATTRIBUTE_TYPE => self::TYPE_FILE,
StorageAttributes::ATTRIBUTE_PATH => $this->path,
StorageAttributes::ATTRIBUTE_FILE_SIZE => $this->fileSize,
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
StorageAttributes::ATTRIBUTE_MIME_TYPE => $this->mimeType,
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
];
}
}

View File

@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class Filesystem implements FilesystemOperator
{
/**
* @var FilesystemAdapter
*/
private $adapter;
/**
* @var Config
*/
private $config;
/**
* @var PathNormalizer
*/
private $pathNormalizer;
public function __construct(
FilesystemAdapter $adapter,
array $config = [],
PathNormalizer $pathNormalizer = null
) {
$this->adapter = $adapter;
$this->config = new Config($config);
$this->pathNormalizer = $pathNormalizer ?: new WhitespacePathNormalizer();
}
public function fileExists(string $location): bool
{
return $this->adapter->fileExists($this->pathNormalizer->normalizePath($location));
}
public function write(string $location, string $contents, array $config = []): void
{
$this->adapter->write(
$this->pathNormalizer->normalizePath($location),
$contents,
$this->config->extend($config)
);
}
public function writeStream(string $location, $contents, array $config = []): void
{
/* @var resource $contents */
$this->assertIsResource($contents);
$this->rewindStream($contents);
$this->adapter->writeStream(
$this->pathNormalizer->normalizePath($location),
$contents,
$this->config->extend($config)
);
}
public function read(string $location): string
{
return $this->adapter->read($this->pathNormalizer->normalizePath($location));
}
public function readStream(string $location)
{
return $this->adapter->readStream($this->pathNormalizer->normalizePath($location));
}
public function delete(string $location): void
{
$this->adapter->delete($this->pathNormalizer->normalizePath($location));
}
public function deleteDirectory(string $location): void
{
$this->adapter->deleteDirectory($this->pathNormalizer->normalizePath($location));
}
public function createDirectory(string $location, array $config = []): void
{
$this->adapter->createDirectory(
$this->pathNormalizer->normalizePath($location),
$this->config->extend($config)
);
}
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
{
$path = $this->pathNormalizer->normalizePath($location);
return new DirectoryListing($this->adapter->listContents($path, $deep));
}
public function move(string $source, string $destination, array $config = []): void
{
$this->adapter->move(
$this->pathNormalizer->normalizePath($source),
$this->pathNormalizer->normalizePath($destination),
$this->config->extend($config)
);
}
public function copy(string $source, string $destination, array $config = []): void
{
$this->adapter->copy(
$this->pathNormalizer->normalizePath($source),
$this->pathNormalizer->normalizePath($destination),
$this->config->extend($config)
);
}
public function lastModified(string $path): int
{
return $this->adapter->lastModified($this->pathNormalizer->normalizePath($path))->lastModified();
}
public function fileSize(string $path): int
{
return $this->adapter->fileSize($this->pathNormalizer->normalizePath($path))->fileSize();
}
public function mimeType(string $path): string
{
return $this->adapter->mimeType($this->pathNormalizer->normalizePath($path))->mimeType();
}
public function setVisibility(string $path, string $visibility): void
{
$this->adapter->setVisibility($this->pathNormalizer->normalizePath($path), $visibility);
}
public function visibility(string $path): string
{
return $this->adapter->visibility($this->pathNormalizer->normalizePath($path))->visibility();
}
/**
* @param mixed $contents
*/
private function assertIsResource($contents): void
{
if (is_resource($contents) === false) {
throw new InvalidStreamProvided(
"Invalid stream provided, expected stream resource, received " . gettype($contents)
);
} elseif ($type = get_resource_type($contents) !== 'stream') {
throw new InvalidStreamProvided(
"Invalid stream provided, expected stream resource, received resource of type " . $type
);
}
}
/**
* @param resource $resource
*/
private function rewindStream($resource): void
{
if (ftell($resource) !== 0 && stream_get_meta_data($resource)['seekable']) {
rewind($resource);
}
}
}

View File

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemAdapter
{
/**
* @throws FilesystemException
*/
public function fileExists(string $path): bool;
/**
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function write(string $path, string $contents, Config $config): void;
/**
* @param resource $contents
*
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function writeStream(string $path, $contents, Config $config): void;
/**
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function read(string $path): string;
/**
* @return resource
*
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function readStream(string $path);
/**
* @throws UnableToDeleteFile
* @throws FilesystemException
*/
public function delete(string $path): void;
/**
* @throws UnableToDeleteDirectory
* @throws FilesystemException
*/
public function deleteDirectory(string $path): void;
/**
* @throws UnableToCreateDirectory
* @throws FilesystemException
*/
public function createDirectory(string $path, Config $config): void;
/**
* @throws InvalidVisibilityProvided
* @throws FilesystemException
*/
public function setVisibility(string $path, string $visibility): void;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function visibility(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function mimeType(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function lastModified(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function fileSize(string $path): FileAttributes;
/**
* @return iterable<StorageAttributes>
*
* @throws FilesystemException
*/
public function listContents(string $path, bool $deep): iterable;
/**
* @throws UnableToMoveFile
* @throws FilesystemException
*/
public function move(string $source, string $destination, Config $config): void;
/**
* @throws UnableToCopyFile
* @throws FilesystemException
*/
public function copy(string $source, string $destination, Config $config): void;
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use Throwable;
interface FilesystemException extends Throwable
{
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemOperationFailed extends FilesystemException
{
public const OPERATION_WRITE = 'WRITE';
public const OPERATION_UPDATE = 'UPDATE';
public const OPERATION_FILE_EXISTS = 'FILE_EXISTS';
public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY';
public const OPERATION_DELETE = 'DELETE';
public const OPERATION_DELETE_DIRECTORY = 'DELETE_DIRECTORY';
public const OPERATION_MOVE = 'MOVE';
public const OPERATION_RETRIEVE_METADATA = 'RETRIEVE_METADATA';
public const OPERATION_COPY = 'COPY';
public const OPERATION_READ = 'READ';
public const OPERATION_SET_VISIBILITY = 'SET_VISIBILITY';
public function operation(): string;
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemOperator extends FilesystemReader, FilesystemWriter
{
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
/**
* This interface contains everything to read from and inspect
* a filesystem. All methods containing are non-destructive.
*/
interface FilesystemReader
{
public const LIST_SHALLOW = false;
public const LIST_DEEP = true;
/**
* @throws FilesystemException
* @throws UnableToCheckFileExistence
*/
public function fileExists(string $location): bool;
/**
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function read(string $location): string;
/**
* @return resource
*
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function readStream(string $location);
/**
* @return DirectoryListing<StorageAttributes>
*
* @throws FilesystemException
*/
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function lastModified(string $path): int;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function fileSize(string $path): int;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function mimeType(string $path): string;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function visibility(string $path): string;
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemWriter
{
/**
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function write(string $location, string $contents, array $config = []): void;
/**
* @param mixed $contents
*
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function writeStream(string $location, $contents, array $config = []): void;
/**
* @throws UnableToSetVisibility
* @throws FilesystemException
*/
public function setVisibility(string $path, string $visibility): void;
/**
* @throws UnableToDeleteFile
* @throws FilesystemException
*/
public function delete(string $location): void;
/**
* @throws UnableToDeleteDirectory
* @throws FilesystemException
*/
public function deleteDirectory(string $location): void;
/**
* @throws UnableToCreateDirectory
* @throws FilesystemException
*/
public function createDirectory(string $location, array $config = []): void;
/**
* @throws UnableToMoveFile
* @throws FilesystemException
*/
public function move(string $source, string $destination, array $config = []): void;
/**
* @throws UnableToCopyFile
* @throws FilesystemException
*/
public function copy(string $source, string $destination, array $config = []): void;
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException as BaseInvalidArgumentException;
class InvalidStreamProvided extends BaseInvalidArgumentException implements FilesystemException
{
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException;
use function var_export;
class InvalidVisibilityProvided extends InvalidArgumentException implements FilesystemException
{
public static function withVisibility(string $visibility, string $expectedMessage): InvalidVisibilityProvided
{
$provided = var_export($visibility, true);
$message = "Invalid visibility provided. Expected {$expectedMessage}, received {$provided}";
throw new InvalidVisibilityProvided($message);
}
}

View File

@ -0,0 +1,419 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\Local;
use function file_put_contents;
use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
use DirectoryIterator;
use FilesystemIterator;
use Generator;
use League\Flysystem\Config;
use League\Flysystem\DirectoryAttributes;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\PathPrefixer;
use League\Flysystem\SymbolicLinkEncountered;
use League\Flysystem\UnableToCopyFile;
use League\Flysystem\UnableToCreateDirectory;
use League\Flysystem\UnableToDeleteDirectory;
use League\Flysystem\UnableToDeleteFile;
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 RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use function chmod;
use function clearstatcache;
use function dirname;
use function error_clear_last;
use function error_get_last;
use function file_exists;
use function is_dir;
use function is_file;
use function mkdir;
use function rename;
use function stream_copy_to_stream;
class LocalFilesystemAdapter implements FilesystemAdapter
{
/**
* @var int
*/
public const SKIP_LINKS = 0001;
/**
* @var int
*/
public const DISALLOW_LINKS = 0002;
/**
* @var PathPrefixer
*/
private $prefixer;
/**
* @var int
*/
private $writeFlags;
/**
* @var int
*/
private $linkHandling;
/**
* @var VisibilityConverter
*/
private $visibility;
/**
* @var MimeTypeDetector
*/
private $mimeTypeDetector;
public function __construct(
string $location,
VisibilityConverter $visibility = null,
int $writeFlags = LOCK_EX,
int $linkHandling = self::DISALLOW_LINKS,
MimeTypeDetector $mimeTypeDetector = null
) {
$this->prefixer = new PathPrefixer($location, DIRECTORY_SEPARATOR);
$this->writeFlags = $writeFlags;
$this->linkHandling = $linkHandling;
$this->visibility = $visibility ?: new PortableVisibilityConverter();
$this->ensureDirectoryExists($location, $this->visibility->defaultForDirectories());
$this->mimeTypeDetector = $mimeTypeDetector ?: new FinfoMimeTypeDetector();
}
public function write(string $path, string $contents, Config $config): void
{
$this->writeToFile($path, $contents, $config);
}
public function writeStream(string $path, $contents, Config $config): void
{
$this->writeToFile($path, $contents, $config);
}
/**
* @param resource|string $contents
*/
private function writeToFile(string $path, $contents, Config $config): void
{
$prefixedLocation = $this->prefixer->prefixPath($path);
$this->ensureDirectoryExists(
dirname($prefixedLocation),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
error_clear_last();
if (@file_put_contents($prefixedLocation, $contents, $this->writeFlags) === false) {
throw UnableToWriteFile::atLocation($path, error_get_last()['message'] ?? '');
}
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($path, (string) $visibility);
}
}
public function delete(string $path): void
{
$location = $this->prefixer->prefixPath($path);
if ( ! file_exists($location)) {
return;
}
error_clear_last();
if ( ! @unlink($location)) {
throw UnableToDeleteFile::atLocation($location, error_get_last()['message'] ?? '');
}
}
public function deleteDirectory(string $prefix): void
{
$location = $this->prefixer->prefixPath($prefix);
if ( ! is_dir($location)) {
return;
}
$contents = $this->listDirectoryRecursively($location, RecursiveIteratorIterator::CHILD_FIRST);
/** @var SplFileInfo $file */
foreach ($contents as $file) {
if ( ! $this->deleteFileInfoObject($file)) {
throw UnableToDeleteDirectory::atLocation($prefix, "Unable to delete file at " . $file->getPathname());
}
}
unset($contents);
if ( ! @rmdir($location)) {
throw UnableToDeleteDirectory::atLocation($prefix, error_get_last()['message'] ?? '');
}
}
private function listDirectoryRecursively(
string $path,
int $mode = RecursiveIteratorIterator::SELF_FIRST
): Generator {
yield from new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
$mode
);
}
protected function deleteFileInfoObject(SplFileInfo $file): bool
{
switch ($file->getType()) {
case 'dir':
return @rmdir((string) $file->getRealPath());
case 'link':
return @unlink((string) $file->getPathname());
default:
return @unlink((string) $file->getRealPath());
}
}
public function listContents(string $path, bool $deep): iterable
{
$location = $this->prefixer->prefixPath($path);
if ( ! is_dir($location)) {
return;
}
/** @var SplFileInfo[] $iterator */
$iterator = $deep ? $this->listDirectoryRecursively($location) : $this->listDirectory($location);
foreach ($iterator as $fileInfo) {
if ($fileInfo->isLink()) {
if ($this->linkHandling & self::SKIP_LINKS) {
continue;
}
throw SymbolicLinkEncountered::atLocation($fileInfo->getPathname());
}
$path = $this->prefixer->stripPrefix($fileInfo->getPathname());
$lastModified = $fileInfo->getMTime();
$isDirectory = $fileInfo->isDir();
$permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4));
$visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions);
yield $isDirectory ? new DirectoryAttributes($path, $visibility, $lastModified) : new FileAttributes(
str_replace('\\', '/', $path),
$fileInfo->getSize(),
$visibility,
$lastModified
);
}
}
public function move(string $source, string $destination, Config $config): void
{
$sourcePath = $this->prefixer->prefixPath($source);
$destinationPath = $this->prefixer->prefixPath($destination);
$this->ensureDirectoryExists(
dirname($destinationPath),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
if ( ! @rename($sourcePath, $destinationPath)) {
throw UnableToMoveFile::fromLocationTo($sourcePath, $destinationPath);
}
}
public function copy(string $source, string $destination, Config $config): void
{
$sourcePath = $this->prefixer->prefixPath($source);
$destinationPath = $this->prefixer->prefixPath($destination);
$this->ensureDirectoryExists(
dirname($destinationPath),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
if ( ! @copy($sourcePath, $destinationPath)) {
throw UnableToCopyFile::fromLocationTo($sourcePath, $destinationPath);
}
}
public function read(string $path): string
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$contents = @file_get_contents($location);
if ($contents === false) {
throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
}
return $contents;
}
public function readStream(string $path)
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$contents = @fopen($location, 'rb');
if ($contents === false) {
throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
}
return $contents;
}
protected function ensureDirectoryExists(string $dirname, int $visibility): void
{
if (is_dir($dirname)) {
return;
}
error_clear_last();
if ( ! @mkdir($dirname, $visibility, true)) {
$mkdirError = error_get_last();
}
clearstatcache(true, $dirname);
if ( ! is_dir($dirname)) {
$errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
throw UnableToCreateDirectory::atLocation($dirname, $errorMessage);
}
}
public function fileExists(string $location): bool
{
$location = $this->prefixer->prefixPath($location);
return is_file($location);
}
public function createDirectory(string $path, Config $config): void
{
$location = $this->prefixer->prefixPath($path);
$visibility = $config->get(Config::OPTION_VISIBILITY, $config->get(Config::OPTION_DIRECTORY_VISIBILITY));
$permissions = $this->resolveDirectoryVisibility($visibility);
if (is_dir($location)) {
$this->setPermissions($location, $permissions);
return;
}
error_clear_last();
if ( ! @mkdir($location, $permissions, true)) {
throw UnableToCreateDirectory::atLocation($path, error_get_last()['message'] ?? '');
}
}
public function setVisibility(string $path, string $visibility): void
{
$path = $this->prefixer->prefixPath($path);
$visibility = is_dir($path) ? $this->visibility->forDirectory($visibility) : $this->visibility->forFile(
$visibility
);
$this->setPermissions($path, $visibility);
}
public function visibility(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
clearstatcache(false, $location);
error_clear_last();
$fileperms = @fileperms($location);
if ($fileperms === false) {
throw UnableToRetrieveMetadata::visibility($path, error_get_last()['message'] ?? '');
}
$permissions = $fileperms & 0777;
$visibility = $this->visibility->inverseForFile($permissions);
return new FileAttributes($path, null, $visibility);
}
private function resolveDirectoryVisibility(?string $visibility): int
{
return $visibility === null ? $this->visibility->defaultForDirectories() : $this->visibility->forDirectory(
$visibility
);
}
public function mimeType(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$mimeType = $this->mimeTypeDetector->detectMimeTypeFromFile($location);
if ($mimeType === null) {
throw UnableToRetrieveMetadata::mimeType($path, error_get_last()['message'] ?? '');
}
return new FileAttributes($path, null, null, null, $mimeType);
}
public function lastModified(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$lastModified = @filemtime($location);
if ($lastModified === false) {
throw UnableToRetrieveMetadata::lastModified($path, error_get_last()['message'] ?? '');
}
return new FileAttributes($path, null, null, $lastModified);
}
public function fileSize(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
if (is_file($location) && ($fileSize = @filesize($location)) !== false) {
return new FileAttributes($path, $fileSize);
}
throw UnableToRetrieveMetadata::fileSize($path, error_get_last()['message'] ?? '');
}
private function listDirectory(string $location): Generator
{
$iterator = new DirectoryIterator($location);
foreach ($iterator as $item) {
if ($item->isDot()) {
continue;
}
yield $item;
}
}
private function setPermissions(string $location, int $visibility): void
{
error_clear_last();
if ( ! @chmod($location, $visibility)) {
$extraMessage = error_get_last()['message'] ?? '';
throw UnableToSetVisibility::atLocation($this->prefixer->stripPrefix($location), $extraMessage);
}
}
}

View File

@ -0,0 +1,334 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use function sprintf;
class MountManager implements FilesystemOperator
{
/**
* @var array<string, FilesystemOperator>
*/
private $filesystems = [];
/**
* MountManager constructor.
*
* @param array<string,FilesystemOperator> $filesystems
*/
public function __construct(array $filesystems = [])
{
$this->mountFilesystems($filesystems);
}
public function fileExists(string $location): bool
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->fileExists($path);
} catch (UnableToCheckFileExistence $exception) {
throw UnableToCheckFileExistence::forLocation($location, $exception);
}
}
public function read(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->read($path);
} catch (UnableToReadFile $exception) {
throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
}
}
public function readStream(string $location)
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->readStream($path);
} catch (UnableToReadFile $exception) {
throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
}
}
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path, $mountIdentifier] = $this->determineFilesystemAndPath($location);
return
$filesystem
->listContents($path, $deep)
->map(
function (StorageAttributes $attributes) use ($mountIdentifier) {
return $attributes->withPath(sprintf('%s://%s', $mountIdentifier, $attributes->path()));
}
);
}
public function lastModified(string $location): int
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->lastModified($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::lastModified($location, $exception->reason(), $exception);
}
}
public function fileSize(string $location): int
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->fileSize($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::fileSize($location, $exception->reason(), $exception);
}
}
public function mimeType(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->mimeType($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::mimeType($location, $exception->reason(), $exception);
}
}
public function visibility(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->visibility($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::visibility($location, $exception->reason(), $exception);
}
}
public function write(string $location, string $contents, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->write($path, $contents, $config);
} catch (UnableToWriteFile $exception) {
throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception);
}
}
public function writeStream(string $location, $contents, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
$filesystem->writeStream($path, $contents, $config);
}
public function setVisibility(string $path, string $visibility): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($path);
$filesystem->setVisibility($path, $visibility);
}
public function delete(string $location): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->delete($path);
} catch (UnableToDeleteFile $exception) {
throw UnableToDeleteFile::atLocation($location, '', $exception);
}
}
public function deleteDirectory(string $location): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->deleteDirectory($path);
} catch (UnableToDeleteDirectory $exception) {
throw UnableToDeleteDirectory::atLocation($location, '', $exception);
}
}
public function createDirectory(string $location, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->createDirectory($path, $config);
} catch (UnableToCreateDirectory $exception) {
throw UnableToCreateDirectory::dueToFailure($location, $exception);
}
}
public function move(string $source, string $destination, array $config = []): void
{
/** @var FilesystemOperator $sourceFilesystem */
/* @var FilesystemOperator $destinationFilesystem */
[$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
[$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
$sourceFilesystem === $destinationFilesystem ? $this->moveInTheSameFilesystem(
$sourceFilesystem,
$sourcePath,
$destinationPath,
$source,
$destination
) : $this->moveAcrossFilesystems($source, $destination);
}
public function copy(string $source, string $destination, array $config = []): void
{
/** @var FilesystemOperator $sourceFilesystem */
/* @var FilesystemOperator $destinationFilesystem */
[$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
[$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
$sourceFilesystem === $destinationFilesystem ? $this->copyInSameFilesystem(
$sourceFilesystem,
$sourcePath,
$destinationPath,
$source,
$destination
) : $this->copyAcrossFilesystem(
$config['visibility'] ?? null,
$sourceFilesystem,
$sourcePath,
$destinationFilesystem,
$destinationPath,
$source,
$destination
);
}
private function mountFilesystems(array $filesystems): void
{
foreach ($filesystems as $key => $filesystem) {
$this->guardAgainstInvalidMount($key, $filesystem);
/* @var string $key */
/* @var FilesystemOperator $filesystem */
$this->mountFilesystem($key, $filesystem);
}
}
/**
* @param mixed $key
* @param mixed $filesystem
*/
private function guardAgainstInvalidMount($key, $filesystem): void
{
if ( ! is_string($key)) {
throw UnableToMountFilesystem::becauseTheKeyIsNotValid($key);
}
if ( ! $filesystem instanceof FilesystemOperator) {
throw UnableToMountFilesystem::becauseTheFilesystemWasNotValid($filesystem);
}
}
private function mountFilesystem(string $key, FilesystemOperator $filesystem): void
{
$this->filesystems[$key] = $filesystem;
}
/**
* @param string $path
*
* @return array{0:FilesystemOperator, 1:string}
*/
private function determineFilesystemAndPath(string $path): array
{
if (strpos($path, '://') < 1) {
throw UnableToResolveFilesystemMount::becauseTheSeparatorIsMissing($path);
}
/** @var string $mountIdentifier */
/** @var string $mountPath */
[$mountIdentifier, $mountPath] = explode('://', $path, 2);
if ( ! array_key_exists($mountIdentifier, $this->filesystems)) {
throw UnableToResolveFilesystemMount::becauseTheMountWasNotRegistered($mountIdentifier);
}
return [$this->filesystems[$mountIdentifier], $mountPath, $mountIdentifier];
}
private function copyInSameFilesystem(
FilesystemOperator $sourceFilesystem,
string $sourcePath,
string $destinationPath,
string $source,
string $destination
): void {
try {
$sourceFilesystem->copy($sourcePath, $destinationPath);
} catch (UnableToCopyFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}
private function copyAcrossFilesystem(
?string $visibility,
FilesystemOperator $sourceFilesystem,
string $sourcePath,
FilesystemOperator $destinationFilesystem,
string $destinationPath,
string $source,
string $destination
): void {
try {
$visibility = $visibility ?? $sourceFilesystem->visibility($sourcePath);
$stream = $sourceFilesystem->readStream($sourcePath);
$destinationFilesystem->writeStream($destinationPath, $stream, compact('visibility'));
} catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}
private function moveInTheSameFilesystem(
FilesystemOperator $sourceFilesystem,
string $sourcePath,
string $destinationPath,
string $source,
string $destination
): void {
try {
$sourceFilesystem->move($sourcePath, $destinationPath);
} catch (UnableToMoveFile $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
}
private function moveAcrossFilesystems(string $source, string $destination): void
{
try {
$this->copy($source, $destination);
$this->delete($source);
} catch (UnableToCopyFile | UnableToDeleteFile $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface PathNormalizer
{
public function normalizePath(string $path): string;
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use function rtrim;
use function strlen;
use function substr;
final class PathPrefixer
{
/**
* @var string
*/
private $prefix = '';
/**
* @var string
*/
private $separator = '/';
public function __construct(string $prefix, string $separator = '/')
{
$this->prefix = rtrim($prefix, '\\/');
if ($this->prefix !== '' || $prefix === $separator) {
$this->prefix .= $separator;
}
$this->separator = $separator;
}
public function prefixPath(string $path): string
{
return $this->prefix . ltrim($path, '\\/');
}
public function stripPrefix(string $path): string
{
/* @var string */
return substr($path, strlen($this->prefix));
}
public function stripDirectoryPrefix(string $path): string
{
return rtrim($this->stripPrefix($path), '\\/');
}
public function prefixDirectoryPath(string $path): string
{
$prefixedPath = $this->prefixPath(rtrim($path, '\\/'));
if ((substr($prefixedPath, -1) === $this->separator) || $prefixedPath === '') {
return $prefixedPath;
}
return $prefixedPath . $this->separator;
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
class PathTraversalDetected extends RuntimeException implements FilesystemException
{
/**
* @var string
*/
private $path;
public function path(): string
{
return $this->path;
}
public static function forPath(string $path): PathTraversalDetected
{
$e = new PathTraversalDetected("Path traversal detected: {$path}");
$e->path = $path;
return $e;
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
final class PortableVisibilityGuard
{
public static function guardAgainstInvalidInput(string $visibility): void
{
if ($visibility !== Visibility::PUBLIC && $visibility !== Visibility::PRIVATE) {
$className = Visibility::class;
throw InvalidVisibilityProvided::withVisibility(
$visibility,
"either {$className}::PUBLIC or {$className}::PRIVATE"
);
}
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
/**
* @internal
*/
trait ProxyArrayAccessToProperties
{
private function formatPropertyName(string $offset): string
{
return str_replace('_', '', lcfirst(ucwords($offset, '_')));
}
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset): bool
{
$property = $this->formatPropertyName((string) $offset);
return isset($this->{$property});
}
/**
* @param mixed $offset
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
$property = $this->formatPropertyName((string) $offset);
return $this->{$property};
}
/**
* @param mixed $offset
* @param mixed $value
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value): void
{
throw new RuntimeException('Properties can not be manipulated');
}
/**
* @param mixed $offset
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset): void
{
throw new RuntimeException('Properties can not be manipulated');
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use ArrayAccess;
use JsonSerializable;
interface StorageAttributes extends JsonSerializable, ArrayAccess
{
public const ATTRIBUTE_PATH = 'path';
public const ATTRIBUTE_TYPE = 'type';
public const ATTRIBUTE_FILE_SIZE = 'file_size';
public const ATTRIBUTE_VISIBILITY = 'visibility';
public const ATTRIBUTE_LAST_MODIFIED = 'last_modified';
public const ATTRIBUTE_MIME_TYPE = 'mime_type';
public const ATTRIBUTE_EXTRA_METADATA = 'extra_metadata';
public const TYPE_FILE = 'file';
public const TYPE_DIRECTORY = 'dir';
public function path(): string;
public function type(): string;
public function visibility(): ?string;
public function lastModified(): ?int;
public static function fromArray(array $attributes): StorageAttributes;
public function isFile(): bool;
public function isDir(): bool;
public function withPath(string $path): StorageAttributes;
public function extraMetadata(): array;
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
final class SymbolicLinkEncountered extends RuntimeException implements FilesystemException
{
/**
* @var string
*/
private $location;
public function location(): string
{
return $this->location;
}
public static function atLocation(string $pathName): SymbolicLinkEncountered
{
$e = new static("Unsupported symbolic link encountered at location $pathName");
$e->location = $pathName;
return $e;
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
class UnableToCheckFileExistence extends RuntimeException implements FilesystemOperationFailed
{
public static function forLocation(string $path, Throwable $exception = null): UnableToCheckFileExistence
{
return new UnableToCheckFileExistence("Unable to check file existence for: ${path}", 0, $exception);
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_FILE_EXISTS;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToCopyFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $destination;
public function source(): string
{
return $this->source;
}
public function destination(): string
{
return $this->destination;
}
public static function fromLocationTo(
string $sourcePath,
string $destinationPath,
Throwable $previous = null
): UnableToCopyFile {
$e = new static("Unable to copy file from $sourcePath to $destinationPath", 0 , $previous);
$e->source = $sourcePath;
$e->destination = $destinationPath;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_COPY;
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToCreateDirectory extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location;
public static function atLocation(string $dirname, string $errorMessage = ''): UnableToCreateDirectory
{
$message = "Unable to create a directory at {$dirname}. ${errorMessage}";
$e = new static(rtrim($message));
$e->location = $dirname;
return $e;
}
public static function dueToFailure(string $dirname, Throwable $previous): UnableToCreateDirectory
{
$message = "Unable to create a directory at {$dirname}";
$e = new static($message, 0, $previous);
$e->location = $dirname;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_CREATE_DIRECTORY;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToDeleteDirectory extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason;
public static function atLocation(
string $location,
string $reason = '',
Throwable $previous = null
): UnableToDeleteDirectory {
$e = new static(rtrim("Unable to delete directory located at: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_DELETE_DIRECTORY;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToDeleteFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason;
public static function atLocation(string $location, string $reason = '', Throwable $previous = null): UnableToDeleteFile
{
$e = new static(rtrim("Unable to delete file located at: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_DELETE;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use LogicException;
class UnableToMountFilesystem extends LogicException implements FilesystemException
{
/**
* @param mixed $key
*/
public static function becauseTheKeyIsNotValid($key): UnableToMountFilesystem
{
return new UnableToMountFilesystem(
'Unable to mount filesystem, key was invalid. String expected, received: ' . gettype($key)
);
}
/**
* @param mixed $filesystem
*/
public static function becauseTheFilesystemWasNotValid($filesystem): UnableToMountFilesystem
{
$received = is_object($filesystem) ? get_class($filesystem) : gettype($filesystem);
return new UnableToMountFilesystem(
'Unable to mount filesystem, filesystem was invalid. Instance of ' . FilesystemOperator::class . ' expected, received: ' . $received
);
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToMoveFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $destination;
public function source(): string
{
return $this->source;
}
public function destination(): string
{
return $this->destination;
}
public static function fromLocationTo(
string $sourcePath,
string $destinationPath,
Throwable $previous = null
): UnableToMoveFile {
$e = new static("Unable to move file from $sourcePath to $destinationPath", 0, $previous);
$e->source = $sourcePath;
$e->destination = $destinationPath;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_MOVE;
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToReadFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason = '';
public static function fromLocation(string $location, string $reason = '', Throwable $previous = null): UnableToReadFile
{
$e = new static(rtrim("Unable to read file from location: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_READ;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
class UnableToResolveFilesystemMount extends RuntimeException implements FilesystemException
{
public static function becauseTheSeparatorIsMissing(string $path): UnableToResolveFilesystemMount
{
return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the path ($path) is missing a separator (://).");
}
public static function becauseTheMountWasNotRegistered(string $mountIdentifier): UnableToResolveFilesystemMount
{
return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the mount ($mountIdentifier) was not registered.");
}
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToRetrieveMetadata extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location;
/**
* @var string
*/
private $metadataType;
/**
* @var string
*/
private $reason;
public static function lastModified(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_LAST_MODIFIED, $reason, $previous);
}
public static function visibility(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_VISIBILITY, $reason, $previous);
}
public static function fileSize(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_FILE_SIZE, $reason, $previous);
}
public static function mimeType(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_MIME_TYPE, $reason, $previous);
}
public static function create(string $location, string $type, string $reason = '', Throwable $previous = null): self
{
$e = new static("Unable to retrieve the $type for file at location: $location. {$reason}", 0, $previous);
$e->reason = $reason;
$e->location = $location;
$e->metadataType = $type;
return $e;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
public function metadataType(): string
{
return $this->metadataType;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_RETRIEVE_METADATA;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
use function rtrim;
final class UnableToSetVisibility extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location;
/**
* @var string
*/
private $reason;
public function reason(): string
{
return $this->reason;
}
public static function atLocation(string $filename, string $extraMessage = '', Throwable $previous = null): self
{
$message = "Unable to set visibility for file {$filename}. $extraMessage";
$e = new static(rtrim($message), 0, $previous);
$e->reason = $extraMessage;
$e->location = $filename;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_SET_VISIBILITY;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToWriteFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason;
public static function atLocation(string $location, string $reason = '', Throwable $previous = null): UnableToWriteFile
{
$e = new static(rtrim("Unable to write file at location: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_WRITE;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\UnixVisibility;
use League\Flysystem\PortableVisibilityGuard;
use League\Flysystem\Visibility;
class PortableVisibilityConverter implements VisibilityConverter
{
/**
* @var int
*/
private $filePublic;
/**
* @var int
*/
private $filePrivate;
/**
* @var int
*/
private $directoryPublic;
/**
* @var int
*/
private $directoryPrivate;
/**
* @var string
*/
private $defaultForDirectories;
public function __construct(
int $filePublic = 0644,
int $filePrivate = 0600,
int $directoryPublic = 0755,
int $directoryPrivate = 0700,
string $defaultForDirectories = Visibility::PRIVATE
) {
$this->filePublic = $filePublic;
$this->filePrivate = $filePrivate;
$this->directoryPublic = $directoryPublic;
$this->directoryPrivate = $directoryPrivate;
$this->defaultForDirectories = $defaultForDirectories;
}
public function forFile(string $visibility): int
{
PortableVisibilityGuard::guardAgainstInvalidInput($visibility);
return $visibility === Visibility::PUBLIC
? $this->filePublic
: $this->filePrivate;
}
public function forDirectory(string $visibility): int
{
PortableVisibilityGuard::guardAgainstInvalidInput($visibility);
return $visibility === Visibility::PUBLIC
? $this->directoryPublic
: $this->directoryPrivate;
}
public function inverseForFile(int $visibility): string
{
if ($visibility === $this->filePublic) {
return Visibility::PUBLIC;
} elseif ($visibility === $this->filePrivate) {
return Visibility::PRIVATE;
}
return Visibility::PUBLIC; // default
}
public function inverseForDirectory(int $visibility): string
{
if ($visibility === $this->directoryPublic) {
return Visibility::PUBLIC;
} elseif ($visibility === $this->directoryPrivate) {
return Visibility::PRIVATE;
}
return Visibility::PUBLIC; // default
}
public function defaultForDirectories(): int
{
return $this->defaultForDirectories === Visibility::PUBLIC ? $this->directoryPublic : $this->directoryPrivate;
}
/**
* @param array<mixed> $permissionMap
*/
public static function fromArray(array $permissionMap, string $defaultForDirectories = Visibility::PRIVATE): PortableVisibilityConverter
{
return new PortableVisibilityConverter(
$permissionMap['file']['public'] ?? 0644,
$permissionMap['file']['private'] ?? 0600,
$permissionMap['dir']['public'] ?? 0755,
$permissionMap['dir']['private'] ?? 0700,
$defaultForDirectories
);
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\UnixVisibility;
interface VisibilityConverter
{
public function forFile(string $visibility): int;
public function forDirectory(string $visibility): int;
public function inverseForFile(int $visibility): string;
public function inverseForDirectory(int $visibility): string;
public function defaultForDirectories(): int;
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
final class UnreadableFileEncountered extends RuntimeException implements FilesystemException
{
/**
* @var string
*/
private $location;
public function location(): string
{
return $this->location;
}
public static function atLocation(string $location): UnreadableFileEncountered
{
$e = new static("Unreadable file encountered at location {$location}.");
$e->location = $location;
return $e;
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
final class Visibility
{
public const PUBLIC = 'public';
public const PRIVATE = 'private';
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class WhitespacePathNormalizer implements PathNormalizer
{
public function normalizePath(string $path): string
{
$path = str_replace('\\', '/', $path);
$this->rejectFunkyWhiteSpace($path);
return $this->normalizeRelativePath($path);
}
private function rejectFunkyWhiteSpace(string $path): void
{
if (preg_match('#\p{C}+#u', $path)) {
throw CorruptedPathDetected::forPath($path);
}
}
private function normalizeRelativePath(string $path): string
{
$parts = [];
foreach (explode('/', $path) as $part) {
switch ($part) {
case '':
case '.':
break;
case '..':
if (empty($parts)) {
throw PathTraversalDetected::forPath($path);
}
array_pop($parts);
break;
default:
$parts[] = $part;
break;
}
}
return implode('/', $parts);
}
}

View File

@ -0,0 +1,31 @@
# Changelog
## 1.10.0 - 2022-04-11
### Fixed
- Added Flysystem v1 inconclusive mime-types and made it configurable as a constructor parameter.
## 1.9.0 - 2021-11-21
### Updated
- Updated lookup
## 1.8.0 - 2021-09-25
### Added
- Added the decorator `OverridingExtensionToMimeTypeMap` which allows you to override values.
## 1.7.0 - 2021-01-18
### Added
- Added a `bufferSampleSize` parameter to the `FinfoMimeTypeDetector` class that allows you to send a reduced content sample which costs less memory.
## 1.6.0 - 2021-01-18
### Changes
- Updated generated mime-type map

View File

@ -0,0 +1,19 @@
Copyright (c) 2013-2022 Frank de Jonge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,34 @@
{
"name": "league/mime-type-detection",
"description": "Mime-type detection for Flysystem",
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"scripts": {
"test": "vendor/bin/phpunit",
"phpstan": "vendor/bin/phpstan analyse -l 6 src"
},
"require": {
"php": "^7.2 || ^8.0",
"ext-fileinfo": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5.8 || ^9.3",
"phpstan/phpstan": "^0.12.68",
"friendsofphp/php-cs-fixer": "^3.2"
},
"autoload": {
"psr-4": {
"League\\MimeTypeDetection\\": "src"
}
},
"config": {
"platform": {
"php": "7.2.0"
}
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace League\MimeTypeDetection;
class EmptyExtensionToMimeTypeMap implements ExtensionToMimeTypeMap
{
public function lookupMimeType(string $extension): ?string
{
return null;
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace League\MimeTypeDetection;
use const PATHINFO_EXTENSION;
class ExtensionMimeTypeDetector implements MimeTypeDetector
{
/**
* @var ExtensionToMimeTypeMap
*/
private $extensions;
public function __construct(ExtensionToMimeTypeMap $extensions = null)
{
$this->extensions = $extensions ?: new GeneratedExtensionToMimeTypeMap();
}
public function detectMimeType(string $path, $contents): ?string
{
return $this->detectMimeTypeFromPath($path);
}
public function detectMimeTypeFromPath(string $path): ?string
{
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
return $this->extensions->lookupMimeType($extension);
}
public function detectMimeTypeFromFile(string $path): ?string
{
return $this->detectMimeTypeFromPath($path);
}
public function detectMimeTypeFromBuffer(string $contents): ?string
{
return null;
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace League\MimeTypeDetection;
interface ExtensionToMimeTypeMap
{
public function lookupMimeType(string $extension): ?string;
}

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace League\MimeTypeDetection;
use const FILEINFO_MIME_TYPE;
use const PATHINFO_EXTENSION;
use finfo;
class FinfoMimeTypeDetector implements MimeTypeDetector
{
private const INCONCLUSIVE_MIME_TYPES = [
'application/x-empty',
'text/plain',
'text/x-asm',
'application/octet-stream',
'inode/x-empty',
];
/**
* @var finfo
*/
private $finfo;
/**
* @var ExtensionToMimeTypeMap
*/
private $extensionMap;
/**
* @var int|null
*/
private $bufferSampleSize;
/**
* @var array<string>
*/
private $inconclusiveMimetypes;
public function __construct(
string $magicFile = '',
ExtensionToMimeTypeMap $extensionMap = null,
?int $bufferSampleSize = null,
array $inconclusiveMimetypes = self::INCONCLUSIVE_MIME_TYPES
) {
$this->finfo = new finfo(FILEINFO_MIME_TYPE, $magicFile);
$this->extensionMap = $extensionMap ?: new GeneratedExtensionToMimeTypeMap();
$this->bufferSampleSize = $bufferSampleSize;
$this->inconclusiveMimetypes = $inconclusiveMimetypes;
}
public function detectMimeType(string $path, $contents): ?string
{
$mimeType = is_string($contents)
? (@$this->finfo->buffer($this->takeSample($contents)) ?: null)
: null;
if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) {
return $mimeType;
}
return $this->detectMimeTypeFromPath($path);
}
public function detectMimeTypeFromPath(string $path): ?string
{
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
return $this->extensionMap->lookupMimeType($extension);
}
public function detectMimeTypeFromFile(string $path): ?string
{
return @$this->finfo->file($path) ?: null;
}
public function detectMimeTypeFromBuffer(string $contents): ?string
{
return @$this->finfo->buffer($this->takeSample($contents)) ?: null;
}
private function takeSample(string $contents): string
{
if ($this->bufferSampleSize === null) {
return $contents;
}
return (string) substr($contents, 0, $this->bufferSampleSize);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace League\MimeTypeDetection;
interface MimeTypeDetector
{
/**
* @param string|resource $contents
*/
public function detectMimeType(string $path, $contents): ?string;
public function detectMimeTypeFromBuffer(string $contents): ?string;
public function detectMimeTypeFromPath(string $path): ?string;
public function detectMimeTypeFromFile(string $path): ?string;
}

View File

@ -0,0 +1,30 @@
<?php
namespace League\MimeTypeDetection;
class OverridingExtensionToMimeTypeMap implements ExtensionToMimeTypeMap
{
/**
* @var ExtensionToMimeTypeMap
*/
private $innerMap;
/**
* @var string[]
*/
private $overrides;
/**
* @param array<string, string> $overrides
*/
public function __construct(ExtensionToMimeTypeMap $innerMap, array $overrides)
{
$this->innerMap = $innerMap;
$this->overrides = $overrides;
}
public function lookupMimeType(string $extension): ?string
{
return $this->overrides[$extension] ?? $this->innerMap->lookupMimeType($extension);
}
}