Index: includes/filetransfer/filetransfer.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/filetransfer/filetransfer.inc,v retrieving revision 1.2 diff -u -p -r1.2 filetransfer.inc --- includes/filetransfer/filetransfer.inc 1 Jul 2009 13:44:53 -0000 1.2 +++ includes/filetransfer/filetransfer.inc 24 Jul 2009 17:50:54 -0000 @@ -20,11 +20,7 @@ abstract class FileTransfer { * The constructer for the UpdateConnection class. This method is also called * from the classes that extend this class and override this method. */ - function __construct($jail, $username, $password, $hostname, $port) { - $this->username = $username; - $this->password = $password; - $this->hostname = $hostname; - $this->port = $port; + function __construct($jail) { $this->jail = $jail; } @@ -41,6 +37,11 @@ abstract class FileTransfer { $this->connect(); return $this->connection; } + + if ($name == 'chroot') { + $this->setChroot(); + return $this->chroot; + } } /** @@ -57,6 +58,8 @@ abstract class FileTransfer { * The destination path. */ public final function copyDirectory($source, $destination) { + $this->sanitizePath($source); + $this->fixPath($destination); $this->checkPath($destination); $this->copyDirectoryJailed($source, $destination); } @@ -68,6 +71,7 @@ abstract class FileTransfer { * The directory to be created. */ public final function createDirectory($directory) { + $this->fixPath($directory); $this->checkPath($directory); $this->createDirectoryJailed($directory); } @@ -79,6 +83,7 @@ abstract class FileTransfer { * The directory to be removed. */ public final function removeDirectory($directory) { + $this->fixPath($directory); $this->checkPath($directory); $this->removeDirectoryJailed($directory); } @@ -92,6 +97,8 @@ abstract class FileTransfer { * The destination file. */ public final function copyFile($source, $destination) { + $this->sanitizePath($source); + $this->fixPath($destination); $this->checkPath($destination); $this->copyFileJailed($source, $destination); } @@ -103,6 +110,7 @@ abstract class FileTransfer { * The destination file to be removed. */ public final function removeFile($destination) { + $this->fixPath($destination); $this->checkPath($destination); $this->removeFileJailed($destination); } @@ -114,12 +122,44 @@ abstract class FileTransfer { * A path to check against the jail. */ protected final function checkPath($path) { - if (realpath(substr($path, 0, strlen($this->jail))) !== $this->jail) { + $full_jail = $this->chroot . $this->jail; + $full_path = realpath(substr($this->chroot . $path, 0, strlen($full_jail))); + if ($full_jail !== $full_path) { throw new FileTransferException('@directory is outside of the @jail', NULL, array('@directory' => $path, '@jail' => $this->jail)); } } /** + * If a path is a windows path, makes it posix compliant + * If $this->chroot has a value, it is stripped from the path to allow for + * chroot'd filetransfer systems. + * + * @param string $path + * + * @return void; + */ + protected final function fixPath(&$path) { + $this->sanitizePath($path); + $path = preg_replace('|^([a-z]{1}):|i', '', $path); // Strip out windows driveletter if its there. + if (!$this->chroot || strpos($path, $this->chroot) !== 0) { + return; + } + $path = substr($path, strlen($this->chroot)); + } + + /** + * Changes backslahes to slashes, also removes a trailing slash. + * + * @param string $path + */ + function sanitizePath(&$path) { + $path = str_replace('\\', '/', $path); // Windows path sanitiation. + if (substr($path, -1) == '/') { + $path = substr($path, 0, -1); + } + } + + /** * Copies a directory. * * We need a separate method to make the $destination is in the jail. @@ -178,7 +218,7 @@ abstract class FileTransfer { * The destination file to be removed. */ abstract protected function removeFileJailed($destination); - + /** * Checks if a particular path is a directory * @@ -188,6 +228,50 @@ abstract class FileTransfer { * @return boolean */ abstract public function isDirectory($path); + + /** + * Checks if a particular path is a file + * + * @param $path + * The path to check + * + * @return boolean + */ + abstract public function isFile($path); + + /** + * Gets the chroot property for this connection. It does this by moving up + * the tree until it finds itself. If successful, it will return a chroot. + * + * @return string chroot + */ + function findChroot() { + // If the file exists as is, there is no chroot. + if ($this->isFile(__FILE__)) { + return FALSE; + } + + $parts = explode('/', dirname(__FILE__)); + $chroot = ''; + while (count($parts)) { + $check = implode($parts, '/'); + if ($this->isFile($check . '/' . basename(__FILE__))) { + // Remove the trailing slash. + return substr($chroot,0,-1); + } + $chroot .= array_shift($parts) . '/'; + } + return FALSE; + } + + /** + * Sets the chroot and changes the jail to match the correct path scheme + * + */ + function setChroot() { + $this->chroot = $this->findChroot(); + $this->fixPath($this->jail); + } } /** Index: includes/filetransfer/ftp.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/filetransfer/ftp.inc,v retrieving revision 1.4 diff -u -p -r1.4 ftp.inc --- includes/filetransfer/ftp.inc 1 Jul 2009 13:44:53 -0000 1.4 +++ includes/filetransfer/ftp.inc 24 Jul 2009 17:50:54 -0000 @@ -5,13 +5,30 @@ * Connection class using the FTP URL wrapper. */ class FileTransferFTPWrapper extends FileTransfer { + + public function __construct($jail, $username, $password, $hostname, $port) { + $this->username = $username; + $this->password = $password; + $this->hostname = $hostname; + $this->port = $port; + parent::__construct($jail); + } + 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.'); } } - + + /** + * Returns a copy of itself using common defaults. + * + * @param string $jail + * @param array $settings + * + * @return FileTransferFTPWrapper + */ static function factory($jail, $settings) { $settings['hostname'] = empty($settings['hostname']) ? 'localhost' : $settings['hostname']; $settings['port'] = empty($settings['port']) ? 21 : $settings['port']; @@ -19,29 +36,29 @@ class FileTransferFTPWrapper extends Fil } function createDirectoryJailed($directory) { - if (!@mkdir($directory)) { + if (!@mkdir($this->connection . '/' . $directory)) { $exception = new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory)); throw $exception; } } function removeDirectoryJailed($directory) { - if (is_dir($directory)) { - $dh = opendir($directory); + if (is_dir($this->connection . '/' . $directory)) { + $dh = opendir($this->connection . '/' .$directory); while (($resource = readdir($dh)) !== FALSE) { if ($resource == '.' || $resource == '..') { continue; } $full_path = $directory . DIRECTORY_SEPARATOR . $resource; - if (is_file($full_path)) { + if (is_file($this->connection . '/' .$full_path)) { $this->removeFile($full_path); } - elseif (is_dir($full_path)) { + elseif (is_dir($this->connection . '/' .$full_path)) { $this->removeDirectory($full_path . '/'); } } closedir($dh); - if (!rmdir($directory)) { + if (!rmdir($this->connection . '/' .$directory)) { $exception = new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory)); throw $exception; } @@ -49,23 +66,37 @@ class FileTransferFTPWrapper extends Fil } function copyFileJailed($source, $destination) { - if (!@copy($this->connection . '/' . $source, $this->connection . '/' . $destination)) { + if (!@copy($source, $this->connection . '/' . $destination)) { throw new FileTransferException('Cannot copy @source_file to @destination_file.', NULL, array('@source' => $source, '@destination' => $destination)); } } function removeFileJailed($destination) { - if (!@unlink($destination)) { + if (!@unlink($this->connection . '/' .$destination)) { throw new FileTransferException('Cannot remove @destination', NULL, array('@destination' => $destination)); } } - + function isDirectory($path) { return is_dir($this->connection . '/' . $path); } + + public function isFile($path) { + // This is stupid, but is_file and file_exists don't work! always return true. + return @fopen($this->connection . '/' . $path,'r'); + } } class FileTransferFTPExtension extends FileTransfer { + + public function __construct($jail, $username, $password, $hostname, $port) { + $this->username = $username; + $this->password = $password; + $this->hostname = $hostname; + $this->port = $port; + parent::__construct($jail); + } + public function connect() { $this->connection = ftp_connect($this->hostname, $this->port); @@ -76,7 +107,14 @@ class FileTransferFTPExtension extends F throw new FileTransferException("Cannot login to FTP server, please check username and password"); } } - + + /** + * Returns a copy of itself using common defaults. + * + * @param string $jail + * @param array $settings + * @return FileTransferFTPExtension + */ static function factory($jail, $settings) { $settings['hostname'] = empty($settings['hostname']) ? 'localhost' : $settings['hostname']; $settings['port'] = empty($settings['port']) ? 21 : $settings['port']; @@ -90,14 +128,14 @@ class FileTransferFTPExtension extends F } protected function createDirectoryJailed($directory) { - if (!@ftp_mkdir($this->connection, $directory)) { + if (!ftp_mkdir($this->connection, $directory)) { throw new FileTransferException("Cannot create directory @directory", NULL, array("@directory" => $directory)); } } protected function removeDirectoryJailed($directory) { $pwd = ftp_pwd($this->connection); - if (!@ftp_chdir($this->connection, $directory)) { + if (!ftp_chdir($this->connection, $directory)) { throw new FileTransferException("Unable to change to directory @directory", NULL, array('@directory' => $directory)); } $list = @ftp_nlist($this->connection, '.'); @@ -124,14 +162,18 @@ class FileTransferFTPExtension extends F throw new FileTransferException("Unable to remove to file @file", NULL, array('@file' => $destination)); } } - + public function isDirectory($path) { $result = FALSE; $curr = ftp_pwd($this->connection); - if (ftp_chdir($this->connection, $path)) { + if (@ftp_chdir($this->connection, $path)) { $result = TRUE; } ftp_chdir($this->connection, $curr); return $result; } + + public function isFile($path) { + return ftp_size($this->connection, $path) != -1; + } } Index: includes/filetransfer/ssh.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/filetransfer/ssh.inc,v retrieving revision 1.2 diff -u -p -r1.2 ssh.inc --- includes/filetransfer/ssh.inc 1 Jul 2009 13:44:53 -0000 1.2 +++ includes/filetransfer/ssh.inc 24 Jul 2009 17:50:54 -0000 @@ -7,7 +7,11 @@ class FileTransferSSH extends FileTransfer { function __construct($jail, $username, $password, $hostname = "localhost", $port = 22) { - parent::__construct($jail, $username, $password, $hostname, $port); + $this->username = $username; + $this->password = $password; + $this->hostname = $hostname; + $this->port = $port; + parent::__construct($jail); } function connect() { @@ -71,4 +75,17 @@ class FileTransferSSH extends FileTransf throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path)); } } + + public function isFile($path) { + $file = escapeshellarg($path); + $cmd = "[ -f {$file} ] && echo 'yes'"; + if ($output = @ssh2_exec($this->connection, $cmd)) { + if ($output == 'yes') { + return TRUE; + } + return FALSE; + } else { + throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path)); + } + } }