123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- <?php
- namespace League\Flysystem\Adapter;
- use DateTime;
- use League\Flysystem\AdapterInterface;
- use League\Flysystem\Config;
- use League\Flysystem\NotSupportedException;
- use League\Flysystem\SafeStorage;
- use RuntimeException;
- abstract class AbstractFtpAdapter extends AbstractAdapter
- {
- /**
- * @var mixed
- */
- protected $connection;
- /**
- * @var string
- */
- protected $host;
- /**
- * @var int
- */
- protected $port = 21;
- /**
- * @var bool
- */
- protected $ssl = false;
- /**
- * @var int
- */
- protected $timeout = 90;
- /**
- * @var bool
- */
- protected $passive = true;
- /**
- * @var string
- */
- protected $separator = '/';
- /**
- * @var string|null
- */
- protected $root;
- /**
- * @var int
- */
- protected $permPublic = 0744;
- /**
- * @var int
- */
- protected $permPrivate = 0700;
- /**
- * @var array
- */
- protected $configurable = [];
- /**
- * @var string
- */
- protected $systemType;
- /**
- * @var SafeStorage
- */
- protected $safeStorage;
- /**
- * True to enable timestamps for FTP servers that return unix-style listings.
- *
- * @var bool
- */
- protected $enableTimestampsOnUnixListings = false;
- /**
- * Constructor.
- *
- * @param array $config
- */
- public function __construct(array $config)
- {
- $this->safeStorage = new SafeStorage();
- $this->setConfig($config);
- }
- /**
- * Set the config.
- *
- * @param array $config
- *
- * @return $this
- */
- public function setConfig(array $config)
- {
- foreach ($this->configurable as $setting) {
- if ( ! isset($config[$setting])) {
- continue;
- }
- $method = 'set' . ucfirst($setting);
- if (method_exists($this, $method)) {
- $this->$method($config[$setting]);
- }
- }
- return $this;
- }
- /**
- * Returns the host.
- *
- * @return string
- */
- public function getHost()
- {
- return $this->host;
- }
- /**
- * Set the host.
- *
- * @param string $host
- *
- * @return $this
- */
- public function setHost($host)
- {
- $this->host = $host;
- return $this;
- }
- /**
- * Set the public permission value.
- *
- * @param int $permPublic
- *
- * @return $this
- */
- public function setPermPublic($permPublic)
- {
- $this->permPublic = $permPublic;
- return $this;
- }
- /**
- * Set the private permission value.
- *
- * @param int $permPrivate
- *
- * @return $this
- */
- public function setPermPrivate($permPrivate)
- {
- $this->permPrivate = $permPrivate;
- return $this;
- }
- /**
- * Returns the ftp port.
- *
- * @return int
- */
- public function getPort()
- {
- return $this->port;
- }
- /**
- * Returns the root folder to work from.
- *
- * @return string
- */
- public function getRoot()
- {
- return $this->root;
- }
- /**
- * Set the ftp port.
- *
- * @param int|string $port
- *
- * @return $this
- */
- public function setPort($port)
- {
- $this->port = (int) $port;
- return $this;
- }
- /**
- * Set the root folder to work from.
- *
- * @param string $root
- *
- * @return $this
- */
- public function setRoot($root)
- {
- $this->root = rtrim($root, '\\/') . $this->separator;
- return $this;
- }
- /**
- * Returns the ftp username.
- *
- * @return string username
- */
- public function getUsername()
- {
- $username = $this->safeStorage->retrieveSafely('username');
- return $username !== null ? $username : 'anonymous';
- }
- /**
- * Set ftp username.
- *
- * @param string $username
- *
- * @return $this
- */
- public function setUsername($username)
- {
- $this->safeStorage->storeSafely('username', $username);
- return $this;
- }
- /**
- * Returns the password.
- *
- * @return string password
- */
- public function getPassword()
- {
- return $this->safeStorage->retrieveSafely('password');
- }
- /**
- * Set the ftp password.
- *
- * @param string $password
- *
- * @return $this
- */
- public function setPassword($password)
- {
- $this->safeStorage->storeSafely('password', $password);
- return $this;
- }
- /**
- * Returns the amount of seconds before the connection will timeout.
- *
- * @return int
- */
- public function getTimeout()
- {
- return $this->timeout;
- }
- /**
- * Set the amount of seconds before the connection should timeout.
- *
- * @param int $timeout
- *
- * @return $this
- */
- public function setTimeout($timeout)
- {
- $this->timeout = (int) $timeout;
- return $this;
- }
- /**
- * Return the FTP system type.
- *
- * @return string
- */
- public function getSystemType()
- {
- return $this->systemType;
- }
- /**
- * Set the FTP system type (windows or unix).
- *
- * @param string $systemType
- *
- * @return $this
- */
- public function setSystemType($systemType)
- {
- $this->systemType = strtolower($systemType);
- return $this;
- }
- /**
- * True to enable timestamps for FTP servers that return unix-style listings.
- *
- * @param bool $bool
- *
- * @return $this
- */
- public function setEnableTimestampsOnUnixListings($bool = false)
- {
- $this->enableTimestampsOnUnixListings = $bool;
- return $this;
- }
- /**
- * @inheritdoc
- */
- public function listContents($directory = '', $recursive = false)
- {
- return $this->listDirectoryContents($directory, $recursive);
- }
- abstract protected function listDirectoryContents($directory, $recursive = false);
- /**
- * Normalize a directory listing.
- *
- * @param array $listing
- * @param string $prefix
- *
- * @return array directory listing
- */
- protected function normalizeListing(array $listing, $prefix = '')
- {
- $base = $prefix;
- $result = [];
- $listing = $this->removeDotDirectories($listing);
- while ($item = array_shift($listing)) {
- if (preg_match('#^.*:$#', $item)) {
- $base = preg_replace('~^\./*|:$~', '', $item);
- continue;
- }
- $result[] = $this->normalizeObject($item, $base);
- }
- return $this->sortListing($result);
- }
- /**
- * Sort a directory listing.
- *
- * @param array $result
- *
- * @return array sorted listing
- */
- protected function sortListing(array $result)
- {
- $compare = function ($one, $two) {
- return strnatcmp($one['path'], $two['path']);
- };
- usort($result, $compare);
- return $result;
- }
- /**
- * Normalize a file entry.
- *
- * @param string $item
- * @param string $base
- *
- * @return array normalized file array
- *
- * @throws NotSupportedException
- */
- protected function normalizeObject($item, $base)
- {
- $systemType = $this->systemType ?: $this->detectSystemType($item);
- if ($systemType === 'unix') {
- return $this->normalizeUnixObject($item, $base);
- } elseif ($systemType === 'windows') {
- return $this->normalizeWindowsObject($item, $base);
- }
- throw NotSupportedException::forFtpSystemType($systemType);
- }
- /**
- * Normalize a Unix file entry.
- *
- * Given $item contains:
- * '-rw-r--r-- 1 ftp ftp 409 Aug 19 09:01 file1.txt'
- *
- * This function will return:
- * [
- * 'type' => 'file',
- * 'path' => 'file1.txt',
- * 'visibility' => 'public',
- * 'size' => 409,
- * 'timestamp' => 1566205260
- * ]
- *
- * @param string $item
- * @param string $base
- *
- * @return array normalized file array
- */
- protected function normalizeUnixObject($item, $base)
- {
- $item = preg_replace('#\s+#', ' ', trim($item), 7);
- if (count(explode(' ', $item, 9)) !== 9) {
- throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
- }
- list($permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name) = explode(' ', $item, 9);
- $type = $this->detectType($permissions);
- $path = $base === '' ? $name : $base . $this->separator . $name;
- if ($type === 'dir') {
- $result = compact('type', 'path');
- if ($this->enableTimestampsOnUnixListings) {
- $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear);
- $result += compact('timestamp');
- }
- return $result;
- }
- $permissions = $this->normalizePermissions($permissions);
- $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
- $size = (int) $size;
- $result = compact('type', 'path', 'visibility', 'size');
- if ($this->enableTimestampsOnUnixListings) {
- $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear);
- $result += compact('timestamp');
- }
- return $result;
- }
- /**
- * Only accurate to the minute (current year), or to the day.
- *
- * Inadequacies in timestamp accuracy are due to limitations of the FTP 'LIST' command
- *
- * Note: The 'MLSD' command is a machine-readable replacement for 'LIST'
- * but many FTP servers do not support it :(
- *
- * @param string $month e.g. 'Aug'
- * @param string $day e.g. '19'
- * @param string $timeOrYear e.g. '09:01' OR '2015'
- *
- * @return int
- */
- protected function normalizeUnixTimestamp($month, $day, $timeOrYear)
- {
- if (is_numeric($timeOrYear)) {
- $year = $timeOrYear;
- $hour = '00';
- $minute = '00';
- $seconds = '00';
- } else {
- $year = date('Y');
- list($hour, $minute) = explode(':', $timeOrYear);
- $seconds = '00';
- }
- $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}");
- return $dateTime->getTimestamp();
- }
- /**
- * Normalize a Windows/DOS file entry.
- *
- * @param string $item
- * @param string $base
- *
- * @return array normalized file array
- */
- protected function normalizeWindowsObject($item, $base)
- {
- $item = preg_replace('#\s+#', ' ', trim($item), 3);
- if (count(explode(' ', $item, 4)) !== 4) {
- throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
- }
- list($date, $time, $size, $name) = explode(' ', $item, 4);
- $path = $base === '' ? $name : $base . $this->separator . $name;
- // 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);
- $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time");
- if ($size === '<DIR>') {
- $type = 'dir';
- return compact('type', 'path', 'timestamp');
- }
- $type = 'file';
- $visibility = AdapterInterface::VISIBILITY_PUBLIC;
- $size = (int) $size;
- return compact('type', 'path', 'visibility', 'size', 'timestamp');
- }
- /**
- * Get the system type from a listing item.
- *
- * @param string $item
- *
- * @return string the system type
- */
- protected function detectSystemType($item)
- {
- return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', trim($item)) ? 'windows' : 'unix';
- }
- /**
- * Get the file type from the permissions.
- *
- * @param string $permissions
- *
- * @return string file type
- */
- protected function detectType($permissions)
- {
- return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
- }
- /**
- * Normalize a permissions string.
- *
- * @param string $permissions
- *
- * @return int
- */
- protected function normalizePermissions($permissions)
- {
- if (is_numeric($permissions)) {
- return ((int) $permissions) & 0777;
- }
- // 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)));
- }
- /**
- * Filter out dot-directories.
- *
- * @param array $list
- *
- * @return array
- */
- public function removeDotDirectories(array $list)
- {
- $filter = function ($line) {
- return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line);
- };
- return array_filter($list, $filter);
- }
- /**
- * @inheritdoc
- */
- public function has($path)
- {
- return $this->getMetadata($path);
- }
- /**
- * @inheritdoc
- */
- public function getSize($path)
- {
- return $this->getMetadata($path);
- }
- /**
- * @inheritdoc
- */
- public function getVisibility($path)
- {
- return $this->getMetadata($path);
- }
- /**
- * Ensure a directory exists.
- *
- * @param string $dirname
- */
- public function ensureDirectory($dirname)
- {
- $dirname = (string) $dirname;
- if ($dirname !== '' && ! $this->has($dirname)) {
- $this->createDir($dirname, new Config());
- }
- }
- /**
- * @return mixed
- */
- public function getConnection()
- {
- if ( ! $this->isConnected()) {
- $this->disconnect();
- $this->connect();
- }
- return $this->connection;
- }
- /**
- * Get the public permission value.
- *
- * @return int
- */
- public function getPermPublic()
- {
- return $this->permPublic;
- }
- /**
- * Get the private permission value.
- *
- * @return int
- */
- public function getPermPrivate()
- {
- return $this->permPrivate;
- }
- /**
- * Disconnect on destruction.
- */
- public function __destruct()
- {
- $this->disconnect();
- }
- /**
- * Establish a connection.
- */
- abstract public function connect();
- /**
- * Close the connection.
- */
- abstract public function disconnect();
- /**
- * Check if a connection is active.
- *
- * @return bool
- */
- abstract public function isConnected();
- protected function escapePath($path)
- {
- return str_replace(['*', '[', ']'], ['\\*', '\\[', '\\]'], $path);
- }
- }
|