| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- <?php
- namespace League\Flysystem\Adapter;
- use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
- use League\Flysystem\AdapterInterface;
- use League\Flysystem\Config;
- use League\Flysystem\ConnectionErrorException;
- use League\Flysystem\ConnectionRuntimeException;
- use League\Flysystem\InvalidRootException;
- use League\Flysystem\Util;
- use League\Flysystem\Util\MimeType;
- use function in_array;
- 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',
- 'enableTimestampsOnUnixListings',
- ];
- /**
- * @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()
- {
- $tries = 3;
- start_connecting:
- 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) {
- $tries--;
- if ($tries > 0) goto start_connecting;
- throw new ConnectionRuntimeException('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 (!in_array(substr($response[0], 0, 3), ['200', '202'])) {
- throw new ConnectionRuntimeException(
- 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort()
- );
- }
- }
- }
- /**
- * Set the connections to passive mode.
- *
- * @throws ConnectionRuntimeException
- */
- 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 ConnectionRuntimeException(
- '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 ($root && ! ftp_chdir($connection, $root)) {
- throw new InvalidRootException('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 ConnectionRuntimeException
- */
- 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 ConnectionRuntimeException(
- 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
- ) . ', username: ' . $this->getUsername()
- );
- }
- }
- /**
- * Disconnect from the FTP server.
- */
- public function disconnect()
- {
- if ($this->hasFtpConnection()) {
- @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'] = $config->get('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, false));
- foreach ($contents as $object) {
- if ($object['type'] === 'file') {
- if ( ! ftp_delete($connection, $object['path'])) {
- return false;
- }
- } elseif ( ! $this->deleteDir($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)
- {
- if ($path === '') {
- return ['type' => 'dir', 'path' => ''];
- }
- if (@ftp_chdir($this->getConnection(), $path) === true) {
- $this->setConnectionRoot();
- return ['type' => 'dir', 'path' => $path];
- }
- $listing = $this->ftpRawlist('-A', $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)
- {
- 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) ?: [], $directory);
- $output = [];
- foreach ($listing as $item) {
- $output[] = $item;
- if ($item['type'] !== 'dir') {
- continue;
- }
- $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path']));
- }
- return $output;
- }
- /**
- * Check if the connection is open.
- *
- * @return bool
- *
- * @throws ConnectionErrorException
- */
- public function isConnected()
- {
- return $this->hasFtpConnection() && $this->getRawExecResponseCode('NOOP') === 200;
- }
- /**
- * @return bool
- */
- 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 . ' ' . $this->escapePath($path));
- }
- private function getRawExecResponseCode($command)
- {
- $response = @ftp_raw($this->connection, trim($command));
- return (int) preg_replace('/\D/', '', implode(' ', $response));
- }
- private function hasFtpConnection(): bool
- {
- return is_resource($this->connection) || $this->connection instanceof \FTP\Connection;
- }
- }
|