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	23 Jul 2009 12:34:27 -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.
@@ -188,6 +228,53 @@ 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->isDirectory($check)) {
+        if ($this->isFile($check . '/' . basename(__FILE__))) {
+          //remove the trailing slash
+          return substr($chroot,0,-1);
+          //okay, so we found ourselves.  We should be at the end of our stack here.
+        }
+      }
+      $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	23 Jul 2009 12:34:27 -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,13 +66,13 @@ 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));
     }
   }
@@ -63,9 +80,23 @@ class FileTransferFTPWrapper extends Fil
   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, '.');
@@ -128,10 +166,14 @@ class FileTransferFTPExtension extends F
   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	23 Jul 2009 12:34:27 -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));
+    }
+  }
 }
