LICENSE 0000666 00000002047 13616610423 0005562 0 ustar 00 Copyright (c) 2013-2019 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. SECURITY.md 0000666 00000000654 13616610423 0006350 0 ustar 00 # Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 1.0.x | :white_check_mark: | | 2.0.x | :x: | ## Reporting a Vulnerability When you've encountered a security vulnerability, please disclose it securely. The security process is described at: [https://flysystem.thephpleague.com/docs/security/](https://flysystem.thephpleague.com/docs/security/) src/Plugin/AbstractPlugin.php 0000666 00000000744 13616610423 0012237 0 ustar 00 filesystem = $filesystem; } } src/Plugin/EmptyDir.php 0000666 00000001254 13616610423 0011047 0 ustar 00 filesystem->listContents($dirname, false); foreach ($listing as $item) { if ($item['type'] === 'dir') { $this->filesystem->deleteDir($item['path']); } else { $this->filesystem->delete($item['path']); } } } } src/Plugin/ForcedRename.php 0000666 00000002042 13616610423 0011640 0 ustar 00 filesystem->delete($newpath); } catch (FileNotFoundException $e) { // The destination path does not exist. That's ok. $deleted = true; } if ($deleted) { return $this->filesystem->rename($path, $newpath); } return false; } } src/Plugin/ListWith.php 0000666 00000002651 13616610423 0011063 0 ustar 00 filesystem->listContents($directory, $recursive); foreach ($contents as $index => $object) { if ($object['type'] === 'file') { $missingKeys = array_diff($keys, array_keys($object)); $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object); } } return $contents; } /** * Get a meta-data value by key name. * * @param array $object * @param string $key * * @return array */ protected function getMetadataByName(array $object, $key) { $method = 'get' . ucfirst($key); if ( ! method_exists($this->filesystem, $method)) { throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key); } $object[$key] = $this->filesystem->{$method}($object['path']); return $object; } } src/Plugin/ListFiles.php 0000666 00000001276 13616610423 0011214 0 ustar 00 filesystem->listContents($directory, $recursive); $filter = function ($object) { return $object['type'] === 'file'; }; return array_values(array_filter($contents, $filter)); } } src/Plugin/GetWithMetadata.php 0000666 00000002243 13616610423 0012325 0 ustar 00 filesystem->getMetadata($path); if ( ! $object) { return false; } $keys = array_diff($metadata, array_keys($object)); foreach ($keys as $key) { if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) { throw new InvalidArgumentException('Could not fetch metadata: ' . $key); } $object[$key] = $this->filesystem->{$method}($path); } return $object; } } src/Plugin/ForcedCopy.php 0000666 00000002021 13616610423 0011340 0 ustar 00 filesystem->delete($newpath); } catch (FileNotFoundException $e) { // The destination path does not exist. That's ok. $deleted = true; } if ($deleted) { return $this->filesystem->copy($path, $newpath); } return false; } } src/Plugin/ListPaths.php 0000666 00000001235 13616610423 0011224 0 ustar 00 filesystem->listContents($directory, $recursive); foreach ($contents as $object) { $result[] = $object['path']; } return $result; } } src/Plugin/PluggableTrait.php 0000666 00000004330 13616610423 0012216 0 ustar 00 plugins[$plugin->getMethod()] = $plugin; return $this; } /** * Find a specific plugin. * * @param string $method * * @throws PluginNotFoundException * * @return PluginInterface */ protected function findPlugin($method) { if ( ! isset($this->plugins[$method])) { throw new PluginNotFoundException('Plugin not found for method: ' . $method); } return $this->plugins[$method]; } /** * Invoke a plugin by method name. * * @param string $method * @param array $arguments * @param FilesystemInterface $filesystem * * @throws PluginNotFoundException * * @return mixed */ protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem) { $plugin = $this->findPlugin($method); $plugin->setFilesystem($filesystem); $callback = [$plugin, 'handle']; return call_user_func_array($callback, $arguments); } /** * Plugins pass-through. * * @param string $method * @param array $arguments * * @throws BadMethodCallException * * @return mixed */ public function __call($method, array $arguments) { try { return $this->invokePlugin($method, $arguments, $this); } catch (PluginNotFoundException $e) { throw new BadMethodCallException( 'Call to undefined method ' . get_class($this) . '::' . $method ); } } } src/Plugin/PluginNotFoundException.php 0000666 00000000267 13616610423 0014107 0 ustar 00 getPathname()); } /** * Create a new exception for a link. * * @param string $systemType * * @return static */ public static function forFtpSystemType($systemType) { $message = "The FTP system type '$systemType' is currently not supported."; return new static($message); } } src/ConfigAwareTrait.php 0000666 00000001523 13616610423 0011244 0 ustar 00 config = $config ? Util::ensureConfig($config) : new Config; } /** * Get the Config. * * @return Config config object */ public function getConfig() { return $this->config; } /** * Convert a config array to a Config object with the correct fallback. * * @param array $config * * @return Config */ protected function prepareConfig(array $config) { $config = new Config($config); $config->setFallback($this->getConfig()); return $config; } } src/Util.php 0000666 00000020610 13616610423 0006766 0 ustar 00 '']; } /** * Normalize a dirname return value. * * @param string $dirname * * @return string normalized dirname */ public static function normalizeDirname($dirname) { return $dirname === '.' ? '' : $dirname; } /** * Get a normalized dirname from a path. * * @param string $path * * @return string dirname */ public static function dirname($path) { return static::normalizeDirname(dirname($path)); } /** * Map result arrays. * * @param array $object * @param array $map * * @return array mapped result */ public static function map(array $object, array $map) { $result = []; foreach ($map as $from => $to) { if ( ! isset($object[$from])) { continue; } $result[$to] = $object[$from]; } return $result; } /** * Normalize path. * * @param string $path * * @throws LogicException * * @return string */ public static function normalizePath($path) { return static::normalizeRelativePath($path); } /** * Normalize relative directories in a path. * * @param string $path * * @throws LogicException * * @return string */ public static function normalizeRelativePath($path) { $path = str_replace('\\', '/', $path); $path = static::removeFunkyWhiteSpace($path); $parts = []; foreach (explode('/', $path) as $part) { switch ($part) { case '': case '.': break; case '..': if (empty($parts)) { throw new LogicException( 'Path is outside of the defined root, path: [' . $path . ']' ); } array_pop($parts); break; default: $parts[] = $part; break; } } return implode('/', $parts); } /** * Removes unprintable characters and invalid unicode characters. * * @param string $path * * @return string $path */ protected static function removeFunkyWhiteSpace($path) { // 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 = preg_replace('#\p{C}+|^\./#u', '', $path); } return $path; } /** * Normalize prefix. * * @param string $prefix * @param string $separator * * @return string normalized path */ public static function normalizePrefix($prefix, $separator) { return rtrim($prefix, $separator) . $separator; } /** * Get content size. * * @param string $contents * * @return int content size */ public static function contentSize($contents) { return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents); } /** * Guess MIME Type based on the path of the file and it's content. * * @param string $path * @param string|resource $content * * @return string|null MIME Type or NULL if no extension detected */ public static function guessMimeType($path, $content) { $mimeType = MimeType::detectByContent($content); if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) { return $mimeType; } return MimeType::detectByFilename($path); } /** * Emulate directories. * * @param array $listing * * @return array listing with emulated directories */ public static function emulateDirectories(array $listing) { $directories = []; $listedDirectories = []; foreach ($listing as $object) { list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories); } $directories = array_diff(array_unique($directories), array_unique($listedDirectories)); foreach ($directories as $directory) { $listing[] = static::pathinfo($directory) + ['type' => 'dir']; } return $listing; } /** * Ensure a Config instance. * * @param null|array|Config $config * * @return Config config instance * * @throw LogicException */ public static function ensureConfig($config) { if ($config === null) { return new Config(); } if ($config instanceof Config) { return $config; } if (is_array($config)) { return new Config($config); } throw new LogicException('A config should either be an array or a Flysystem\Config object.'); } /** * Rewind a stream. * * @param resource $resource */ public static function rewindStream($resource) { if (ftell($resource) !== 0 && static::isSeekableStream($resource)) { rewind($resource); } } public static function isSeekableStream($resource) { $metadata = stream_get_meta_data($resource); return $metadata['seekable']; } /** * Get the size of a stream. * * @param resource $resource * * @return int|null stream size */ public static function getStreamSize($resource) { $stat = fstat($resource); if ( ! is_array($stat) || ! isset($stat['size'])) { return null; } return $stat['size']; } /** * Emulate the directories of a single object. * * @param array $object * @param array $directories * @param array $listedDirectories * * @return array */ protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories) { if ($object['type'] === 'dir') { $listedDirectories[] = $object['path']; } if ( ! isset($object['dirname']) || trim($object['dirname']) === '') { return [$directories, $listedDirectories]; } $parent = $object['dirname']; while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories)) { $directories[] = $parent; $parent = static::dirname($parent); } if (isset($object['type']) && $object['type'] === 'dir') { $listedDirectories[] = $object['path']; return [$directories, $listedDirectories]; } return [$directories, $listedDirectories]; } /** * Returns the trailing name component of the path. * * @param string $path * * @return string */ private static function basename($path) { $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/'; $path = rtrim($path, $separators); $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path); if (DIRECTORY_SEPARATOR === '/') { return $basename; } // @codeCoverageIgnoreStart // Extra Windows path munging. This is tested via AppVeyor, but code // coverage is not reported. // Handle relative paths with drive letters. c:file.txt. while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) { $basename = substr($basename, 2); } // Remove colon for standalone drive letter names. if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) { $basename = rtrim($basename, ':'); } return $basename; // @codeCoverageIgnoreEnd } } src/Exception.php 0000666 00000000161 13616610423 0010006 0 ustar 00 getRealPath() ) ); } } src/InvalidRootException.php 0000666 00000000222 13616610423 0012157 0 ustar 00 path = $path; $this->filesystem = $filesystem; } /** * Check whether the entree is a directory. * * @return bool */ public function isDir() { return $this->getType() === 'dir'; } /** * Check whether the entree is a file. * * @return bool */ public function isFile() { return $this->getType() === 'file'; } /** * Retrieve the entree type (file|dir). * * @return string file or dir */ public function getType() { $metadata = $this->filesystem->getMetadata($this->path); return $metadata ? $metadata['type'] : 'dir'; } /** * Set the Filesystem object. * * @param FilesystemInterface $filesystem * * @return $this */ public function setFilesystem(FilesystemInterface $filesystem) { $this->filesystem = $filesystem; return $this; } /** * Retrieve the Filesystem object. * * @return FilesystemInterface */ public function getFilesystem() { return $this->filesystem; } /** * Set the entree path. * * @param string $path * * @return $this */ public function setPath($path) { $this->path = $path; return $this; } /** * Retrieve the entree path. * * @return string path */ public function getPath() { return $this->path; } /** * Plugins pass-through. * * @param string $method * @param array $arguments * * @return mixed */ public function __call($method, array $arguments) { array_unshift($arguments, $this->path); $callback = [$this->filesystem, $method]; try { return call_user_func_array($callback, $arguments); } catch (BadMethodCallException $e) { throw new BadMethodCallException( 'Call to undefined method ' . get_called_class() . '::' . $method ); } } } src/Config.php 0000666 00000003701 13616610423 0007260 0 ustar 00 settings = $settings; } /** * Get a setting. * * @param string $key * @param mixed $default * * @return mixed config setting or default when not found */ public function get($key, $default = null) { if ( ! array_key_exists($key, $this->settings)) { return $this->getDefault($key, $default); } return $this->settings[$key]; } /** * Check if an item exists by key. * * @param string $key * * @return bool */ public function has($key) { if (array_key_exists($key, $this->settings)) { return true; } return $this->fallback instanceof Config ? $this->fallback->has($key) : false; } /** * Try to retrieve a default setting from a config fallback. * * @param string $key * @param mixed $default * * @return mixed config setting or default when not found */ protected function getDefault($key, $default) { if ( ! $this->fallback) { return $default; } return $this->fallback->get($key, $default); } /** * Set a setting. * * @param string $key * @param mixed $value * * @return $this */ public function set($key, $value) { $this->settings[$key] = $value; return $this; } /** * Set the fallback. * * @param Config $fallback * * @return $this */ public function setFallback(Config $fallback) { $this->fallback = $fallback; return $this; } } src/FilesystemNotFoundException.php 0000666 00000000327 13616610423 0013534 0 ustar 00 path = $path; parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous); } /** * Get the path which was found. * * @return string */ public function getPath() { return $this->path; } } src/Util/MimeType.php 0000666 00000022475 13616610423 0010532 0 ustar 00 'application/mac-binhex40', 'cpt' => 'application/mac-compactpro', 'csv' => 'text/csv', 'bin' => 'application/octet-stream', 'dms' => 'application/octet-stream', 'lha' => 'application/octet-stream', 'lzh' => 'application/octet-stream', 'exe' => 'application/octet-stream', 'class' => 'application/octet-stream', 'psd' => 'application/x-photoshop', 'so' => 'application/octet-stream', 'sea' => 'application/octet-stream', 'dll' => 'application/octet-stream', 'oda' => 'application/oda', 'pdf' => 'application/pdf', 'ai' => 'application/pdf', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', 'ps' => 'application/postscript', 'smi' => 'application/smil', 'smil' => 'application/smil', 'mif' => 'application/vnd.mif', 'xls' => 'application/vnd.ms-excel', 'xlt' => 'application/vnd.ms-excel', 'xla' => 'application/vnd.ms-excel', 'ppt' => 'application/powerpoint', 'pot' => 'application/vnd.ms-powerpoint', 'pps' => 'application/vnd.ms-powerpoint', 'ppa' => 'application/vnd.ms-powerpoint', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'wbxml' => 'application/wbxml', 'wmlc' => 'application/wmlc', 'dcr' => 'application/x-director', 'dir' => 'application/x-director', 'dxr' => 'application/x-director', 'dvi' => 'application/x-dvi', 'gtar' => 'application/x-gtar', 'gz' => 'application/x-gzip', 'gzip' => 'application/x-gzip', 'php' => 'application/x-httpd-php', 'php4' => 'application/x-httpd-php', 'php3' => 'application/x-httpd-php', 'phtml' => 'application/x-httpd-php', 'phps' => 'application/x-httpd-php-source', 'js' => 'application/javascript', 'swf' => 'application/x-shockwave-flash', 'sit' => 'application/x-stuffit', 'tar' => 'application/x-tar', 'tgz' => 'application/x-tar', 'z' => 'application/x-compress', 'xhtml' => 'application/xhtml+xml', 'xht' => 'application/xhtml+xml', 'rdf' => 'application/rdf+xml', 'zip' => 'application/x-zip', 'rar' => 'application/x-rar', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mpga' => 'audio/mpeg', 'mp2' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'aif' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'ram' => 'audio/x-pn-realaudio', 'rm' => 'audio/x-pn-realaudio', 'rpm' => 'audio/x-pn-realaudio-plugin', 'ra' => 'audio/x-realaudio', 'rv' => 'video/vnd.rn-realvideo', 'wav' => 'audio/x-wav', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpe' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'bmp' => 'image/bmp', 'tiff' => 'image/tiff', 'tif' => 'image/tiff', 'svg' => 'image/svg+xml', 'css' => 'text/css', 'html' => 'text/html', 'htm' => 'text/html', 'shtml' => 'text/html', 'txt' => 'text/plain', 'text' => 'text/plain', 'log' => 'text/plain', 'rtx' => 'text/richtext', 'rtf' => 'text/rtf', 'xml' => 'application/xml', 'xsl' => 'application/xml', 'dmn' => 'application/octet-stream', 'bpmn' => 'application/octet-stream', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpe' => 'video/mpeg', 'qt' => 'video/quicktime', 'mov' => 'video/quicktime', 'avi' => 'video/x-msvideo', 'movie' => 'video/x-sgi-movie', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', 'dot' => 'application/msword', 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'word' => 'application/msword', 'xl' => 'application/excel', 'eml' => 'message/rfc822', 'json' => 'application/json', 'pem' => 'application/x-x509-user-cert', 'p10' => 'application/x-pkcs10', 'p12' => 'application/x-pkcs12', 'p7a' => 'application/x-pkcs7-signature', 'p7c' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', 'p7r' => 'application/x-pkcs7-certreqresp', 'p7s' => 'application/pkcs7-signature', 'crt' => 'application/x-x509-ca-cert', 'crl' => 'application/pkix-crl', 'der' => 'application/x-x509-ca-cert', 'kdb' => 'application/octet-stream', 'pgp' => 'application/pgp', 'gpg' => 'application/gpg-keys', 'sst' => 'application/octet-stream', 'csr' => 'application/octet-stream', 'rsa' => 'application/x-pkcs7', 'cer' => 'application/pkix-cert', '3g2' => 'video/3gpp2', '3gp' => 'video/3gp', 'mp4' => 'video/mp4', 'm4a' => 'audio/x-m4a', 'f4v' => 'video/mp4', 'webm' => 'video/webm', 'aac' => 'audio/x-acc', 'm4u' => 'application/vnd.mpegurl', 'm3u' => 'text/plain', 'xspf' => 'application/xspf+xml', 'vlc' => 'application/videolan', 'wmv' => 'video/x-ms-wmv', 'au' => 'audio/x-au', 'ac3' => 'audio/ac3', 'flac' => 'audio/x-flac', 'ogg' => 'audio/ogg', 'kmz' => 'application/vnd.google-earth.kmz', 'kml' => 'application/vnd.google-earth.kml+xml', 'ics' => 'text/calendar', 'zsh' => 'text/x-scriptzsh', '7zip' => 'application/x-7z-compressed', 'cdr' => 'application/cdr', 'wma' => 'audio/x-ms-wma', 'jar' => 'application/java-archive', 'tex' => 'application/x-tex', 'latex' => 'application/x-latex', 'odt' => 'application/vnd.oasis.opendocument.text', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'odp' => 'application/vnd.oasis.opendocument.presentation', 'odg' => 'application/vnd.oasis.opendocument.graphics', 'odc' => 'application/vnd.oasis.opendocument.chart', 'odf' => 'application/vnd.oasis.opendocument.formula', 'odi' => 'application/vnd.oasis.opendocument.image', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'odb' => 'application/vnd.oasis.opendocument.database', 'ott' => 'application/vnd.oasis.opendocument.text-template', ]; /** * Detects MIME Type based on given content. * * @param mixed $content * * @return string|null MIME Type or NULL if no mime type detected */ public static function detectByContent($content) { if ( ! class_exists('finfo') || ! is_string($content)) { return null; } try { $finfo = new finfo(FILEINFO_MIME_TYPE); return $finfo->buffer($content) ?: null; // @codeCoverageIgnoreStart } catch (ErrorException $e) { // This is caused by an array to string conversion error. } } // @codeCoverageIgnoreEnd /** * Detects MIME Type based on file extension. * * @param string $extension * * @return string|null MIME Type or NULL if no extension detected */ public static function detectByFileExtension($extension) { return isset(static::$extensionToMimeTypeMap[$extension]) ? static::$extensionToMimeTypeMap[$extension] : 'text/plain'; } /** * @param string $filename * * @return string|null MIME Type or NULL if no extension detected */ public static function detectByFilename($filename) { $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); return empty($extension) ? 'text/plain' : static::detectByFileExtension($extension); } /** * @return array Map of file extension to MIME Type */ public static function getExtensionToMimeTypeMap() { return static::$extensionToMimeTypeMap; } } src/Util/ContentListingFormatter.php 0000666 00000005034 13616610423 0013621 0 ustar 00 directory = rtrim($directory, '/'); $this->recursive = $recursive; $this->caseSensitive = $caseSensitive; } /** * Format contents listing. * * @param array $listing * * @return array */ public function formatListing(array $listing) { $listing = array_filter(array_map([$this, 'addPathInfo'], $listing), [$this, 'isEntryOutOfScope']); return $this->sortListing(array_values($listing)); } private function addPathInfo(array $entry) { return $entry + Util::pathinfo($entry['path']); } /** * Determine if the entry is out of scope. * * @param array $entry * * @return bool */ private function isEntryOutOfScope(array $entry) { if (empty($entry['path']) && $entry['path'] !== '0') { return false; } if ($this->recursive) { return $this->residesInDirectory($entry); } return $this->isDirectChild($entry); } /** * Check if the entry resides within the parent directory. * * @param array $entry * * @return bool */ private function residesInDirectory(array $entry) { if ($this->directory === '') { return true; } return $this->caseSensitive ? strpos($entry['path'], $this->directory . '/') === 0 : stripos($entry['path'], $this->directory . '/') === 0; } /** * Check if the entry is a direct child of the directory. * * @param array $entry * * @return bool */ private function isDirectChild(array $entry) { return $this->caseSensitive ? $entry['dirname'] === $this->directory : strcasecmp($this->directory, $entry['dirname']) === 0; } /** * @param array $listing * * @return array */ private function sortListing(array $listing) { usort($listing, function ($a, $b) { return strcasecmp($a['path'], $b['path']); }); return $listing; } } src/Util/StreamHasher.php 0000666 00000001121 13616610423 0011350 0 ustar 00 algo = $algo; } /** * @param resource $resource * * @return string */ public function hash($resource) { rewind($resource); $context = hash_init($this->algo); hash_update_stream($context, $resource); fclose($resource); return hash_final($context); } } src/FilesystemInterface.php 0000666 00000017007 13616610423 0012024 0 ustar 00 Filesystem,] * * @throws InvalidArgumentException */ public function __construct(array $filesystems = []) { $this->mountFilesystems($filesystems); } /** * Mount filesystems. * * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] * * @throws InvalidArgumentException * * @return $this */ public function mountFilesystems(array $filesystems) { foreach ($filesystems as $prefix => $filesystem) { $this->mountFilesystem($prefix, $filesystem); } return $this; } /** * Mount filesystems. * * @param string $prefix * @param FilesystemInterface $filesystem * * @throws InvalidArgumentException * * @return $this */ public function mountFilesystem($prefix, FilesystemInterface $filesystem) { if ( ! is_string($prefix)) { throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.'); } $this->filesystems[$prefix] = $filesystem; return $this; } /** * Get the filesystem with the corresponding prefix. * * @param string $prefix * * @throws FilesystemNotFoundException * * @return FilesystemInterface */ public function getFilesystem($prefix) { if ( ! isset($this->filesystems[$prefix])) { throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix); } return $this->filesystems[$prefix]; } /** * Retrieve the prefix from an arguments array. * * @param array $arguments * * @throws InvalidArgumentException * * @return array [:prefix, :arguments] */ public function filterPrefix(array $arguments) { if (empty($arguments)) { throw new InvalidArgumentException('At least one argument needed'); } $path = array_shift($arguments); if ( ! is_string($path)) { throw new InvalidArgumentException('First argument should be a string'); } list($prefix, $path) = $this->getPrefixAndPath($path); array_unshift($arguments, $path); return [$prefix, $arguments]; } /** * @param string $directory * @param bool $recursive * * @throws InvalidArgumentException * @throws FilesystemNotFoundException * * @return array */ public function listContents($directory = '', $recursive = false) { list($prefix, $directory) = $this->getPrefixAndPath($directory); $filesystem = $this->getFilesystem($prefix); $result = $filesystem->listContents($directory, $recursive); foreach ($result as &$file) { $file['filesystem'] = $prefix; } return $result; } /** * Call forwarder. * * @param string $method * @param array $arguments * * @throws InvalidArgumentException * @throws FilesystemNotFoundException * * @return mixed */ public function __call($method, $arguments) { list($prefix, $arguments) = $this->filterPrefix($arguments); return $this->invokePluginOnFilesystem($method, $arguments, $prefix); } /** * @param string $from * @param string $to * @param array $config * * @throws InvalidArgumentException * @throws FilesystemNotFoundException * @throws FileExistsException * * @return bool */ public function copy($from, $to, array $config = []) { list($prefixFrom, $from) = $this->getPrefixAndPath($from); $buffer = $this->getFilesystem($prefixFrom)->readStream($from); if ($buffer === false) { return false; } list($prefixTo, $to) = $this->getPrefixAndPath($to); $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config); if (is_resource($buffer)) { fclose($buffer); } return $result; } /** * List with plugin adapter. * * @param array $keys * @param string $directory * @param bool $recursive * * @throws InvalidArgumentException * @throws FilesystemNotFoundException * * @return array */ public function listWith(array $keys = [], $directory = '', $recursive = false) { list($prefix, $directory) = $this->getPrefixAndPath($directory); $arguments = [$keys, $directory, $recursive]; return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix); } /** * Move a file. * * @param string $from * @param string $to * @param array $config * * @throws InvalidArgumentException * @throws FilesystemNotFoundException * * @return bool */ public function move($from, $to, array $config = []) { list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from); list($prefixTo, $pathTo) = $this->getPrefixAndPath($to); if ($prefixFrom === $prefixTo) { $filesystem = $this->getFilesystem($prefixFrom); $renamed = $filesystem->rename($pathFrom, $pathTo); if ($renamed && isset($config['visibility'])) { return $filesystem->setVisibility($pathTo, $config['visibility']); } return $renamed; } $copied = $this->copy($from, $to, $config); if ($copied) { return $this->delete($from); } return false; } /** * Invoke a plugin on a filesystem mounted on a given prefix. * * @param string $method * @param array $arguments * @param string $prefix * * @throws FilesystemNotFoundException * * @return mixed */ public function invokePluginOnFilesystem($method, $arguments, $prefix) { $filesystem = $this->getFilesystem($prefix); try { return $this->invokePlugin($method, $arguments, $filesystem); } catch (PluginNotFoundException $e) { // Let it pass, it's ok, don't panic. } $callback = [$filesystem, $method]; return call_user_func_array($callback, $arguments); } /** * @param string $path * * @throws InvalidArgumentException * * @return string[] [:prefix, :path] */ protected function getPrefixAndPath($path) { if (strpos($path, '://') < 1) { throw new InvalidArgumentException('No prefix detected in path: ' . $path); } return explode('://', $path, 2); } /** * Check whether a file exists. * * @param string $path * * @return bool */ public function has($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->has($path); } /** * Read a file. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return string|false The file contents or false on failure. */ public function read($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->read($path); } /** * Retrieves a read-stream for a path. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return resource|false The path resource or false on failure. */ public function readStream($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->readStream($path); } /** * Get a file's metadata. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return array|false The file metadata or false on failure. */ public function getMetadata($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->getMetadata($path); } /** * Get a file's size. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return int|false The file size or false on failure. */ public function getSize($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->getSize($path); } /** * Get a file's mime-type. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return string|false The file mime-type or false on failure. */ public function getMimetype($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->getMimetype($path); } /** * Get a file's timestamp. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return string|false The timestamp or false on failure. */ public function getTimestamp($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->getTimestamp($path); } /** * Get a file's visibility. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return string|false The visibility (public|private) or false on failure. */ public function getVisibility($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->getVisibility($path); } /** * Write a new file. * * @param string $path The path of the new file. * @param string $contents The file contents. * @param array $config An optional configuration array. * * @throws FileExistsException * * @return bool True on success, false on failure. */ public function write($path, $contents, array $config = []) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->write($path, $contents, $config); } /** * Write a new file using a stream. * * @param string $path The path of the new file. * @param resource $resource The file handle. * @param array $config An optional configuration array. * * @throws InvalidArgumentException If $resource is not a file handle. * @throws FileExistsException * * @return bool True on success, false on failure. */ public function writeStream($path, $resource, array $config = []) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->writeStream($path, $resource, $config); } /** * Update an existing file. * * @param string $path The path of the existing file. * @param string $contents The file contents. * @param array $config An optional configuration array. * * @throws FileNotFoundException * * @return bool True on success, false on failure. */ public function update($path, $contents, array $config = []) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->update($path, $contents, $config); } /** * Update an existing file using a stream. * * @param string $path The path of the existing file. * @param resource $resource The file handle. * @param array $config An optional configuration array. * * @throws InvalidArgumentException If $resource is not a file handle. * @throws FileNotFoundException * * @return bool True on success, false on failure. */ public function updateStream($path, $resource, array $config = []) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->updateStream($path, $resource, $config); } /** * Rename a file. * * @param string $path Path to the existing file. * @param string $newpath The new path of the file. * * @throws FileExistsException Thrown if $newpath exists. * @throws FileNotFoundException Thrown if $path does not exist. * * @return bool True on success, false on failure. */ public function rename($path, $newpath) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->rename($path, $newpath); } /** * Delete a file. * * @param string $path * * @throws FileNotFoundException * * @return bool True on success, false on failure. */ public function delete($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->delete($path); } /** * Delete a directory. * * @param string $dirname * * @throws RootViolationException Thrown if $dirname is empty. * * @return bool True on success, false on failure. */ public function deleteDir($dirname) { list($prefix, $dirname) = $this->getPrefixAndPath($dirname); return $this->getFilesystem($prefix)->deleteDir($dirname); } /** * Create a directory. * * @param string $dirname The name of the new directory. * @param array $config An optional configuration array. * * @return bool True on success, false on failure. */ public function createDir($dirname, array $config = []) { list($prefix, $dirname) = $this->getPrefixAndPath($dirname); return $this->getFilesystem($prefix)->createDir($dirname); } /** * Set the visibility for a file. * * @param string $path The path to the file. * @param string $visibility One of 'public' or 'private'. * * @throws FileNotFoundException * * @return bool True on success, false on failure. */ public function setVisibility($path, $visibility) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->setVisibility($path, $visibility); } /** * Create a file or update if exists. * * @param string $path The path to the file. * @param string $contents The file contents. * @param array $config An optional configuration array. * * @return bool True on success, false on failure. */ public function put($path, $contents, array $config = []) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->put($path, $contents, $config); } /** * Create a file or update if exists. * * @param string $path The path to the file. * @param resource $resource The file handle. * @param array $config An optional configuration array. * * @throws InvalidArgumentException Thrown if $resource is not a resource. * * @return bool True on success, false on failure. */ public function putStream($path, $resource, array $config = []) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->putStream($path, $resource, $config); } /** * Read and delete a file. * * @param string $path The path to the file. * * @throws FileNotFoundException * * @return string|false The file contents, or false on failure. */ public function readAndDelete($path) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->readAndDelete($path); } /** * Get a file/directory handler. * * @deprecated * * @param string $path The path to the file. * @param Handler $handler An optional existing handler to populate. * * @return Handler Either a file or directory handler. */ public function get($path, Handler $handler = null) { list($prefix, $path) = $this->getPrefixAndPath($path); return $this->getFilesystem($prefix)->get($path); } } src/File.php 0000666 00000010124 13616610423 0006727 0 ustar 00 filesystem->has($this->path); } /** * Read the file. * * @return string|false file contents */ public function read() { return $this->filesystem->read($this->path); } /** * Read the file as a stream. * * @return resource|false file stream */ public function readStream() { return $this->filesystem->readStream($this->path); } /** * Write the new file. * * @param string $content * * @return bool success boolean */ public function write($content) { return $this->filesystem->write($this->path, $content); } /** * Write the new file using a stream. * * @param resource $resource * * @return bool success boolean */ public function writeStream($resource) { return $this->filesystem->writeStream($this->path, $resource); } /** * Update the file contents. * * @param string $content * * @return bool success boolean */ public function update($content) { return $this->filesystem->update($this->path, $content); } /** * Update the file contents with a stream. * * @param resource $resource * * @return bool success boolean */ public function updateStream($resource) { return $this->filesystem->updateStream($this->path, $resource); } /** * Create the file or update if exists. * * @param string $content * * @return bool success boolean */ public function put($content) { return $this->filesystem->put($this->path, $content); } /** * Create the file or update if exists using a stream. * * @param resource $resource * * @return bool success boolean */ public function putStream($resource) { return $this->filesystem->putStream($this->path, $resource); } /** * Rename the file. * * @param string $newpath * * @return bool success boolean */ public function rename($newpath) { if ($this->filesystem->rename($this->path, $newpath)) { $this->path = $newpath; return true; } return false; } /** * Copy the file. * * @param string $newpath * * @return File|false new file or false */ public function copy($newpath) { if ($this->filesystem->copy($this->path, $newpath)) { return new File($this->filesystem, $newpath); } return false; } /** * Get the file's timestamp. * * @return string|false The timestamp or false on failure. */ public function getTimestamp() { return $this->filesystem->getTimestamp($this->path); } /** * Get the file's mimetype. * * @return string|false The file mime-type or false on failure. */ public function getMimetype() { return $this->filesystem->getMimetype($this->path); } /** * Get the file's visibility. * * @return string|false The visibility (public|private) or false on failure. */ public function getVisibility() { return $this->filesystem->getVisibility($this->path); } /** * Get the file's metadata. * * @return array|false The file metadata or false on failure. */ public function getMetadata() { return $this->filesystem->getMetadata($this->path); } /** * Get the file size. * * @return int|false The file size or false on failure. */ public function getSize() { return $this->filesystem->getSize($this->path); } /** * Delete the file. * * @return bool success boolean */ public function delete() { return $this->filesystem->delete($this->path); } } src/ConnectionErrorException.php 0000666 00000000222 13616610423 0013036 0 ustar 00 get('visibility')) { $result['visibility'] = $visibility; } return $result; } /** * @inheritdoc */ public function update($path, $contents, Config $config) { return false; } /** * @inheritdoc */ public function read($path) { return false; } /** * @inheritdoc */ public function rename($path, $newpath) { return false; } /** * @inheritdoc */ public function delete($path) { return false; } /** * @inheritdoc */ public function listContents($directory = '', $recursive = false) { return []; } /** * @inheritdoc */ public function getMetadata($path) { return false; } /** * @inheritdoc */ public function getSize($path) { return false; } /** * @inheritdoc */ public function getMimetype($path) { return false; } /** * @inheritdoc */ public function getTimestamp($path) { return false; } /** * @inheritdoc */ public function getVisibility($path) { return false; } /** * @inheritdoc */ public function setVisibility($path, $visibility) { return compact('visibility'); } /** * @inheritdoc */ public function createDir($dirname, Config $config) { return ['path' => $dirname, 'type' => 'dir']; } /** * @inheritdoc */ public function deleteDir($dirname) { return false; } } src/Adapter/Ftp.php 0000666 00000032734 13616610423 0010174 0 ustar 00 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 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 (substr($response[0], 0, 3) !== '200') { 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 (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'] = $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', 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) ?: [], $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 is_resource($this->connection) && $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 . ' ' . $path); } private function getRawExecResponseCode($command) { $response = @ftp_raw($this->connection, trim($command)); return (int) preg_replace('/\D/', '', implode(' ', $response)); } } src/Adapter/Ftpd.php 0000666 00000002116 13616610423 0010327 0 ustar 00 'dir', 'path' => '']; } if (@ftp_chdir($this->getConnection(), $path) === true) { $this->setConnectionRoot(); return ['type' => 'dir', 'path' => $path]; } if ( ! ($object = ftp_raw($this->getConnection(), 'STAT ' . $path)) || count($object) < 3) { return false; } if (substr($object[1], 0, 5) === "ftpd:") { return false; } return $this->normalizeObject($object[1], ''); } /** * @inheritdoc */ protected function listDirectoryContents($directory, $recursive = true) { $listing = ftp_rawlist($this->getConnection(), $directory, $recursive); if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) { return []; } return $this->normalizeListing($listing, $directory); } } src/Adapter/AbstractFtpAdapter.php 0000666 00000035422 13616610423 0013156 0 ustar 00 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') { return compact('type', 'path'); } $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 === '