Commit b8448cac authored by Dries's avatar Dries

- Patch #528326 by JacobSingh, cwgordon7: clean-up and bug fixes for the FTP file transfer classes.

parent 45da7480
......@@ -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 @@ function __get($name) {
$this->connect();
return $this->connection;
}
if ($name == 'chroot') {
$this->setChroot();
return $this->chroot;
}
}
/**
......@@ -57,10 +58,28 @@ function __get($name) {
* The destination path.
*/
public final function copyDirectory($source, $destination) {
$source = $this->sanitizePath($source);
$destination = $this->fixRemotePath($destination);
$this->checkPath($destination);
$this->copyDirectoryJailed($source, $destination);
}
/**
* @see http://php.net/chmod
*
* @param string $path
* @param long $mode
* @param bool $recursive
*/
public final function chmod($path, $mode, $recursive = FALSE) {
$path = $this->sanitizePath($path);
$path = $this->fixRemotePath($path);
$this->checkPath($path);
$this->chmodJailed($path, $mode, $recursive);
}
protected abstract function chmodJailed($path, $mode, $recursive);
/**
* Creates a directory.
*
......@@ -68,6 +87,7 @@ function __get($name) {
* The directory to be created.
*/
public final function createDirectory($directory) {
$directory = $this->fixRemotePath($directory);
$this->checkPath($directory);
$this->createDirectoryJailed($directory);
}
......@@ -79,6 +99,7 @@ function __get($name) {
* The directory to be removed.
*/
public final function removeDirectory($directory) {
$directory = $this->fixRemotePath($directory);
$this->checkPath($directory);
$this->removeDirectoryJailed($directory);
}
......@@ -92,6 +113,8 @@ function __get($name) {
* The destination file.
*/
public final function copyFile($source, $destination) {
$source = $this->sanitizePath($source);
$destination = $this->fixRemotePath($destination);
$this->checkPath($destination);
$this->copyFileJailed($source, $destination);
}
......@@ -103,6 +126,7 @@ function __get($name) {
* The destination file to be removed.
*/
public final function removeFile($destination) {
$destination = $this->fixRemotePath($destination);
$this->checkPath($destination);
$this->removeFileJailed($destination);
}
......@@ -114,11 +138,50 @@ function __get($name) {
* A path to check against the jail.
*/
protected final function checkPath($path) {
if (drupal_realpath(substr($path, 0, strlen($this->jail))) !== $this->jail) {
$full_jail = $this->chroot . $this->jail;
$full_path = drupal_realpath(substr($this->chroot . $path, 0, strlen($full_jail)));
$full_path = $this->fixRemotePath($full_path, FALSE);
if ($full_jail !== $full_path) {
throw new FileTransferException('@directory is outside of the @jail', NULL, array('@directory' => $path, '@jail' => $this->jail));
}
}
/**
* Returns a modified path suitable for passing to the server.
* If a path is a windows path, makes it posix compliant by removing the drive letter.
* If $this->chroot has a value, it is stripped from the path to allow for
* chroot'd filetransfer systems.
*
* @param $path
* @param $strip_chroot
*
* @return string;
*/
protected final function fixRemotePath($path, $strip_chroot = TRUE) {
$path = $this->sanitizePath($path);
$path = preg_replace('|^([a-z]{1}):|i', '', $path); // Strip out windows driveletter if its there.
if ($strip_chroot) {
if ($this->chroot && strpos($path, $this->chroot) === 0) {
$path = ($path == $this->chroot) ? '' : substr($path, strlen($this->chroot));
}
}
return $path;
}
/**
* Changes backslahes to slashes, also removes a trailing slash.
*
* @param string $path
* @return string;
*/
function sanitizePath($path) {
$path = str_replace('\\', '/', $path); // Windows path sanitiation.
if (substr($path, -1) == '/') {
$path = substr($path, 0, -1);
}
return $path;
}
/**
* Copies a directory.
*
......@@ -178,7 +241,7 @@ protected function copyDirectoryJailed($source, $destination) {
* The destination file to be removed.
*/
abstract protected function removeFileJailed($destination);
/**
* Checks if a particular path is a directory
*
......@@ -188,6 +251,54 @@ protected function copyDirectoryJailed($source, $destination) {
* @return boolean
*/
abstract public function isDirectory($path);
/**
* Checks if a particular path is a file (not a directory).
*
* @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.
$path = __FILE__;
$path = $this->fixRemotePath($path, FALSE);
if ($this->isFile($path)) {
return FALSE;
}
$path = dirname(__FILE__);
$path = $this->fixRemotePath($path, FALSE);
$parts = explode('/', $path);
$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->jail = $this->fixRemotePath($this->jail);
}
}
/**
......@@ -200,4 +311,4 @@ function __construct($message, $code = 0, $arguments = array()) {
parent::__construct($message, $code);
$this->arguments = $arguments;
}
}
}
\ No newline at end of file
......@@ -49,23 +49,56 @@ function removeDirectoryJailed($directory) {
}
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');
}
/**
* This is impossible with the stream wrapper,
* So we cheat and use the other implementation
*
* @staticvar FileTransferFTPExtension $ftp_ext_file_transfer
* @param string $path
* @param long $mode
* @param bool $recursive
*/
function chmodJailed($path, $mode, $recursive) {
static $ftp_ext_file_transfer;
if (!$ftp_ext_file_transfer) {
$ftp_ext_file_transfer = new FileTransferFTPExtension($this->jail, $this->username, $this->password, $this->hostname, $this->port);
}
$ftp_ext_file_transfer->chmodJailed($path, $mode, $recursive);
}
}
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 +109,14 @@ public function connect() {
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,17 +130,20 @@ protected function copyFileJailed($source, $destination) {
}
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, '.');
if (!$list) {
$list = array();
}
foreach ($list as $item){
if ($item == '.' || $item == '..') {
continue;
......@@ -124,14 +167,40 @@ protected function removeFileJailed($destination) {
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;
}
function chmodJailed($path, $mode, $recursive) {
if (!ftp_chmod($this->connection, $mode, $path)) {
throw new FileTransferException("Unable to set permissions on %file", NULL, array ('%file' => $path));
}
if ($this->isDirectory($path) && $recursive) {
$filelist = @ftp_nlist($this->connection, $path);
if (!$filelist) {
//empty directory - returns false
return;
}
foreach ($filelist as $file) {
$this->chmodJailed($file, $mode, $recursive);
}
}
}
}
if (!function_exists('ftp_chmod')) {
function ftp_chmod($ftp_stream, $mode, $filename) {
return ftp_site($ftp_stream, sprintf('CHMOD %o %s', $mode, $filename));
}
}
\ No newline at end of file
......@@ -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,24 @@ public function isDirectory($path) {
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));
}
}
function chmodJailed($path, $mode, $recursive) {
$cmd = sprintf("chmod %s%o %s", $recursive ? '-R ' : '', $mode, escapeshellarg($path));
if (@!ssh2_exec($this->connection, $cmd)) {
throw new FileTransferException('Cannot change permissions of @path.', NULL, array('@path' => $path));
}
}
}
......@@ -139,6 +139,14 @@ class TestFileTransfer extends FileTransfer {
function isDirectory($path) {
return $this->shouldIsDirectoryReturnTrue;
}
function isFile($path) {
return FALSE;
}
function chmodJailed($path, $mode, $recursive) {
return;
}
}
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment