PK )}wQ
7 .travis.ymlnu ٘ language: php
services:
- docker
cache:
directories:
- $HOME/.composer/cache/files
matrix:
include:
- php: 7.2
dist: bionic
env: COMPOSER_OPTS=""
- php: 7.2
dist: bionic
env: COMPOSER_OPTS="--prefer-lowest"
- php: 7.3
dist: bionic
env: COMPOSER_OPTS=""
- php: 7.3
dist: bionic
env: COMPOSER_OPTS="--prefer-lowest"
- php: 7.4
dist: bionic
env: COMPOSER_OPTS=""
- php: 7.4
dist: bionic
env: COMPOSER_OPTS="--prefer-lowest"
- php: nightly
dist: bionic
env: COMPOSER_OPTS="--ignore-platform-reqs" UPGRADE_AWS_SDK="yes"
- php: nightly
dist: bionic
env: COMPOSER_OPTS="--ignore-platform-reqs --prefer-lowest" UPGRADE_AWS_SDK="yes"
allow_failures:
- php: nightly
env: COMPOSER_OPTS="--ignore-platform-reqs" UPGRADE_AWS_SDK="yes"
- php: nightly
env: COMPOSER_OPTS="--ignore-platform-reqs --prefer-lowest" UPGRADE_AWS_SDK="yes"
install:
- docker-compose -f test_files/docker-compose.yml up -d
- travis_retry composer update --prefer-dist $COMPOSER_OPTS
- chmod 0400 ./test_files/sftp/id_*
- eval `ssh-agent`
- ssh-add ./test_files/sftp/id_rsa
- php test_files/wait_for_sftp.php
- php test_files/wait_for_ftp.php 2121
- php test_files/wait_for_ftp.php 2122
- if [[ "${UPGRADE_AWS_SDK}" == "yes" ]]; then composer require --dev --ignore-platform-reqs aws/aws-sdk-php:^3.147.3; fi
script:
- FLYSYSTEM_TEST_SFTP=yes vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
- vendor/bin/phpstan analyse -l 6 src
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.xml
PK )}wQo<
V src/StorageAttributes.phpnu ٘ 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;
}
}
PK )}wQF% ! src/FilesystemOperationFailed.phpnu ٘ 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_VISIBILITY));
}
public function setVisibility(string $path, $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);
return new FileAttributes($path, null, null, null, $mimetype);
} catch (Throwable $exception) {
throw UnableToRetrieveMetadata::mimeType($path, '', $exception);
}
}
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);
foreach ($listing as $filename => $attributes) {
if ($filename === '.' || $filename === '..') {
continue;
}
// Ensure numeric keys are strings.
$filename = (string) $filename;
$itemPath = $this->prefixer->stripPrefix($location . ltrim($filename, '/'));
$attributes = $this->convertListingToAttributes($itemPath, $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
{
if ($attributes['type'] === NET_SFTP_TYPE_DIRECTORY) {
return new DirectoryAttributes(
ltrim($path, '/'), $this->visibilityConverter->inverseForDirectory($attributes['permissions'] & 0777)
);
}
return new FileAttributes(
$path,
$attributes['size'],
$this->visibilityConverter->inverseForFile($attributes['permissions'] & 0777),
$attributes['mtime']
);
}
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($destinationLocation, $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);
}
}
}
PK )}wQd src/PhpseclibV2/README.mdnu ٘ ## Sub-split of league/flysystem for SFTP using phpseclib2.
View the [documentation](https://flysystem.thephpleague.com/v2/docs/adapter/sftp/).
PK )}wQ k k * src/PhpseclibV2/SftpConnectionProvider.phpnu ٘ 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() ?: 'no-public-key';
$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();
}
}
}
PK )}wQXS - src/PhpseclibV2/SimpleConnectivityChecker.phpnu ٘ isConnected();
}
}
PK )}wQ{' 7 src/PhpseclibV2/UnableToEstablishAuthenticityOfHost.phpnu ٘ succeedAfter = $succeedAfter;
}
public function isConnected(SFTP $connection): bool
{
if ($this->numberOfTimesChecked >= $this->succeedAfter) {
return true;
}
$this->numberOfTimesChecked++;
return false;
}
}
PK )}wQJJc c src/PhpseclibV2/composer.jsonnu ٘ {
"name": "league/flysystem-sftp",
"description": "In-memory filesystem adapter for Flysystem.",
"keywords": ["flysystem", "filesystem", "sftp", "files", "file"],
"autoload": {
"psr-4": {
"League\\Flysystem\\PhpseclibV2\\": ""
}
},
"require": {
"php": "^7.2",
"league/flysystem": "2.0.0-alpha.3",
"league/mime-type-detection": "^1.0.0",
"phpseclib/phpseclib": "^2.0"
},
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
]
}
PK )}wQdk) ) ! src/InvalidVisibilityProvided.phpnu ٘ formatPropertyName((string) $offset);
return isset($this->{$property});
}
/**
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
$property = $this->formatPropertyName((string) $offset);
return $this->{$property};
}
/**
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value): void
{
throw new RuntimeException('Properties can not be manipulated');
}
/**
* @param mixed $offset
*/
public function offsetUnset($offset): void
{
throw new RuntimeException('Properties can not be manipulated');
}
}
PK )}wQS1 src/UnableToCreateDirectory.phpnu ٘ location = $dirname;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_CREATE_DIRECTORY;
}
public function location(): string
{
return $this->location;
}
}
PK )}wQe ' src/Ftp/InvalidListResponseReceived.phpnu ٘ host = $host;
$this->root = $root;
$this->username = $username;
$this->password = $password;
$this->port = $port;
$this->ssl = $ssl;
$this->timeout = $timeout;
$this->utf8 = $utf8;
$this->passive = $passive;
$this->transferMode = $transferMode;
$this->systemType = $systemType;
$this->ignorePassiveAddress = $ignorePassiveAddress;
$this->enableTimestampsOnUnixListings = $enableTimestampsOnUnixListings;
$this->recurseManually = $recurseManually;
}
public function host(): string
{
return $this->host;
}
public function root(): string
{
return $this->root;
}
public function username(): string
{
return $this->username;
}
public function password(): string
{
return $this->password;
}
public function port(): int
{
return $this->port;
}
public function ssl(): bool
{
return $this->ssl;
}
public function timeout(): int
{
return $this->timeout;
}
public function utf8(): bool
{
return $this->utf8;
}
public function passive(): bool
{
return $this->passive;
}
public function transferMode(): int
{
return $this->transferMode;
}
public function systemType(): ?string
{
return $this->systemType;
}
public function ignorePassiveAddress(): ?bool
{
return $this->ignorePassiveAddress;
}
public function timestampsOnUnixListingsEnabled(): bool
{
return $this->enableTimestampsOnUnixListings;
}
public function recurseManually(): bool
{
return $this->recurseManually;
}
public static function fromArray(array $options): FtpConnectionOptions
{
return new FtpConnectionOptions(
$options['host'] ?? 'invalid://host-not-set',
$options['root'] ?? 'invalid://root-not-set',
$options['username'] ?? 'invalid://username-not-set',
$options['password'] ?? 'invalid://password-not-set',
$options['port'] ?? 21,
$options['ssl'] ?? false,
$options['timeout'] ?? 90,
$options['utf8'] ?? false,
$options['passive'] ?? true,
$options['transferMode'] ?? FTP_BINARY,
$options['systemType'] ?? null,
$options['ignorePassiveAddress'] ?? null,
$options['timestampsOnUnixListingsEnabled'] ?? false,
$options['recurseManually'] ?? true
);
}
}
PK )}wQ
" src/Ftp/FtpConnectionException.phpnu ٘ createConnectionResource(
$options->host(),
$options->port(),
$options->timeout(),
$options->ssl()
);
try {
$this->authenticate($options, $connection);
$this->enableUtf8Mode($options, $connection);
$this->ignorePassiveAddress($options, $connection);
$this->makeConnectionPassive($options, $connection);
} catch (FtpConnectionException $exception) {
ftp_close($connection);
throw $exception;
}
return $connection;
}
/**
* @return resource
*/
private function createConnectionResource(string $host, int $port, int $timeout, bool $ssl)
{
$connection = $ssl ? @ftp_ssl_connect($host, $port, $timeout) : @ftp_connect($host, $port, $timeout);
if ( ! is_resource($connection)) {
throw UnableToConnectToFtpHost::forHost($host, $port, $ssl);
}
return $connection;
}
/**
* @param resource $connection
*/
private function authenticate(FtpConnectionOptions $options, $connection): void
{
if ( ! @ftp_login($connection, $options->username(), $options->password())) {
throw new UnableToAuthenticate();
}
}
/**
* @param resource $connection
*/
private function enableUtf8Mode(FtpConnectionOptions $options, $connection): void
{
if ( ! $options->utf8()) {
return;
}
$response = ftp_raw($connection, "OPTS UTF8 ON");
if (substr($response[0] ?? '', 0, 3) !== '200') {
throw new UnableToEnableUtf8Mode(
'Could not set UTF-8 mode for connection: ' . $options->host() . '::' . $options->port()
);
}
}
/**
* @param resource $connection
*/
private function ignorePassiveAddress(FtpConnectionOptions $options, $connection): void
{
$ignorePassiveAddress = $options->ignorePassiveAddress();
if ( ! is_bool($ignorePassiveAddress) || ! defined('FTP_USEPASVADDRESS')) {
return;
}
if ( ! ftp_set_option($connection, FTP_USEPASVADDRESS, ! $ignorePassiveAddress)) {
throw UnableToSetFtpOption::whileSettingOption('FTP_USEPASVADDRESS');
}
}
/**
* @param resource $connection
*/
private function makeConnectionPassive(FtpConnectionOptions $options, $connection): void
{
if ( ! ftp_pasv($connection, $options->passive())) {
throw new UnableToMakeConnectionPassive(
'Could not set passive mode for connection: ' . $options->host() . '::' . $options->port()
);
}
}
}
PK )}wQu` $ src/Ftp/UnableToConnectToFtpHost.phpnu ٘ connectivityChecker = $connectivityChecker;
}
public function failNextCall(): void
{
$this->failNextCall = true;
}
/**
* @inheritDoc
*/
public function isConnected($connection): bool
{
if ($this->failNextCall) {
$this->failNextCall = false;
return false;
}
return $this->connectivityChecker->isConnected($connection);
}
}
PK )}wQ` src/Ftp/ConnectionProvider.phpnu ٘ connectionOptions = $connectionOptions;
$this->connectionProvider = $connectionProvider ?: new FtpConnectionProvider();
$this->connectivityChecker = $connectivityChecker ?: new NoopCommandConnectivityChecker();
$this->visibilityConverter = $visibilityConverter ?: new PortableVisibilityConverter();
$this->prefixer = new PathPrefixer($connectionOptions->root());
$this->mimeTypeDetector = $mimeTypeDetector ?: new FinfoMimeTypeDetector();
}
/**
* @return resource
*/
private function connection()
{
start:
if ( ! is_resource($this->connection)) {
$this->connection = $this->connectionProvider->createConnection($this->connectionOptions);
}
if ($this->connectivityChecker->isConnected($this->connection) === false) {
$this->connection = false;
goto start;
}
ftp_chdir($this->connection, $this->connectionOptions->root());
return $this->connection;
}
private function isPureFtpdServer(): bool
{
if ($this->isPureFtpdServer !== null) {
return $this->isPureFtpdServer;
}
$response = ftp_raw($this->connection, 'HELP');
return $this->isPureFtpdServer = stripos(implode(' ', $response), 'Pure-FTPd') !== false;
}
public function fileExists(string $path): bool
{
try {
$this->fileSize($path);
return true;
} catch (UnableToRetrieveMetadata $exception) {
return false;
}
}
public function write(string $path, string $contents, Config $config): void
{
try {
$writeStream = fopen('php://temp', 'w+b');
fwrite($writeStream, $contents);
rewind($writeStream);
$this->writeStream($path, $writeStream, $config);
} finally {
is_resource($writeStream) && fclose($writeStream);
}
}
public function writeStream(string $path, $resource, Config $config): void
{
try {
$this->ensureParentDirectoryExists($path, $config->get(Config::OPTION_DIRECTORY_VISIBILITY));
} catch (Throwable $exception) {
throw UnableToWriteFile::atLocation($path, 'creating parent directory failed', $exception);
}
$location = $this->prefixer->prefixPath($path);
if ( ! ftp_fput($this->connection(), $location, $resource, $this->connectionOptions->transferMode())) {
throw UnableToWriteFile::atLocation($path, 'writing the file failed');
}
if ( ! $visibility = $config->get(Config::OPTION_VISIBILITY)) {
return;
}
try {
$this->setVisibility($path, $visibility);
} catch (Throwable $exception) {
throw UnableToWriteFile::atLocation($path, 'setting visibility failed', $exception);
}
}
public function read(string $path): string
{
$readStream = $this->readStream($path);
$contents = stream_get_contents($readStream);
fclose($readStream);
return $contents;
}
public function readStream(string $path)
{
$location = $this->prefixer->prefixPath($path);
$stream = fopen('php://temp', 'w+b');
$result = @ftp_fget($this->connection(), $stream, $location, $this->connectionOptions->transferMode());
if ( ! $result) {
fclose($stream);
throw UnableToReadFile::fromLocation($path);
}
rewind($stream);
return $stream;
}
public function delete(string $path): void
{
$connection = $this->connection();
$this->deleteFile($path, $connection);
}
/**
* @param resource $connection
*/
private function deleteFile(string $path, $connection): void
{
$location = $this->prefixer->prefixPath($path);
$success = @ftp_delete($connection, $location);
if ($success === false && ftp_size($connection, $location) !== -1) {
throw UnableToDeleteFile::atLocation($path, 'the file still exists');
}
}
public function deleteDirectory(string $path): void
{
/** @var StorageAttributes[] $contents */
$contents = $this->listContents($path, true);
$connection = $this->connection();
$directories = [$path];
foreach ($contents as $item) {
if ($item->isDir()) {
$directories[] = $item->path();
continue;
}
try {
$this->deleteFile($item->path(), $connection);
} catch (Throwable $exception) {
throw UnableToDeleteDirectory::atLocation($path, 'unable to delete child', $exception);
}
}
rsort($directories);
foreach ($directories as $directory) {
if ( ! @ftp_rmdir($connection, $this->prefixer->prefixPath($directory))) {
throw UnableToDeleteDirectory::atLocation($path, "Could not delete directory $directory");
}
}
}
public function createDirectory(string $path, Config $config): void
{
$this->ensureDirectoryExists($path, $config->get('visibility'));
}
public function setVisibility(string $path, $visibility): void
{
$location = $this->prefixer->prefixPath($path);
$mode = $this->visibilityConverter->forFile($visibility);
if ( ! @ftp_chmod($this->connection(), $mode, $location)) {
throw UnableToSetVisibility::atLocation($path);
}
}
private function fetchFileMetadata(string $path, string $type): FileAttributes
{
$path = ltrim($path, '/');
$dirname = dirname($path);
$attributes = null;
if ($dirname === '.') {
$dirname = '';
}
/** @var StorageAttributes[] $items */
$items = $this->listContents($dirname, false);
foreach ($items as $attributes) {
if ($attributes->path() === $path) {
break;
}
}
if ( ! $attributes instanceof FileAttributes) {
throw UnableToRetrieveMetadata::create(
$path,
$type,
'expected file, ' . ($attributes instanceof DirectoryAttributes ? 'directory found' : 'nothing found')
);
}
return $attributes;
}
public function mimeType(string $path): FileAttributes
{
try {
$contents = $this->read($path);
$mimetype = $this->mimeTypeDetector->detectMimeType($path, $contents);
return new FileAttributes($path, null, null, null, $mimetype);
} catch (Throwable $exception) {
throw UnableToRetrieveMetadata::mimeType($path, '', $exception);
}
}
public function lastModified(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connection();
$lastModified = @ftp_mdtm($connection, $location);
if ($lastModified < 0) {
throw UnableToRetrieveMetadata::lastModified($path);
}
return new FileAttributes($path, null, null, $lastModified);
}
public function visibility(string $path): FileAttributes
{
return $this->fetchFileMetadata($path, FileAttributes::ATTRIBUTE_VISIBILITY);
}
public function fileSize(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
$connection = $this->connection();
$fileSize = @ftp_size($connection, $location);
if ($fileSize < 0) {
throw UnableToRetrieveMetadata::fileSize($path);
}
return new FileAttributes($path, $fileSize);
}
public function listContents(string $path, bool $deep): iterable
{
$path = ltrim($path, '/');
$path = $path === '' ? $path : trim($path, '/') . '/';
if ($deep && $this->connectionOptions->recurseManually()) {
yield from $this->listDirectoryContentsRecursive($path);
} else {
$location = $this->prefixer->prefixPath($path);
$options = $deep ? '-alnR' : '-aln';
$listing = $this->ftpRawlist($options, $location);
yield from $this->normalizeListing($listing, $path);
}
}
private function normalizeListing(array $listing, string $prefix = ''): Generator
{
$base = $prefix;
foreach ($listing as $item) {
if ($item === '' || preg_match('#.* \.(\.)?$|^total#', $item)) {
continue;
}
if (preg_match('#^.*:$#', $item)) {
$base = preg_replace('~^\./*|:$~', '', $item);
continue;
}
yield $this->normalizeObject($item, $base);
}
}
private function normalizeObject(string $item, string $base): StorageAttributes
{
$systemType = $this->systemType ?: $this->detectSystemType($item);
if ($systemType === self::SYSTEM_TYPE_UNIX) {
return $this->normalizeUnixObject($item, $base);
}
return $this->normalizeWindowsObject($item, $base);
}
private function detectSystemType(string $item): string
{
return preg_match(
'/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/',
$item
) ? self::SYSTEM_TYPE_WINDOWS : self::SYSTEM_TYPE_UNIX;
}
private function normalizeWindowsObject(string $item, string $base): StorageAttributes
{
$item = preg_replace('#\s+#', ' ', trim($item), 3);
$parts = explode(' ', $item, 4);
if (count($parts) !== 4) {
throw new InvalidListResponseReceived("Metadata can't be parsed from item '$item' , not enough parts.");
}
[$date, $time, $size, $name] = $parts;
$path = $base === '' ? $name : rtrim($base, '/') . '/' . $name;
if ($size === '
') {
return new DirectoryAttributes($path);
}
// Check for the correct date/time format
$format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
$dt = DateTime::createFromFormat($format, $date . $time);
$lastModified = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time");
$size = (int) $size;
return new FileAttributes($path, (int) $size, null, $lastModified);
}
private function normalizeUnixObject(string $item, string $base): StorageAttributes
{
$item = preg_replace('#\s+#', ' ', trim($item), 7);
$parts = explode(' ', $item, 9);
if (count($parts) !== 9) {
throw new InvalidListResponseReceived("Metadata can't be parsed from item '$item' , not enough parts.");
}
[$permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name] = $parts;
$isDirectory = $this->listingItemIsDirectory($permissions);
$permissions = $this->normalizePermissions($permissions);
$path = $base === '' ? $name : rtrim($base, '/') . '/' . $name;
if ($isDirectory) {
return new DirectoryAttributes($path, $this->visibilityConverter->inverseForDirectory($permissions));
}
$visibility = $this->visibilityConverter->inverseForFile($permissions);
$size = (int) $size;
$lastModified = null;
if ($this->connectionOptions->timestampsOnUnixListingsEnabled()) {
$lastModified = $this->normalizeUnixTimestamp($month, $day, $timeOrYear);
}
return new FileAttributes($path, (int) $size, $visibility, $lastModified);
}
private function listingItemIsDirectory(string $permissions): bool
{
return substr($permissions, 0, 1) === 'd';
}
private function normalizeUnixTimestamp(string $month, string $day, string $timeOrYear): int
{
if (is_numeric($timeOrYear)) {
$year = $timeOrYear;
$hour = '00';
$minute = '00';
$seconds = '00';
} else {
$year = date('Y');
[$hour, $minute] = explode(':', $timeOrYear);
$seconds = '00';
}
$dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}");
return $dateTime->getTimestamp();
}
private function normalizePermissions(string $permissions): int
{
// remove the type identifier
$permissions = substr($permissions, 1);
// map the string rights to the numeric counterparts
$map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
$permissions = strtr($permissions, $map);
// split up the permission groups
$parts = str_split($permissions, 3);
// convert the groups
$mapper = function ($part) {
return array_sum(str_split($part));
};
// converts to decimal number
return octdec(implode('', array_map($mapper, $parts)));
}
/**
* @inheritdoc
*
* @param string $directory
*/
private function listDirectoryContentsRecursive(string $directory): Generator
{
$location = $this->prefixer->prefixPath($directory);
$listing = $this->ftpRawlist('-aln', $location);
/** @var StorageAttributes[] $listing */
$listing = $this->normalizeListing($listing, $directory);
foreach ($listing as $item) {
yield $item;
if ( ! $item->isDir()) {
continue;
}
$children = $this->listDirectoryContentsRecursive($item->path());
foreach ($children as $child) {
yield $child;
}
}
}
private function ftpRawlist(string $options, string $path): array
{
$path = rtrim($path, '/') . '/';
$connection = $this->connection();
if ($this->isPureFtpdServer()) {
$path = str_replace(' ', '\ ', $path);
}
return ftp_rawlist($connection, $options . ' ' . $path, stripos($options, 'R') !== false) ?: [];
}
public function move(string $source, string $destination, Config $config): void
{
try {
$this->ensureParentDirectoryExists($destination, $config->get(Config::OPTION_DIRECTORY_VISIBILITY));
} catch (Throwable $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
$sourceLocation = $this->prefixer->prefixPath($source);
$destinationLocation = $this->prefixer->prefixPath($destination);
$connection = $this->connection();
if ( ! @ftp_rename($connection, $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);
}
}
private function ensureParentDirectoryExists(string $path, ?string $visibility): void
{
$dirname = dirname($path);
if ($dirname === '' || $dirname === '.') {
return;
}
$this->ensureDirectoryExists($dirname, $visibility);
}
/**
* @param string $dirname
*/
private function ensureDirectoryExists(string $dirname, ?string $visibility): void
{
$connection = $this->connection();
$dirPath = '';
$parts = explode('/', rtrim($dirname, '/'));
$mode = $visibility ? $this->visibilityConverter->forDirectory($visibility) : false;
foreach ($parts as $part) {
$dirPath .= '/' . $part;
$location = $this->prefixer->prefixPath($dirPath);
if (@ftp_chdir($connection, $location)) {
continue;
}
error_clear_last();
$result = @ftp_mkdir($connection, $location);
if ($result === false) {
$errorMessage = error_get_last()['message'] ?? 'unable to create the directory';
throw UnableToCreateDirectory::atLocation($dirPath, $errorMessage);
}
if ($mode !== false && @ftp_chmod($connection, $mode, $location) === false) {
throw UnableToCreateDirectory::atLocation($dirPath, 'unable to chmod the directory');
}
}
}
}
PK )}wQJ`P% % ) src/Ftp/RawListFtpConnectivityChecker.phpnu ٘ 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;
}
}
PK )}wQ^!n2 2 $ src/Local/LocalFilesystemAdapter.phpnu ٘ 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 $location, string $contents, Config $config): void
{
$prefixedLocation = $this->prefixer->prefixPath($location);
$this->ensureDirectoryExists(
dirname($prefixedLocation),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
error_clear_last();
if (($size = @file_put_contents($prefixedLocation, $contents, $this->writeFlags)) === false) {
throw UnableToWriteFile::atLocation($location, error_get_last()['message'] ?? '');
}
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($location, (string) $visibility);
}
}
public function writeStream(string $location, $contents, Config $config): void
{
$path = $this->prefixer->prefixPath($location);
$this->ensureDirectoryExists(
dirname($path),
$this->resolveDirectoryVisibility($config->get('directory_visibility'))
);
error_clear_last();
$stream = @fopen($path, 'w+b');
if ( ! ($stream && false !== stream_copy_to_stream($contents, $stream) && fclose($stream))) {
$reason = error_get_last()['message'] ?? '';
throw UnableToWriteFile::atLocation($path, $reason);
}
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($location, (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());
yield $fileInfo->isDir() ? new DirectoryAttributes($path) : new FileAttributes(
$path, $fileInfo->getSize(), null, $fileInfo->getMTime()
);
}
}
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('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('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(false, $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, $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);
}
}
}
PK )}wQ}{ { src/UnableToRetrieveMetadata.phpnu ٘ location = $location;
$e->metadataType = $type;
return $e;
}
public function location(): string
{
return $this->location;
}
public function metadataType(): string
{
return $this->metadataType;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_RETRIEVE_METADATA;
}
}
PK )}wQ; src/PathTraversalDetected.phpnu ٘ path;
}
public static function forPath(string $path): PathTraversalDetected
{
$e = new PathTraversalDetected("Path traversal detected: {$path}");
$e->path = $path;
return $e;
}
}
PK )}wQ
src/DirectoryListing.phpnu ٘
*/
private $listing;
/**
* @param iterable $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);
}
/**
* @return iterable
*/
public function getIterator(): iterable
{
return $this->listing;
}
/**
* @return T[]
*/
public function toArray(): array
{
return $this->listing instanceof Traversable
? iterator_to_array($this->listing, false)
: (array) $this->listing;
}
}
PK )}wQЂ src/FilesystemException.phpnu ٘ location;
}
public static function atLocation(string $pathName): SymbolicLinkEncountered
{
$e = new static("Unsupported symbolic link encountered at location $pathName");
$e->location = $pathName;
return $e;
}
}
PK )}wQ src/PathPrefixer.phpnu ٘ prefix = rtrim($prefix, '\\/');
if ($prefix !== '') {
$this->prefix .= $separator;
}
$this->separator = $separator;
}
public function prefixPath(string $path): string
{
return $this->prefix . ltrim($path, '\\/');
}
public function stripPrefix(string $path): string
{
return substr($path, strlen($this->prefix));
}
}
PK )}wQ`t׃ src/FileAttributes.phpnu ٘ 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 static function fromArray(array $attributes): StorageAttributes
{
return new FileAttributes(
$attributes[self::ATTRIBUTE_PATH],
$attributes[self::ATTRIBUTE_FILE_SIZE] ?? null,
$attributes[self::ATTRIBUTE_VISIBILITY] ?? null,
$attributes[self::ATTRIBUTE_LAST_MODIFIED] ?? null,
$attributes[self::ATTRIBUTE_MIME_TYPE] ?? null,
$attributes[self::ATTRIBUTE_EXTRA_METADATA] ?? []
);
}
public function jsonSerialize(): array
{
return [
self::ATTRIBUTE_TYPE => self::TYPE_FILE,
self::ATTRIBUTE_PATH => $this->path,
self::ATTRIBUTE_FILE_SIZE => $this->fileSize,
self::ATTRIBUTE_VISIBILITY => $this->visibility,
self::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
self::ATTRIBUTE_MIME_TYPE => $this->mimeType,
self::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
];
}
}
PK )}wQߍ}g g src/DirectoryAttributes.phpnu ٘ path = $path;
$this->visibility = $visibility;
}
public function path(): string
{
return $this->path;
}
public function type(): string
{
return StorageAttributes::TYPE_DIRECTORY;
}
public function visibility(): ?string
{
return $this->visibility;
}
public function isFile(): bool
{
return false;
}
public function isDir(): bool
{
return true;
}
public static function fromArray(array $attributes): StorageAttributes
{
return new DirectoryAttributes(
$attributes['path'], $attributes['visibility'] ?? null
);
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
return [
'type' => $this->type,
'path' => $this->path,
'visibility' => $this->visibility,
];
}
}
PK )}wQDg src/Filesystem.phpnu ٘ 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)) {
throw new InvalidStreamProvided(
"Invalid stream provided, expected resource, received " . gettype($contents)
);
}
}
/**
* @param resource $resource
*/
private function rewindStream($resource): void
{
if (ftell($resource) !== 0 && stream_get_meta_data($resource)['seekable']) {
rewind($resource);
}
}
}
PK )}wQv, , ! src/UnreadableFileEncountered.phpnu ٘ location;
}
public static function atLocation(string $location): UnreadableFileEncountered
{
$e = new static("Unreadable file encountered at location {$location}.");
$e->location = $location;
return $e;
}
}
PK )}wQ src/UnableToWriteFile.phpnu ٘ 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;
}
}
PK )}wQl/ src/UnableToSetVisibility.phpnu ٘ 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;
}
}
PK )}wQzY, src/FilesystemWriter.phpnu ٘ 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;
}
}
PK )}wQg src/FilesystemOperator.phpnu ٘ 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;
}
}
PK )}wQ` ` 2 src/UnixVisibility/PortableVisibilityConverter.phpnu ٘ 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 $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'] ?? 0755,
$defaultForDirectories
);
}
}
PK )}wQ.|呑 * src/UnixVisibility/VisibilityConverter.phpnu ٘
* @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;
}
PK )}wQ7,Bya
a
src/FilesystemAdapter.phpnu ٘
* @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;
}
PK )}wQ1V src/PortableVisibilityGuard.phpnu ٘ removeFunkyWhiteSpace($path);
return $this->normalizeRelativePath($path);
}
private function removeFunkyWhiteSpace(string $path): string
{
// Remove unprintable characters and invalid unicode characters.
// We do this check in a loop, since removing invalid unicode characters
// can lead to new characters being created.
while (preg_match('#\p{C}+|^\./#u', $path)) {
$path = (string) preg_replace('#\p{C}+|^\./#u', '', $path);
}
return $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);
}
}
PK )}wQ( src/UnableToCopyFile.phpnu ٘ 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 move file from $sourcePath to $destinationPath", 0 , $previous);
$e->source = $sourcePath;
$e->destination = $destinationPath;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_COPY;
}
}
PK )}wQB9I I src/Config.phpnu ٘ 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);
}
}
PK )}wQ/s phpunit.phpnu ٘
src/
src/
src/
PK )}wQ8$
$
readme.mdnu ٘ # 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)
[![Build Status](https://travis-ci.org/thephpleague/flysystem.svg?branch=v1.0)](https://travis-ci.org/thephpleague/flysystem)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/flysystem.svg)](https://scrutinizer-ci.com/g/thephpleague/flysystem/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/flysystem.svg)](https://scrutinizer-ci.com/g/thephpleague/flysystem)
[![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 different 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 interact with your Flysystem instance
### Commonly-Used Adapters
* **[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/)**
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).
PK )}wQ phpstan.neonnu ٘ parameters:
checkMissingIterableValueType: false
reportUnmatchedIgnoredErrors: false
checkGenericClassInNonGenericObjectType: false
scanFiles:
- src/AdapterTestUtilities/test-functions.php
excludes_analyse:
- src/AdapterTestUtilities/
- src/AwsS3V3
- src/FTP
- src/InMemory
- src/PHPSecLibV2
ignoreErrors:
- '#Comparison operation "<" between 0|1 and 4 is always true.#'
- '#Method League\\Flysystem\\AwsS3V3\\S3ClientStub.*#'
- '#Constant NET_SFTP_TYPE_DIRECTORY not found\.#'
- '#\$local_file of method phpseclib\\Net\\SFTP::get\(\) expects string, resource given#'
PK )}wQ^/ CHANGELOG.mdnu ٘ # Version 2.x Changelog
## 2.0.0-UNRELEASED
## Changes
* Renamed AwsS3V3Filesystem to AwsS3V3Filesystem (in line with other adapter names).
* Renamed the PHPSecLibV2 package to PhpseclibV2, Renamed the FTP package to Ftp.
* Public key and ss-agent authentication support for Sftp
## Fixes
* Allow creation of files with empty streams.
## 2.0.0-alpha.3 2020-03-21
## Fixes
* Corrected the required version for the sub-split packages.
## 2.0.0-alpha.2 2020-03-17
## Changes
* The `League\Flysystem\FilesystemAdapter::listContents` method returns an `iterable` instead of a `Generator`.
* The `League\Flysystem\DirectoryListing` class accepts an `iterable` instead of a `Generator`.
## 2.0.0-alpha.1 2020-03-09
* Initial 2.0.0 alpha release
PK )}wQ5 .scrutinizer.ymlnu ٘ filter:
paths: ["src/*"]
excluded_paths: ["src/*Test.php"]
checks:
php:
code_rating: true
remove_extra_empty_lines: true
remove_php_closing_tag: true
remove_trailing_whitespace: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: true
order_alphabetically: true
fix_php_opening_tag: true
fix_linefeed: true
fix_line_ending: true
fix_identation_4spaces: true
fix_doc_comments: true
tools:
external_code_coverage:
timeout: 1800
runs: 6
php_code_coverage: false
php_code_sniffer:
config:
standard: PSR2
filter:
paths: ['src']
php_loc:
enabled: true
excluded_dirs: [vendor]
php_sim: false
PK )}wQ3ǫ'' ' LICENSEnu ٘ Copyright (c) 2013-2020 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.
PK )}wQځ̙
composer.jsonnu ٘ {
"name": "league/flysystem",
"description": "File storage abstraction for PHP",
"keywords": [
"filesystem", "filesystems", "files", "storage", "aws",
"s3", "ftp", "sftp", "webdav", "file", "cloud"
],
"type": "library",
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src"
}
},
"require": {
"php": "^7.2",
"ext-json": "*",
"league/mime-type-detection": "^1.0.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"phpstan/phpstan": "^0.12.26",
"phpseclib/phpseclib": "^2.0",
"aws/aws-sdk-php": "^3.132.4"
},
"conflict": {
"guzzlehttp/ringphp": "<1.1.1"
},
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
]
}
PK )}wQ
7 .travis.ymlnu ٘ PK )}wQo<
V src/StorageAttributes.phpnu ٘ PK )}wQ~ M src/UnableToMoveFile.phpnu ٘ PK )}wQF% ! X
src/FilesystemOperationFailed.phpnu ٘ PK )}wQ') ) a src/PhpseclibV2/SftpAdapter.phpnu ٘ PK )}wQd : src/PhpseclibV2/README.mdnu ٘ PK )}wQ k k * x; src/PhpseclibV2/SftpConnectionProvider.phpnu ٘ PK )}wQXS - =R src/PhpseclibV2/SimpleConnectivityChecker.phpnu ٘ PK )}wQ{' 7 S src/PhpseclibV2/UnableToEstablishAuthenticityOfHost.phpnu ٘ PK )}wQ)= ( U src/PhpseclibV2/UnableToAuthenticate.phpnu ٘ PK )}wQa a * Y src/PhpseclibV2/UnableToLoadPrivateKey.phpnu ٘ PK )}wQlrÔ - Z src/PhpseclibV2/UnableToConnectToSftpHost.phpnu ٘ PK )}wQT ' \ src/PhpseclibV2/ConnectivityChecker.phpnu ٘ PK )}wQ &