Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.715 diff -n -u -r1.715 system.module --- modules/system/system.module 23 Jun 2009 12:11:19 -0000 1.715 +++ modules/system/system.module 25 Jun 2009 05:17:58 -0000 @@ -828,7 +828,7 @@ } /** - * Implementation of hook_filetransfer_backends(). + * Implement hook_filetransfer_backends(). */ function system_filetransfer_backends() { $backends = array(); @@ -841,20 +841,51 @@ 'class' => 'FileTransferSSH', ); } - if (function_exists('ftp_connect')) { - $backends['ftp_extension'] = array( + if (function_exists('ftp_connect') || ini_get('allow_url_fopen')) { + $backends['FTP'] = array( 'title' => t('FTP Extension'), - 'class' => 'FileTransferFTPExtension', + 'class' => 'FileTransferFTP', ); } + return $backends; +} - if (ini_get('allow_url_fopen')) { - $backends['ftp_wrapper'] = array( - 'title' => t('FTP Wrapper'), - 'class' => 'FileTransferFTPWrapper', - ); +function system_filetransfer_backend_form($type, $show_non_stored_fields = FALSE) { + $form = array(); + + $form['hostname'] = array ( + '#type' => 'textfield', + '#title' => t('Host'), + '#default_value' => 'localhost', + ); + + $form['port'] = array ( + '#type' => 'textfield', + '#title' => t('Port'), + '#default_value' => NULL, + ); + + $form['username'] = array ( + '#type' => 'textfield', + '#title' => t('Username'), + ); + + $form['password'] = array ( + '#type' => 'password', + '#title' => t('Password'), + '#description' => t('This is not saved in the database and is only used to test the connection'), + ); + + switch ($type) { + case 'ssh': + $form['port']['#default_value'] = 22; + break; + case 'ftp_wrapper': + case 'ftp_extension': + $form['port']['#default_value'] = 21; + break; } - return $backends; + return $form; } /** @@ -2543,29 +2574,37 @@ /** * Attempts to get a file using drupal_http_request and to store it locally. * - * @param $path + * @param $url * The URL of the file to grab. + * + * @param $destination + * Where the file should be saved, if a directory is provided, file is saved + * in that directory with its original name. If a filename is provided, + * remote fileis stored to that location. NOTE: Relative to drupal "files" directory" + * + * @param $overwrite boolean + * Defaults to TRUE, will overwrite existing files of the same name. + * * @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; - } +function system_retrieve_file($url, $destination = NULL, $overwrite = TRUE) { + if (!$destination) { + $destination = file_directory_temp(); + } + $parsed_url = parse_url($url); + $local = is_dir(file_directory_path() . '/' . $destination) ? $destination . '/' . basename($parsed_url['path']) : $destination; + + if (!$overwrite && file_exists($local)) { + drupal_set_message(t('@remote could not be saved. @local already exists', array('@remote' => $url, '@local' => $local)), 'error'); + return FALSE; + } + + $result = drupal_http_request($url); + if ($result->code != 200 || !file_save_data($result->data, $local)) { + drupal_set_message(t('@remote could not be saved.', array('@remote' => $url)), 'error'); + return FALSE; } + return $local; } Index: includes/filetransfer/filetransfer.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/filetransfer/filetransfer.inc,v retrieving revision 1.1 diff -n -u -r1.1 filetransfer.inc --- includes/filetransfer/filetransfer.inc 23 Jun 2009 12:11:19 -0000 1.1 +++ includes/filetransfer/filetransfer.inc 25 Jun 2009 05:17:58 -0000 @@ -2,27 +2,37 @@ // $Id: filetransfer.inc,v 1.1 2009/06/23 12:11:19 dries Exp $ /* - * Connection class. + * Base FileTransfer class. * - * This class does file operations on directories not writeable by the - * webserver. It connects back to the server using some backend (for example - * FTP or SSH). To keep security the password should always be asked from the - * user and never stored. + * Classes extending this class perform file operations on directories not + * writeable by the webserver. To achieve this, the class should connect back + * to the server using some backend (for example FTP or SSH). To keep security, + * the password should always be asked from the user and never stored. For + * safety, all methods operate only inside a "jail", by default the Drupal root. */ abstract class FileTransfer { + protected $username; + + protected $password; + + protected $hostname = 'localhost'; + + protected $port; + /** * The constructer for the UpdateConnection class. This method is also called * from the classes that extend this class and override this method. */ - function __construct($settings) { - $this->username = $settings['username']; - $this->password = $settings['password']; - $this->hostname = isset($settings['hostname']) ? $settings['hostname'] : 'localhost'; - if (isset($settings['port'])) { - $this->port = $settings['port']; - } + function __construct($jail, $username, $password, $hostname, $port) { + $this->username = $username; + $this->password = $password; + $this->hostname = $hostname; + $this->port = $port; + $this->jail = $jail; } + + abstract static function factory($jail, $settings); /** * Implementation of the magic __get() method. If the connection isn't set to @@ -31,30 +41,107 @@ * this method. */ function __get($name) { - static $connection; if ($name == 'connection') { - $this->connection = $this->connect(); + $this->connect(); return $this->connection; } } + + /** + * Connect to the server. + */ + abstract protected function connect(); + + /** + * Copies a directory. + * + * @param $source + * The source path. + * @param $destination + * The destination path. + */ + public final function copyDirectory($source, $destination) { + $this->checkPath($destination); + $this->copyDirectoryJailed($source, $destination); + } + + /** + * Creates a directory. + * + * @param $directory + * The directory to be created. + */ + public final function createDirectory($directory) { + $this->checkPath($directory); + $this->createDirectoryJailed($directory); + } + + /** + * Removes a directory. + * + * @param $directory + * The directory to be removed. + */ + public final function removeDirectory($directory) { + $this->checkPath($directory); + $this->removeDirectoryJailed($directory); + } + + /** + * Copies a file. + * + * @param $source + * The source file. + * @param $destination + * The destination file. + */ + public final function copyFile($source, $destination) { + $this->checkPath($destination); + $this->copyFileJailed($source, $destination); + } /** + * Removes a file. + * + * @param $destination + * The destination file to be removed. + */ + public final function removeFile($destination) { + $this->checkPath($destination); + $this->removeFileJailed($destination); + } + + /** + * Checks that the path is inside the jail and throws an exception if not. + * + * @param $path + * A path to check against the jail. + */ + protected final function checkPath($path) { + if (realpath(substr($path, 0, strlen($this->jail))) !== $this->jail) { + throw new FileTransferException('@directory is outside of the @jail', NULL, array('@directory' => $path, '@jail' => $this->jail)); + } + } + + /** * Copies a directory. * + * We need a separate method to make the $destination is in the jail. + * * @param $source * The source path. * @param $destination * The destination path. */ - protected function copyDirectory($source, $destination) { - $this->createDirectory($destination . basename($source)); + protected function copyDirectoryJailed($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); + $this->createDirectory($destination . '/' . $relative_path); } else { - $this->copyFile($file->getPathName(), $destination . $relative_path); + $this->copyFile($file->getPathName(), $destination . '/' . $relative_path); } } } @@ -65,7 +152,7 @@ * @param $directory * The directory to be created. */ - abstract function createDirectory($directory); + abstract protected function createDirectoryJailed($directory); /** * Removes a directory. @@ -73,7 +160,7 @@ * @param $directory * The directory to be removed. */ - abstract function removeDirectory($directory); + abstract protected function removeDirectoryJailed($directory); /** * Copies a file. @@ -83,8 +170,7 @@ * @param $destination * The destination file. */ - abstract function copyFile($source, $destination); - + abstract protected function copyFileJailed($source, $destination); /** * Removes a file. @@ -92,11 +178,17 @@ * @param $destination * The destination file to be removed. */ - abstract function removeFile($destination); + abstract protected function removeFileJailed($destination); } /** * FileTransferException class. */ class FileTransferException extends Exception { + public $arguments; + + function __construct($message, $code = 0, $arguments = array()) { + parent::__construct($message, $code); + $this->arguments = $arguments; + } } Index: includes/filetransfer/ftp.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/filetransfer/ftp.inc,v retrieving revision 1.3 diff -n -u -r1.3 ftp.inc --- includes/filetransfer/ftp.inc 24 Jun 2009 01:45:09 -0000 1.3 +++ includes/filetransfer/ftp.inc 25 Jun 2009 05:17:58 -0000 @@ -4,33 +4,43 @@ /** * Common code for the FTP connections. */ -abstract class FileTransferFTP extends FileTransfer { - function __construct($settings) { - // This is the default, if $settings contains a port, this will be overridden. - $this->port = 21; - parent::__construct($settings); +class FileTransferFTP extends FileTransfer { + function __construct($jail, $username, $password, $hostname = "localhost", $port = 21) { + parent::__construct($jail, $username, $password, $hostname, $port); + } + + static function factory($jail, $settings) { + $settings['hostname'] = empty($settings['hostname']) ? 'localhost' : $settings['hostname']; + $settings['port'] = empty($settings['port']) ? 21 : $settings['port']; + //If we can use the wrapper, use it + if (ini_get('allow_url_fopen') == TRUE) { + return new FileTransferFTPWrapper($jail, $settings['username'], $settings['password'], $settings['hostname'], $settings['port']); + } elseif (function_exists('ftp_connect')) { + return new FileTransferFTPExtension($jail, $settings['username'], $settings['password'], $settings['hostname'], $settings['port']); + } } } /** * Connection class using the FTP URL wrapper. */ -class FileTransferFTPWrapper extends FileTransfer { +class FileTransferFTPWrapper extends FileTransferFTP { function connect() { - $this->connection = 'ftp://' . urlencode($this->username) . ':' . urlencode($this->password) . '@' . $this->hostname . ':' . $this->port . '/'; - if (!is_dir($this->connection)) { + $connection = 'ftp://' . urlencode($this->username) . ':' . urlencode($this->password) . '@' . $this->hostname . ':' . $this->port . '/'; + if (!is_dir($connection)) { throw new FileTransferException('FTP Connection failed.'); } + return $connection; } - function createDirectory($directory) { + function createDirectoryJailed($directory) { if (!@createDirectory($directory)) { $exception = new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory)); throw $exception; } } - function removeDirectory($directory) { + function removeDirectoryJailed($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)); } @@ -56,44 +66,45 @@ } } - function copyFile($source, $destination) { + function copyFileJailed($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) { + function removeFileJailed($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); +class FileTransferFTPExtension extends FileTransferFTP { + protected function connect() { + $connection = ftp_connect($this->hostname, $this->port); - if (!$this->connection) { + if (!$connection) { throw new FileTransferException("Cannot connect to FTP Server, please check settings"); } - if (!ftp_login($this->connection, $this->username, $this->password)) { + if (!ftp_login($connection, $this->username, $this->password)) { throw new FileTransferException("Cannot login to FTP server, please check username and password"); } + return $connection; } - function copyFile($source, $destination) { + protected function copyFileJailed($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) { + protected function createDirectoryJailed($directory) { if (!@ftp_createDirectory($this->connection, $directory)) { throw new FileTransferException("Cannot create directory @directory", NULL, array("@directory" => $directory)); } } - function removeDirectory($directory) { + protected function removeDirectoryJailed($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)); } @@ -120,7 +131,7 @@ } } - function removeFile($destination) { + protected function removeFileJailed($destination) { if (!ftp_delete($this->connection, $item)) { throw new FileTransferException("Unable to remove to file @file", NULL, array('@file' => $item)); } Index: includes/filetransfer/ssh.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/filetransfer/ssh.inc,v retrieving revision 1.1 diff -n -u -r1.1 ssh.inc --- includes/filetransfer/ssh.inc 23 Jun 2009 12:11:19 -0000 1.1 +++ includes/filetransfer/ssh.inc 25 Jun 2009 05:17:59 -0000 @@ -6,50 +6,52 @@ */ class FileTransferSSH extends FileTransfer { - function __construct($settings) { - // This is the default, if $settings contains a port, this will be overridden. - $this->port = 22; - parent::__construct($settings); + function __construct($jail, $username, $password, $hostname = "localhost", $port = 22) { + parent::__construct($jail, $username, $password, $hostname, $port); } function connect() { - $this->connection = @ssh2_connect($setings['hostname'], $this->port); - if (!$this->connection) { - throw new FileTransferException('SSH Connection failed.'); + $connection = @ssh2_connect($this->hostname, $this->port); + if (!$connection) { + throw new FileTransferException('SSH Connection failed to @host:@port'); } - if (!@ssh2_auth_password($this->connection, $this->username, $this->password)) { + if (!@ssh2_auth_password($connection, $this->username, $this->password)) { throw new FileTransferException('The supplied username/password combination was not accepted.'); } + return $connection; + } + + static function factory($jail, $settings) { + $settings['hostname'] = empty($settings['hostname']) ? 'localhost' : $settings['hostname']; + $settings['port'] = empty($settings['port']) ? 22 : $settings['port']; + return new FileTransferSSH($jail, $settings['username'], $settings['password'], $settings['hostname'], $settings['port']); } - function copyFile($source, $destination) { + protected function copyFileJailed($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) { + protected function copyDirectoryJailed($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) { + protected function createDirectoryJailed($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)); - } + protected function removeDirectoryJailed($directory) { if (!@ssh2_exec($this->connection, 'rm -Rf ' . escapeshellarg($directory))) { throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory)); } } - function removeFile($destination) { + protected function removeFileJailed($destination) { if (!@ssh2_exec($this->connection, 'rm ' . escapeshellarg($destination))) { throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $destination)); }