diff --git a/includes/filetransfer/filetransfer.inc b/includes/filetransfer/filetransfer.inc new file mode 100644 index 00000000000..2fb76991308 --- /dev/null +++ b/includes/filetransfer/filetransfer.inc @@ -0,0 +1,102 @@ +username = $settings['username']; + $this->password = $settings['password']; + $this->hostname = isset($settings['hostname']) ? $settings['hostname'] : 'localhost'; + if (isset($settings['port'])) { + $this->port = $settings['port']; + } + } + + /** + * Implementation of the magic __get() method. If the connection isn't set to + * anything, this will call the connect() method and set it to and return the + * result; afterwards, the connection will be returned directly without using + * this method. + */ + function __get($name) { + static $connection; + if ($name == 'connection') { + $this->connection = $this->connect(); + return $this->connection; + } + } + + /** + * Copies a directory. + * + * @param $source + * The source path. + * @param $destination + * The destination path. + */ + protected function copyDirectory($source, $destination) { + $this->createDirectory($destination . basename($source)); + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { + $relative_path = basename($source) . substr($filename, strlen($source)); + if ($file->isDir()) { + $this->createDirectory($destination . $relative_path); + } + else { + $this->copyFile($file->getPathName(), $destination . $relative_path); + } + } + } + + /** + * Creates a directory. + * + * @param $directory + * The directory to be created. + */ + abstract function createDirectory($directory); + + /** + * Removes a directory. + * + * @param $directory + * The directory to be removed. + */ + abstract function removeDirectory($directory); + + /** + * Copies a file. + * + * @param $source + * The source file. + * @param $destination + * The destination file. + */ + abstract function copyFile($source, $destination); + + + /** + * Removes a file. + * + * @param $destination + * The destination file to be removed. + */ + abstract function removeFile($destination); +} + +/** + * FileTransferException class. + */ +class FileTransferException extends Exception { +} diff --git a/includes/filetransfer/ftp.inc b/includes/filetransfer/ftp.inc new file mode 100644 index 00000000000..d21e88becf3 --- /dev/null +++ b/includes/filetransfer/ftp.inc @@ -0,0 +1,128 @@ +port = 21; + parent::__construct($settings); + } +} + +/** + * Connection class using the FTP URL wrapper. + */ +class FileTransferFTPWrapper extends FileTransfer { + function connect() { + $this->connection = 'ftp://' . urlencode($this->username) . ':' . urlencode($this->password) . '@' . $this->hostname . ':' . $this->port . '/'; + if (!is_dir($this->connection)) { + throw new FileTransferException('FTP Connection failed.'); + } + } + + function createDirectory($directory) { + if (!@createDirectory($directory)) { + $exception = new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory)); + throw $exception; + } + } + + function removeDirectory($directory) { + if (realpath(substr($directory, 0, strlen(DRUPAL_ROOT))) !== DRUPAL_ROOT) { + throw new FileTransferException('@directory is outside of the Drupal root.', NULL, array('@directory' => $directory)); + } + if (is_dir($directory)) { + $dh = opendir($directory); + while (($resource = readdir($dh)) !== FALSE) { + if ($resource == '.' || $resource == '..') { + continue; + } + $full_path = $directory . DIRECTORY_SEPARATOR . $resource; + if (is_file($full_path)) { + $this->removeFile($full_path); + } + elseif (is_dir($full_path)) { + $this->removeDirectory($full_path . '/'); + } + } + closedir($dh); + if (!removeDirectory($directory)) { + $exception = new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory)); + throw $exception; + } + } + } + + function copyFile($source, $destination) { + if (!@copy($this->connection . '/' . $source, $this->connection . '/' . $destination)) { + throw new FileTransferException('Cannot copy @source_file to @destination_file.', NULL, array('@source' => $source, '@destination' => $destination)); + } + } + + function removeFile($destination) { + if (!@unlink($destination)) { + throw new FileTransferException('Cannot remove @destination', NULL, array('@destination' => $destination)); + } + } +} + +class FileTransferFTPExtension extends FileTransfer { + function connect() { + $this->connection = ftp_connect($this->hostname, $this->port); + + if (!$this->connection) { + throw new FileTransferException("Cannot connect to FTP Server, please check settings"); + } + if (!ftp_login($this->connection, $this->username, $this->password)) { + throw new FileTransferException("Cannot login to FTP server, please check username and password"); + } + } + + function copyFile($source, $destination) { + if (!@ftp_put($this->connection, $destination, $source, FTP_BINARY)) { + throw new FileTransferException("Cannot move @source to @destination", NULL, array("@source" => $source, "@destination" => $destination)); + } + } + + function createDirectory($directory) { + if (!@ftp_createDirectory($this->connection, $directory)) { + throw new FileTransferException("Cannot create directory @directory", NULL, array("@directory" => $directory)); + } + } + + function removeDirectory($directory) { + if (realpath(substr($directory, 0, strlen(DRUPAL_ROOT))) !== DRUPAL_ROOT) { + throw new FileTransferException('@directory is outside of the Drupal root.', NULL, array('@directory' => $directory)); + } + $pwd = ftp_pwd($this->connection); + if (!@ftp_chdir($this->connection, $directory)) { + throw new FileTransferException("Unable to change to directory @directory", NULL, array('@directory' => $directory)); + } + $list = @ftp_nlist($this->connection, '.'); + foreach ($list as $item){ + if ($item == '.' || $item == '..') { + continue; + } + if (@ftp_chdir($this->connection, $item)){ + ftp_chdir($this->connection, '..'); + $this->removeDirectory($item); + } + else { + $this->removeFile($item); + } + } + ftp_chdir($this->connection, $pwd); + if (!ftp_removeDirectory($this->connection, $directory)) { + throw new FileTransferException("Unable to remove to directory @directory", NULL, array('@directory' => $directory)); + } + } + + function removeFile($destination) { + if (!ftp_delete($this->connection, $item)) { + throw new FileTransferException("Unable to remove to file @file", NULL, array('@file' => $item)); + } + } +} diff --git a/includes/filetransfer/ssh.inc b/includes/filetransfer/ssh.inc new file mode 100644 index 00000000000..846b976dd3a --- /dev/null +++ b/includes/filetransfer/ssh.inc @@ -0,0 +1,57 @@ +port = 22; + parent::__construct($settings); + } + + function connect() { + $this->connection = @ssh2_connect($setings['hostname'], $this->port); + if (!$this->connection) { + throw new FileTransferException('SSH Connection failed.'); + } + if (!@ssh2_auth_password($this->connection, $this->username, $this->password)) { + throw new FileTransferException('The supplied username/password combination was not accepted.'); + } + } + + function copyFile($source, $destination) { + if (!@ssh2_scp_send($this->connection, $source, $destination)) { + throw new FileTransferException('Cannot copy @source_file to @destination_file.', NULL, array('@source' => $source, '@destination' => $destination)); + } + } + + function copyDirectory($source, $destination) { + if (!@ssh2_exec($this->connection, 'cp -Rp ' . escapeshellarg($source) . ' ' . escapeshellarg($destination))) { + throw new FileTransferException('Cannot copy directory @directory.', NULL, array('@directory' => $source)); + } + } + + function createDirectory($directory) { + if (!@ssh2_exec($this->connection, 'mkdir ' . escapeshellarg($directory))) { + throw new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory)); + } + } + + function removeDirectory($directory) { + if (realpath(substr($directory, 0, strlen(DRUPAL_ROOT))) !== DRUPAL_ROOT) { + throw new FileTransferException('@directory is outside of the Drupal root.', NULL, array('@directory' => $directory)); + } + if (!@ssh2_exec($this->connection, 'rm -Rf ' . escapeshellarg($directory))) { + throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory)); + } + } + + function removeFile($destination) { + if (!@ssh2_exec($this->connection, 'rm ' . escapeshellarg($destination))) { + throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $destination)); + } + } +} diff --git a/modules/system/system.module b/modules/system/system.module index c4224423df5..807b800154f 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -827,6 +827,36 @@ function system_admin_menu_block_access($path, $permission) { return !empty($content); } +/** + * Implementation of hook_filetransfer_backends(). + */ +function system_filetransfer_backends() { + $backends = array(); + + // SSH2 lib connection is only available if the proper PHP extension is + // installed. + if (function_exists('ssh2_connect')) { + $backends['ssh'] = array( + 'title' => t('SSH'), + 'class' => 'FileTransferSSH', + ); + } + if (function_exists('ftp_connect')) { + $backends['ftp_extension'] = array( + 'title' => t('FTP Extension'), + 'class' => 'FileTransferFTPExtension', + ); + } + + if (ini_get('allow_url_fopen')) { + $backends['ftp_wrapper'] = array( + 'title' => t('FTP Wrapper'), + 'class' => 'FileTransferFTPWrapper', + ); + } + return $backends; +} + /** * Implement hook_init(). */ @@ -2509,3 +2539,33 @@ function system_image_toolkits() { ), ); } + +/** + * Attempts to get a file using drupal_http_request and to store it locally. + * + * @param $path + * The URL of the file to grab. + * @return + * On success the address the files was saved to, FALSE on failure. + */ +function system_retrieve_file($path) { + // Get each of the specified files. + $parsed_url = parse_url($path); + $local = file_directory_temp() . '/update-cache/' . basename($parsed_url['path']); + if (!file_exists(file_directory_temp() . '/update-cache/')) { + mkdir(file_directory_temp() . '/update-cache/'); + } + + // Check the cache and download the file if needed. + if (!file_exists($local)) { + // $result->data is the actual contents of the downloaded file. This saves + // it into a local file, whose path is stored in $local. $local is stored + // relative to the Drupal installation. + $result = drupal_http_request($path); + if ($result->code != 200 || !file_save_data($result->data, $local)) { + drupal_set_message(t('@remote could not be saved.', array('@remote' => $path)), 'error'); + return FALSE; + } + } + return $local; +}