/
home
/
sjslayjy
/
public_html
/
theweavenest
/
vendor
/
league
/
flysystem
/
src
/
Ftp
/
Upload File
HOME
<?php declare(strict_types=1); namespace League\Flysystem\Ftp; use DateTime; 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\StorageAttributes; 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 Throwable; use function array_map; use function error_clear_last; use function error_get_last; use function ftp_chdir; use function ftp_close; use function is_string; class FtpAdapter implements FilesystemAdapter { private const SYSTEM_TYPE_WINDOWS = 'windows'; private const SYSTEM_TYPE_UNIX = 'unix'; private ConnectionProvider $connectionProvider; private ConnectivityChecker $connectivityChecker; /** * @var resource|false|\FTP\Connection */ private mixed $connection = false; private PathPrefixer $prefixer; private VisibilityConverter $visibilityConverter; private ?bool $isPureFtpdServer = null; private ?bool $useRawListOptions; private ?string $systemType; private MimeTypeDetector $mimeTypeDetector; private ?string $rootDirectory = null; public function __construct( private FtpConnectionOptions $connectionOptions, ?ConnectionProvider $connectionProvider = null, ?ConnectivityChecker $connectivityChecker = null, ?VisibilityConverter $visibilityConverter = null, ?MimeTypeDetector $mimeTypeDetector = null, private bool $detectMimeTypeUsingPath = false, ) { $this->systemType = $this->connectionOptions->systemType(); $this->connectionProvider = $connectionProvider ?? new FtpConnectionProvider(); $this->connectivityChecker = $connectivityChecker ?? new NoopCommandConnectivityChecker(); $this->visibilityConverter = $visibilityConverter ?? new PortableVisibilityConverter(); $this->mimeTypeDetector = $mimeTypeDetector ?? new FinfoMimeTypeDetector(); $this->useRawListOptions = $connectionOptions->useRawListOptions(); } /** * Disconnect FTP connection on destruct. */ public function __destruct() { $this->disconnect(); } /** * @return resource */ private function connection() { start: if ( ! $this->hasFtpConnection()) { $this->connection = $this->connectionProvider->createConnection($this->connectionOptions); $this->rootDirectory = $this->resolveConnectionRoot($this->connection); $this->prefixer = new PathPrefixer($this->rootDirectory); return $this->connection; } if ($this->connectivityChecker->isConnected($this->connection) === false) { $this->connection = false; goto start; } ftp_chdir($this->connection, $this->rootDirectory); return $this->connection; } public function disconnect(): void { if ($this->hasFtpConnection()) { @ftp_close($this->connection); } $this->connection = false; } 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; } private function isServerSupportingListOptions(): bool { if ($this->useRawListOptions !== null) { return $this->useRawListOptions; } $response = ftp_raw($this->connection, 'SYST'); $syst = implode(' ', $response); return $this->useRawListOptions = stripos($syst, 'FileZilla') === false && stripos($syst, 'L8') === 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 { isset($writeStream) && is_resource($writeStream) && fclose($writeStream); } } public function writeStream(string $path, $contents, 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, $contents, $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, error_get_last()['message'] ?? ''); } 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(Config::OPTION_DIRECTORY_VISIBILITY, $config->get(Config::OPTION_VISIBILITY))); } public function setVisibility(string $path, string $visibility): void { $location = $this->prefixer()->prefixPath($path); $mode = $this->visibilityConverter->forFile($visibility); if ( ! @ftp_chmod($this->connection(), $mode, $location)) { $message = error_get_last()['message'] ?? ''; throw UnableToSetVisibility::atLocation($path, $message); } } private function fetchMetadata(string $path, string $type): FileAttributes { $location = $this->prefixer()->prefixPath($path); if ($this->isPureFtpdServer) { $location = $this->escapePath($location); } $object = @ftp_raw($this->connection(), 'STAT ' . $location); if (empty($object) || count($object) < 3 || str_starts_with($object[1], "ftpd:")) { throw UnableToRetrieveMetadata::create($path, $type, error_get_last()['message'] ?? ''); } $attributes = $this->normalizeObject($object[1], ''); 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 { $mimetype = $this->detectMimeTypeUsingPath ? $this->mimeTypeDetector->detectMimeTypeFromPath($path) : $this->mimeTypeDetector->detectMimeType($path, $this->read($path)); } catch (Throwable $exception) { throw UnableToRetrieveMetadata::mimeType($path, $exception->getMessage(), $exception); } if ($mimetype === null) { throw UnableToRetrieveMetadata::mimeType($path, 'Unknown.'); } return new FileAttributes($path, null, null, null, $mimetype); } 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->fetchMetadata($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, error_get_last()['message'] ?? ''); } 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 { $this->systemType === null && $this->systemType = $this->detectSystemType($item); if ($this->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 === '<DIR>') { 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"); 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; $lastModified = $this->connectionOptions->timestampsOnUnixListingsEnabled() ? $this->normalizeUnixTimestamp( $month, $day, $timeOrYear ) : null; if ($isDirectory) { return new DirectoryAttributes( $path, $this->visibilityConverter->inverseForDirectory($permissions), $lastModified ); } $visibility = $this->visibilityConverter->inverseForFile($permissions); return new FileAttributes($path, (int) $size, $visibility, $lastModified); } private function listingItemIsDirectory(string $permissions): bool { return str_starts_with($permissions, 'd'); } private function normalizeUnixTimestamp(string $month, string $day, string $timeOrYear): int { if (is_numeric($timeOrYear)) { $year = $timeOrYear; $hour = '00'; $minute = '00'; } else { $year = date('Y'); [$hour, $minute] = explode(':', $timeOrYear); } $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "$year-$month-$day-$hour:$minute:00"); 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 = static function ($part) { return array_sum(array_map(static function ($p) { return (int) $p; }, str_split($part))); }; // converts to decimal number return octdec(implode('', array_map($mapper, $parts))); } 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); $path = $this->escapePath($path); } if ( ! $this->isServerSupportingListOptions()) { $options = ''; } return ftp_rawlist($connection, ($options ? $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::because(error_get_last()['message'] ?? 'reason unknown', $source, $destination); } } public function copy(string $source, string $destination, Config $config): void { try { $readStream = $this->readStream($source); $visibility = $config->get(Config::OPTION_VISIBILITY); if ($visibility === null && $config->get(Config::OPTION_RETAIN_VISIBILITY, true)) { $config = $config->withSetting(Config::OPTION_VISIBILITY, $this->visibility($source)->visibility()); } $this->writeStream($destination, $readStream, $config); } 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); } private function ensureDirectoryExists(string $dirname, ?string $visibility): void { $connection = $this->connection(); $dirPath = ''; $parts = explode('/', trim($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: ' . (error_get_last()['message'] ?? 'reason unknown'), ); } } } private function escapePath(string $path): string { return str_replace(['*', '[', ']'], ['\\*', '\\[', '\\]'], $path); } /** * @return bool */ private function hasFtpConnection(): bool { return $this->connection instanceof \FTP\Connection || is_resource($this->connection); } public function directoryExists(string $path): bool { $location = $this->prefixer()->prefixPath($path); $connection = $this->connection(); return @ftp_chdir($connection, $location) === true; } /** * @param resource|\FTP\Connection $connection */ private function resolveConnectionRoot($connection): string { $root = $this->connectionOptions->root(); error_clear_last(); if ($root !== '' && @ftp_chdir($connection, $root) !== true) { throw UnableToResolveConnectionRoot::itDoesNotExist($root, error_get_last()['message'] ?? ''); } error_clear_last(); $pwd = @ftp_pwd($connection); if ( ! is_string($pwd)) { throw UnableToResolveConnectionRoot::couldNotGetCurrentDirectory(error_get_last()['message'] ?? ''); } return $pwd; } /** * @return PathPrefixer */ private function prefixer(): PathPrefixer { if ($this->rootDirectory === null) { $this->connection(); } return $this->prefixer; } }