Spaces:
Sleeping
Sleeping
| namespace League\Flysystem\Adapter; | |
| use ErrorException; | |
| use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait; | |
| use League\Flysystem\AdapterInterface; | |
| use League\Flysystem\Config; | |
| use League\Flysystem\Util; | |
| use League\Flysystem\Util\MimeType; | |
| use RuntimeException; | |
| class Ftp extends AbstractFtpAdapter | |
| { | |
| use StreamedCopyTrait; | |
| /** | |
| * @var int | |
| */ | |
| protected $transferMode = FTP_BINARY; | |
| /** | |
| * @var null|bool | |
| */ | |
| protected $ignorePassiveAddress = null; | |
| /** | |
| * @var bool | |
| */ | |
| protected $recurseManually = false; | |
| /** | |
| * @var bool | |
| */ | |
| protected $utf8 = false; | |
| /** | |
| * @var array | |
| */ | |
| protected $configurable = [ | |
| 'host', | |
| 'port', | |
| 'username', | |
| 'password', | |
| 'ssl', | |
| 'timeout', | |
| 'root', | |
| 'permPrivate', | |
| 'permPublic', | |
| 'passive', | |
| 'transferMode', | |
| 'systemType', | |
| 'ignorePassiveAddress', | |
| 'recurseManually', | |
| 'utf8', | |
| ]; | |
| /** | |
| * @var bool | |
| */ | |
| protected $isPureFtpd; | |
| /** | |
| * Set the transfer mode. | |
| * | |
| * @param int $mode | |
| * | |
| * @return $this | |
| */ | |
| public function setTransferMode($mode) | |
| { | |
| $this->transferMode = $mode; | |
| return $this; | |
| } | |
| /** | |
| * Set if Ssl is enabled. | |
| * | |
| * @param bool $ssl | |
| * | |
| * @return $this | |
| */ | |
| public function setSsl($ssl) | |
| { | |
| $this->ssl = (bool) $ssl; | |
| return $this; | |
| } | |
| /** | |
| * Set if passive mode should be used. | |
| * | |
| * @param bool $passive | |
| */ | |
| public function setPassive($passive = true) | |
| { | |
| $this->passive = $passive; | |
| } | |
| /** | |
| * @param bool $ignorePassiveAddress | |
| */ | |
| public function setIgnorePassiveAddress($ignorePassiveAddress) | |
| { | |
| $this->ignorePassiveAddress = $ignorePassiveAddress; | |
| } | |
| /** | |
| * @param bool $recurseManually | |
| */ | |
| public function setRecurseManually($recurseManually) | |
| { | |
| $this->recurseManually = $recurseManually; | |
| } | |
| /** | |
| * @param bool $utf8 | |
| */ | |
| public function setUtf8($utf8) | |
| { | |
| $this->utf8 = (bool) $utf8; | |
| } | |
| /** | |
| * Connect to the FTP server. | |
| */ | |
| public function connect() | |
| { | |
| if ($this->ssl) { | |
| $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); | |
| } else { | |
| $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout()); | |
| } | |
| if ( ! $this->connection) { | |
| throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()); | |
| } | |
| $this->login(); | |
| $this->setUtf8Mode(); | |
| $this->setConnectionPassiveMode(); | |
| $this->setConnectionRoot(); | |
| $this->isPureFtpd = $this->isPureFtpdServer(); | |
| } | |
| /** | |
| * Set the connection to UTF-8 mode. | |
| */ | |
| protected function setUtf8Mode() | |
| { | |
| if ($this->utf8) { | |
| $response = ftp_raw($this->connection, "OPTS UTF8 ON"); | |
| if (substr($response[0], 0, 3) !== '200') { | |
| throw new RuntimeException( | |
| 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort() | |
| ); | |
| } | |
| } | |
| } | |
| /** | |
| * Set the connections to passive mode. | |
| * | |
| * @throws RuntimeException | |
| */ | |
| protected function setConnectionPassiveMode() | |
| { | |
| if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) { | |
| ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress); | |
| } | |
| if ( ! ftp_pasv($this->connection, $this->passive)) { | |
| throw new RuntimeException( | |
| 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort() | |
| ); | |
| } | |
| } | |
| /** | |
| * Set the connection root. | |
| */ | |
| protected function setConnectionRoot() | |
| { | |
| $root = $this->getRoot(); | |
| $connection = $this->connection; | |
| if (isset($root) && ! ftp_chdir($connection, $root)) { | |
| throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot()); | |
| } | |
| // Store absolute path for further reference. | |
| // This is needed when creating directories and | |
| // initial root was a relative path, else the root | |
| // would be relative to the chdir'd path. | |
| $this->root = ftp_pwd($connection); | |
| } | |
| /** | |
| * Login. | |
| * | |
| * @throws RuntimeException | |
| */ | |
| protected function login() | |
| { | |
| set_error_handler(function () {}); | |
| $isLoggedIn = ftp_login( | |
| $this->connection, | |
| $this->getUsername(), | |
| $this->getPassword() | |
| ); | |
| restore_error_handler(); | |
| if ( ! $isLoggedIn) { | |
| $this->disconnect(); | |
| throw new RuntimeException( | |
| 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( | |
| ) . ', username: ' . $this->getUsername() | |
| ); | |
| } | |
| } | |
| /** | |
| * Disconnect from the FTP server. | |
| */ | |
| public function disconnect() | |
| { | |
| if (is_resource($this->connection)) { | |
| ftp_close($this->connection); | |
| } | |
| $this->connection = null; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function write($path, $contents, Config $config) | |
| { | |
| $stream = fopen('php://temp', 'w+b'); | |
| fwrite($stream, $contents); | |
| rewind($stream); | |
| $result = $this->writeStream($path, $stream, $config); | |
| fclose($stream); | |
| if ($result === false) { | |
| return false; | |
| } | |
| $result['contents'] = $contents; | |
| $result['mimetype'] = Util::guessMimeType($path, $contents); | |
| return $result; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function writeStream($path, $resource, Config $config) | |
| { | |
| $this->ensureDirectory(Util::dirname($path)); | |
| if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) { | |
| return false; | |
| } | |
| if ($visibility = $config->get('visibility')) { | |
| $this->setVisibility($path, $visibility); | |
| } | |
| $type = 'file'; | |
| return compact('type', 'path', 'visibility'); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function update($path, $contents, Config $config) | |
| { | |
| return $this->write($path, $contents, $config); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function updateStream($path, $resource, Config $config) | |
| { | |
| return $this->writeStream($path, $resource, $config); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function rename($path, $newpath) | |
| { | |
| return ftp_rename($this->getConnection(), $path, $newpath); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function delete($path) | |
| { | |
| return ftp_delete($this->getConnection(), $path); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function deleteDir($dirname) | |
| { | |
| $connection = $this->getConnection(); | |
| $contents = array_reverse($this->listDirectoryContents($dirname)); | |
| foreach ($contents as $object) { | |
| if ($object['type'] === 'file') { | |
| if ( ! ftp_delete($connection, $object['path'])) { | |
| return false; | |
| } | |
| } elseif ( ! ftp_rmdir($connection, $object['path'])) { | |
| return false; | |
| } | |
| } | |
| return ftp_rmdir($connection, $dirname); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function createDir($dirname, Config $config) | |
| { | |
| $connection = $this->getConnection(); | |
| $directories = explode('/', $dirname); | |
| foreach ($directories as $directory) { | |
| if (false === $this->createActualDirectory($directory, $connection)) { | |
| $this->setConnectionRoot(); | |
| return false; | |
| } | |
| ftp_chdir($connection, $directory); | |
| } | |
| $this->setConnectionRoot(); | |
| return ['type' => 'dir', 'path' => $dirname]; | |
| } | |
| /** | |
| * Create a directory. | |
| * | |
| * @param string $directory | |
| * @param resource $connection | |
| * | |
| * @return bool | |
| */ | |
| protected function createActualDirectory($directory, $connection) | |
| { | |
| // List the current directory | |
| $listing = ftp_nlist($connection, '.') ?: []; | |
| foreach ($listing as $key => $item) { | |
| if (preg_match('~^\./.*~', $item)) { | |
| $listing[$key] = substr($item, 2); | |
| } | |
| } | |
| if (in_array($directory, $listing, true)) { | |
| return true; | |
| } | |
| return (boolean) ftp_mkdir($connection, $directory); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function getMetadata($path) | |
| { | |
| $connection = $this->getConnection(); | |
| if ($path === '') { | |
| return ['type' => 'dir', 'path' => '']; | |
| } | |
| if (@ftp_chdir($connection, $path) === true) { | |
| $this->setConnectionRoot(); | |
| return ['type' => 'dir', 'path' => $path]; | |
| } | |
| $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path)); | |
| if (empty($listing) || in_array('total 0', $listing, true)) { | |
| return false; | |
| } | |
| if (preg_match('/.* not found/', $listing[0])) { | |
| return false; | |
| } | |
| if (preg_match('/^total [0-9]*$/', $listing[0])) { | |
| array_shift($listing); | |
| } | |
| return $this->normalizeObject($listing[0], ''); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function getMimetype($path) | |
| { | |
| if ( ! $metadata = $this->getMetadata($path)) { | |
| return false; | |
| } | |
| $metadata['mimetype'] = MimeType::detectByFilename($path); | |
| return $metadata; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function getTimestamp($path) | |
| { | |
| $timestamp = ftp_mdtm($this->getConnection(), $path); | |
| return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function read($path) | |
| { | |
| if ( ! $object = $this->readStream($path)) { | |
| return false; | |
| } | |
| $object['contents'] = stream_get_contents($object['stream']); | |
| fclose($object['stream']); | |
| unset($object['stream']); | |
| return $object; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function readStream($path) | |
| { | |
| $stream = fopen('php://temp', 'w+b'); | |
| $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode); | |
| rewind($stream); | |
| if ( ! $result) { | |
| fclose($stream); | |
| return false; | |
| } | |
| return ['type' => 'file', 'path' => $path, 'stream' => $stream]; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function setVisibility($path, $visibility) | |
| { | |
| $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate(); | |
| if ( ! ftp_chmod($this->getConnection(), $mode, $path)) { | |
| return false; | |
| } | |
| return compact('path', 'visibility'); | |
| } | |
| /** | |
| * @inheritdoc | |
| * | |
| * @param string $directory | |
| */ | |
| protected function listDirectoryContents($directory, $recursive = true) | |
| { | |
| $directory = str_replace('*', '\\*', $directory); | |
| if ($recursive && $this->recurseManually) { | |
| return $this->listDirectoryContentsRecursive($directory); | |
| } | |
| $options = $recursive ? '-alnR' : '-aln'; | |
| $listing = $this->ftpRawlist($options, $directory); | |
| return $listing ? $this->normalizeListing($listing, $directory) : []; | |
| } | |
| /** | |
| * @inheritdoc | |
| * | |
| * @param string $directory | |
| */ | |
| protected function listDirectoryContentsRecursive($directory) | |
| { | |
| $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: []); | |
| $output = []; | |
| foreach ($listing as $directory) { | |
| $output[] = $directory; | |
| if ($directory['type'] !== 'dir') continue; | |
| $output = array_merge($output, $this->listDirectoryContentsRecursive($directory['path'])); | |
| } | |
| return $output; | |
| } | |
| /** | |
| * Check if the connection is open. | |
| * | |
| * @return bool | |
| * @throws ErrorException | |
| */ | |
| public function isConnected() | |
| { | |
| try { | |
| return is_resource($this->connection) && ftp_rawlist($this->connection, '/') !== false; | |
| } catch (ErrorException $e) { | |
| if (strpos($e->getMessage(), 'ftp_rawlist') === false) { | |
| throw $e; | |
| } | |
| return false; | |
| } | |
| } | |
| /** | |
| * @return null|string | |
| */ | |
| protected function isPureFtpdServer() | |
| { | |
| $response = ftp_raw($this->connection, 'HELP'); | |
| return stripos(implode(' ', $response), 'Pure-FTPd') !== false; | |
| } | |
| /** | |
| * The ftp_rawlist function with optional escaping. | |
| * | |
| * @param string $options | |
| * @param string $path | |
| * | |
| * @return array | |
| */ | |
| protected function ftpRawlist($options, $path) | |
| { | |
| $connection = $this->getConnection(); | |
| if ($this->isPureFtpd) { | |
| $path = str_replace(' ', '\ ', $path); | |
| } | |
| return ftp_rawlist($connection, $options . ' ' . $path); | |
| } | |
| } | |