Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.271
diff -u -p -r1.271 bootstrap.inc
--- includes/bootstrap.inc	1 Mar 2009 09:32:17 -0000	1.271
+++ includes/bootstrap.inc	11 Mar 2009 21:14:43 -0000
@@ -235,6 +235,11 @@ define('REGISTRY_RESET_LOOKUP_CACHE', 1)
 define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
 
 /**
+ * URL to check for updates, if a given project doesn't define its own.
+ */
+define('EXTENSIONS_DEFAULT_URL', 'http://updates.drupal.org/release-history');
+
+/**
  * @} End of "Title text filtering flags".
  */
 
@@ -1615,3 +1620,92 @@ function registry_rebuild() {
 /**
  * @} End of "ingroup registry".
  */
+
+/**
+ * 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 extensions_get_file($path) {
+  require_once DRUPAL_ROOT . '/includes/extensions.inc';
+  return _extensions_get_file($path);
+}
+
+/**
+ * Add an extension to the file system.
+ *
+ * @param $backend
+ *   The machine-readable name of the backend handling the addition to the file
+ *   system; for example, 'ftp'.
+ * @param $type
+ *   The type of extension being added, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be added by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function extensions_add_extension($backend, $type, $files, $root, $host, $username, $password) {
+  require_once DRUPAL_ROOT . '/includes/extensions.inc';
+  $function = $backend . '_add_extension';
+  if (drupal_function_exists($function)) {
+    return $function($type, $files, $root, $host, $username, $password);
+  }
+  return FALSE;
+}
+
+/**
+ * Remove an extension from the file system.
+ *
+ * @param $backend
+ *   The machine-readable name of the backend handling the removal from the
+ *   file system; for example, 'ftp'.
+ * @param $type
+ *   The type of extension being removed, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be removed by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function extensions_remove_extension($backend, $type, $files, $root, $host, $username, $password) {
+  require_once DRUPAL_ROOT . '/includes/extensions.inc';
+  $function = $backend . '_remove_extension';
+  if (drupal_function_exists($function)) {
+    return $function($type, $files, $root, $host, $username, $password);
+  }
+  return FALSE;
+}
+
+/**
+ * Get a list of all available backends that can upload files onto the system.
+ *
+ * @return
+ *   Array containing the names of all available backends.
+ */
+function extension_list_backends() {
+  $backends = module_invoke_all('extension_backends');
+  sort($backends);
+  return $backends;
+}
\ No newline at end of file
Index: includes/extensions.ftp.inc
===================================================================
RCS file: includes/extensions.ftp.inc
diff -N includes/extensions.ftp.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/extensions.ftp.inc	11 Mar 2009 23:04:10 -0000
@@ -0,0 +1,368 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * The FTP Backend for the plugin manager.
+ */
+
+/**
+ * Install the supplied files to the appropriate locations.
+ *
+ * @param $type
+ *   The type of extension being added, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be added by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_add_extension($type, $files, $root, $host, $username, $password) {
+  if (function_exists('ftp_connect')) {
+    return ftp_add_extension_library($type, $files, $root, $host, $username, $password);
+  }
+  return ftp_add_extension_wrapper($type, $files, $root, $host, $username, $password);
+}
+
+/**
+ * Install the supplied files to the appropriate locations using ftp without
+ * the stream wrapper.
+ *
+ * @param $type
+ *   The type of extension being added, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be added by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_add_extension_library($type, $files, $root, $host, $username, $password) {
+  // Figure out the type of files.
+  $dir = "sites/all/${type}s";
+
+  // Connect to the local ftp server, parsing for non-standard port.
+  $parts = explode(':', $host);
+  if (count($parts) == 2) {
+    $connect = ftp_connect($parts[0], (int)$parts[1]);
+  }
+  else {
+    $connect = ftp_connect($host);
+  }
+  if (!$connect) {
+    drupal_set_message(t('No ftp server could be found.'), 'error');
+    return FALSE;
+  }
+
+  // Login to the local ftp server.
+  if (!@ftp_login($connect, $username, $password)) {
+    drupal_set_message(t('Could not login to the ftp server.'), 'error');
+    return FALSE;
+  }
+
+  $ftp_path = $root . '/' . $dir;
+  if (!@ftp_chdir($connect, $ftp_path)) {
+    drupal_set_message(t('Your provided drupal install directory is invalid.'),  'error');
+    return FALSE;
+  }
+
+  // Prepare the directories to use later.
+  $extract_dir = file_directory_path() .'/extensions_extraction/';
+  // Process each of the files.
+  foreach ($files as $index => $file) {
+    ftp_chdir($connect, '/');
+    if (trim($file, '\\/') != $file) {
+      if (!@ftp_mkdir($connect, "$ftp_path/$file") && !@ftp_chdir($connect, "$ftp_path/$file")) {
+        drupal_set_message(t('Unable to create directory @directory.', array('@directory' => $file)),'error');
+        return FALSE;
+      }
+    }
+    else {
+      if (!@ftp_put($connect, "$ftp_path/$file", $extract_dir . $file, FTP_BINARY)) {
+        drupal_set_message(t('Unable to upload @file.', array('@file' => $file)), 'error');
+        return FALSE;
+      }
+    }
+  }
+  return TRUE;
+}
+
+/**
+ * Install the supplied files to the appropriate locations using the ftp
+ * stream wrapper.
+ *
+ * @param $type
+ *   The type of extension being added, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be added by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_add_extension_wrapper($type, $files, $root, $host, $username, $password) {
+  $dir = "sites/all/${type}s";
+
+  // Write the common part of the url.
+  $ftp_base_dir = 'ftp://' . urlencode($username) . ':' . urlencode($password) . "@$host/";
+
+  if (!@is_dir($ftp_base_dir)) {
+    drupal_set_message(t('The supplied username/password combination was not accepted, or no local ftp server is running.'), 'error');
+    return FALSE;
+  }
+
+  $ftp_path = $ftp_base_dir . $root . '/' . $dir;
+
+  // If it's not a valid path, then quit.
+  if (!is_dir($ftp_path)) {
+    drupal_set_message(t('Could not guess the ftp directory for drupal.'), 'error');
+    return FALSE;
+  }
+
+  // Prepare the directories to use later.
+  $extract_dir = file_directory_path() . '/extensions_extraction/';
+
+  // Process each of the files.
+  foreach ($files as $index => $file) {
+    if (trim($file, "\\/") != $file) {
+      @mkdir("$ftp_path/$file");
+    }
+    else {
+      @copy($extract_dir . $file, "$ftp_path/$file");
+    }
+  }
+  return TRUE;
+}
+
+/**
+ * Remove the supplied files.
+ *
+ * @param $type
+ *   The type of extension being removed, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be removed by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_remove_extension($type, $files, $root, $host, $username, $password) {
+  if (function_exists('ftp_connect')) {
+    return ftp_remove_extension_library($type, $files, $root, $host, $username, $password);
+  }
+  return ftp_remove_extension_wrapper($type, $files, $root, $host, $username, $password);
+}
+
+/**
+ * Remove the files using ftp without
+ * the stream wrapper.
+ *
+ * @param $type
+ *   The type of extension being removed, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be removed by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_remove_extension_library($type, $files, $root, $host, $username, $password) {
+  $directory = "sites/all/${type}s";
+
+  // Connect to the local ftp server.
+  $connect = ftp_connect($host);
+  if (!$connect) {
+    drupal_set_message(t('No ftp server could be found.'), 'error');
+    return FALSE;
+  }
+
+  // Login to the local ftp server.
+  if (!@ftp_login($connect, $username, $password)) {
+    drupal_set_message(t('Could not login to the ftp server.'), 'error');
+    return FALSE;
+  }
+
+  // Try to guess the ftp address for the drupal install.
+  // Start by putting the entire address together and replacing \ with /.
+  $local_path = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT'] . base_path());
+
+  // Grab the last on off the end and explode it.
+  $local_path = explode('/', rtrim($local_path, '/'));
+
+  $ftp_path = $root . $dir;
+
+  // If we can't change to the proper directory, abort.
+  if (!@ftp_chdir($connect, $ftp_path)) {
+    drupal_set_message(t('Could not reach the proper directory.'), 'error');
+    return FALSE;
+  }
+  ftp_chdir($connect, '/');
+
+  // Process each of the files.
+  foreach ($files as $file) {
+    if (trim($file, '\\/') != $file) {
+      if (!ftp_remove_extension_rmdir($connect, "$root/$directory/$files")) {
+        return FALSE;
+      }
+    }
+  }
+  return TRUE;
+}
+
+/**
+ * Helper function for ftp_remove_extension_library().
+ *
+ * @param $connect
+ *   The FTP connection to use to remove the directory.
+ * @param $directory
+ *   The directory to remove.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_remove_extension_rmdir($connect, $directory){
+  $pwd = ftp_pwd($connect);
+  if (!@ftp_chdir($connect,$dir)) {
+    return FALSE;
+  }
+  $list = @ftp_nlist($connect,'.');
+  foreach($list as $item){
+    if ($item=='.' || $item=='..') {
+      continue;
+    }
+    if (@ftp_chdir($connect,$item)){
+      ftp_chdir($connect,'..');
+      if (!ftp_plugin_manager_rmdir($connect,$item)) {
+        return FALSE;
+      }
+    }
+    elseif (!ftp_delete($connect,$item)) {
+      return FALSE;
+    }
+  }
+  ftp_chdir($connect, $pwd);
+  if (!ftp_rmdir($connect, $dir)) {
+    return FAlSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Remove the supplied folder using the ftp stream wrapper.
+ *
+ * @param $type
+ *   The type of extension being removed, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be removed by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_remove_extension_wrapper($type, $files, $root, $host, $username, $password) {
+  $dir = '';
+
+  // Write the common part of the url
+  $ftp_base_dir = 'ftp://' . urlencode($username) . ':' . urlencode($password) . "@$host/";
+
+  if (!@is_dir($ftp_base_dir)) {
+    drupal_set_message(t('The supplied username/password combination was not accepted, or no ftp server is running.'), 'error');
+    return FALSE;
+  }
+
+  $ftp_path = $root . $dir;
+
+  // If we the FTP path is not valid, abort.
+  if (!is_dir($ftp_path)) {
+    drupal_set_message(t('Could not reach the proper directory.'), 'error');
+    return FALSE;
+  }
+  // Process each of the files.
+  foreach ($files as $file) {
+    if (trim($file, '\\/') != $file) {
+      if (!ftp_remove_extension_rmdir_stream("$ftp_path/$file")) {
+        return FALSE;
+      }
+    }
+  }
+  return TRUE;
+}
+
+/**
+ * Helper function for ftp_remove_extension_wrapper(). Removes a directory
+ * through the FTP stream.
+ *
+ * @param $target
+ *   The target folder to remove.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ftp_remove_extension_rmdir_stream($target) {
+  if(is_dir($target) && is_writeable($target)) {
+    foreach (new DirectoryIterator($target) as $resource) {
+      if ($resource->isDot()) {
+        unset($resource);
+        continue;
+      }
+      if ($resource->isFile()) {
+        ftp_plugin_manager_rmdir_stream($resource->getPathName());
+      }
+      elseif ($resource->isDir()) {
+        ftp_plugin_manager_rmdir_stream($resource->getRealPath());
+      }
+      unset($resource);
+    }
+    if (@rmdir($target)) {
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
\ No newline at end of file
Index: includes/extensions.inc
===================================================================
RCS file: includes/extensions.inc
diff -N includes/extensions.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/extensions.inc	11 Mar 2009 21:28:00 -0000
@@ -0,0 +1,179 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides an API for automatically installing modules and themes.
+ */
+
+/**
+ * Helper function for extensions_get_file(). Attempts to get a file using
+ * drupal_http_request(), and then stores 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 _extensions_get_file($path) {
+  // Get each of the specified files.
+  $parsed_url = parse_url($path);
+  $local = file_directory_temp() . "/extensions_cache/" . basename($parsed_url['path']);
+
+  // 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;
+}
+
+/**
+ * Untars a file.
+ *
+ * @param $file
+ *   The file to untar.
+ * @return
+ *   An array containing the locations of the extracted files, or FALSE on
+ *   failure.
+ */
+function extensions_untar($file) {
+  $dir = file_directory_temp() . '/extensions_extraction/';
+  $file_safe = escapeshellarg($file);
+  $dir_safe  = escapeshellarg($dir);
+  $file_list = array();
+
+  // Try to use tar to extract the files.
+  if (function_exists('popen')) {
+    $handle = popen("tar -zvxf $file_safe -C $dir_safe", "r");
+    while ($line = fgets($handle)) {
+      $file_list[] = trim($line);
+    }
+    pclose($handle);
+  }
+
+  // If tar returned something, then it is present, so return it.
+  if (!empty($file_list)) {
+    return $file_list;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Downloads the list of available themes/modules.
+ *
+ * @return
+ *   None.
+ */
+function extensions_list_reload() {
+  // Download a new copy of the project-list.
+  $file = drupal_http_request(EXTENSION_DEFAULT_URL . '/project-list/all');
+  if (isset($file->error)) {
+    drupal_set_message(t($file->error), 'error');
+    return;
+  }
+
+  // Parse it.
+  $xml = simplexml_load_string($file->data);
+  if ($xml == FALSE) {
+    return;
+  }
+
+  // @todo Clear cache here.
+
+  // Look at each project node.
+  $drupal_version = constant("DRUPAL_CORE_COMPATIBILITY");
+  foreach ($xml->project AS $project) {
+    // If there isn't a release for this version of Drupal then skip it.
+    if (!isset($project->api_versions)) {
+      continue;
+    }
+    $versions = (array) $project->api_versions;
+    if (!is_array($versions['api_version'])) {
+      $versions['api_version'] = array($versions['api_version']);
+    }
+    if (!in_array($drupal_version, $versions['api_version'])) {
+      continue;
+    }
+
+    // @todo Cache data here.
+  }
+  drupal_set_message(t('The repository was updated successfully.'), 'status');
+  variable_set('extensions_last_reload', REQUEST_TIME);
+}
+
+/**
+ * Get the information about each extension.
+ *
+ * @param $projects
+ *   The project or projects on which information is desired.
+ * @return
+ *   An array containing info on the supplied projects.
+ */
+function extensions_get_release_history($projects) {
+  $version = DRUPAL_CORE_COMPATIBILITY;
+  $results = array();
+
+  // If projects isn't an array, turn it into one.
+  if (!is_array($projects)) {
+    $projects = array($projects);
+  }
+
+  // Look up the data for every project requested.
+  foreach ($projects as $project) {
+    $file = drupal_http_request(EXTENSIONS_DEFAULT_URL . "/$project/$version");
+    $xml = simplexml_load_string($file->data);
+
+    // If it failed, then quit.
+    if ($xml == FALSE) {
+      drupal_set_message(t('Downloading the release history failed for @project.', array('@project' => $project)), "error");
+      return FALSE;
+    }
+
+    // Get the title, release_link and download_link.
+    $results[$project]['title'] = (string)$xml->title;
+
+    // Get information about every release.
+    foreach ($xml->releases->release AS $release) {
+      $release_version = (string)$release->version;
+      $results[$project]['release'][] = array(
+        'release_link' => (string)$release->release_link,
+        'download_link' => (string)$release->download_link,
+        'date' => (string)$release->date,
+        'version' => $release_version,
+      );
+      $results[$project]['version'][] = $release_version;
+    }
+  }
+
+  // Order them and then return the results.
+  ksort($results);
+  return $results;
+}
+
+/**
+ * See if everything that is needed to use the extension manager is available.
+ *
+ * @return
+ *   TRUE if all of the required dependencies are available, FALSE otherwise.
+ */
+function extensions_runnable() {
+  // See if we have a way to untar the files.
+  $handle = popen("tar --version", "r");
+  if (!fgets($handle)) {
+    pclose($handle);
+    return FALSE;
+  }
+  pclose($handle);
+
+  // See if we have any available backends.
+  $backends = extension_list_backends();
+  return !empty($backends);
+}
\ No newline at end of file
Index: includes/extensions.ssh.inc
===================================================================
RCS file: includes/extensions.ssh.inc
diff -N includes/extensions.ssh.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/extensions.ssh.inc	11 Mar 2009 21:41:37 -0000
@@ -0,0 +1,121 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * The SSH Backend for the extensions subsystem.
+ */
+
+/**
+ * Install the supplied files to the appropriate locations.
+ *
+ * @param $type
+ *   The type of extension being added, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be added by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ssh_add_extension($type, $files, $root, $host, $username, $password) {
+  // Figure out the type of files.
+  $dir = "sites/all/${type}s";
+
+  // Connect to the server via ssh, parsing for non-standard port.
+  $parts = explode(':',$host);
+  if (count($parts) == 2) {
+    $connection = ssh2_connect($parts[0], (int)$parts[1]);
+  }
+  else {
+    $connection = ssh2_connect($host);
+  }
+
+  if ($connection == FALSE) {
+    drupal_set_message(t('Could not connect to ssh on this host.'), 'error');
+    return FALSE;
+  }
+
+  if (!ssh2_auth_password($connection, $username, $password)) {
+    drupal_set_message(t('The supplied username/password combination was not accepted.'), 'error');
+    return FALSE;
+  }
+
+  // Prepare the directories to use later.
+  $extract_dir = file_directory_path() .'/extensions_extraction';
+
+  // Process each of the files.
+  foreach ($files as $file) {
+    if (trim($file, "\\/") != $file) {
+      ssh2_exec($connection, "mkdir ". escapeshellarg("$root/$dir/$file"));
+    }
+    else {
+      ssh2_scp_send($connection, "$extract_dir/$file", "$root/$dir/$file");
+    }
+  }
+
+  return TRUE;
+}
+
+/**
+ * Remove the supplied files.
+ *
+ * @param $type
+ *   The type of extension being removed, such as 'module' or 'theme'.
+ * @param $files
+ *   An array of files to be removed by the specified backend.
+ * @param $root
+ *   The location of the drupal installation; may be different from DRUPAL_ROOT
+ *   depending on the backend being used.
+ * @param $host
+ *   The host server for the backend, such as an FTP server for the ftp
+ *   backend.
+ * @param $username
+ *   The username to use to log in to the backend.
+ * @param $password
+ *   The password to use to log in to the backend.
+ * @return
+ *   TRUE on success, or FALSE on failure.
+ */
+function ssh_remove_extension($type, $files, $root, $host, $username, $password) {
+  $dir = 'sites/all';
+
+  // Connect to the server via ssh, parsing for non-standard port.
+  $parts = explode(':',$host);
+  if (count($parts) == 2) {
+    $connection = ssh2_connect($parts[0], (int)$parts[1]);
+  }
+  else {
+    $connection = ssh2_connect($host);
+  }
+
+  if ($connection == FALSE) {
+    drupal_set_message(t('Could not connect to ssh on this host.'), 'error');
+    return FALSE;
+  }
+
+  if (!ssh2_auth_password($connection, $username, $password)) {
+    drupal_set_message(t('The supplied username/password combination was not accepted.'), 'error');
+    return FALSE;
+  }
+
+  // Process each of the files.
+  foreach ($files as $file) {
+    if (trim($file, '\\/') != $file) {
+      ssh2_exec($connection, 'rmdir ' . escapeshellarg("$root/$dir/$file"));
+    }
+    else {
+      ssh2_exec($connection, 'rm ' . escapeshellarg("$root/$dir/$file"));
+    }
+  }
+
+  return TRUE;
+}
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.24
diff -u -p -r1.24 system.api.php
--- modules/system/system.api.php	10 Mar 2009 16:08:43 -0000	1.24
+++ modules/system/system.api.php	11 Mar 2009 19:53:06 -0000
@@ -12,6 +12,52 @@
  */
 
 /**
+ * Define additional backends to be supported for installing/updating modules
+ * and themes from drupal.org.
+ *
+ * By default, ftp and ssh are enabled if the appropriate PHP extensions are
+ * installed. These backends are handled by system.module. Any additional
+ * backends defined here should also define the following callbacks:
+ *  - "NAME_add_extension($type, $files, $root, $host, $username, $password)":
+ *    NAME here corresponds to the keys of the return array; for this example,
+ *    this function would be btp_add_extension(). This function should install
+ *    the files specified by $files to the server specified by $host using the
+ *    credentials specified by $username and $password. $root specifies the
+ *    root Drupal directory, DRUPAL_ROOT by default, but user-alterable in
+ *    case, for example, an ftp user's root directory is different from the
+ *    system's. $type specifies the type of plugin this is; for example,
+ *    'module' or 'theme' depending on the type of resource. Should return TRUE
+ *    on success, or FALSE on failure.
+ *
+ *  - "NAME_remove_extension($type, $files, $root $host, $username, $password)":
+ *    NAME here corresponds to the keys of the return array; for this example,
+ *    this function would be btp_remove_extension(). This function should
+ *    install the files specified by $files to the server specified by $host
+ *    using the credentials specified by $username and $password. $root
+ *    specifies the root Drupal directory, DRUPAL_ROOT by default, but user-
+ *    alterable in case, for example, an ftp user's root directory is different
+ *    from the system's. $type specifies the type of plugin this is; for
+ *    example, 'module' or 'theme' depending on the type of resource. Should
+ *    return TRUE on success, or FALSE on failure.
+ *
+ * @return
+ *   An associative array where the keys correspond to the internal name of the
+ *   backend, used for function calls, and the values correspond to the human
+ *   readable name of the backend.
+ */
+function hook_extension_backends() {
+  $backends = array();
+
+  // BTP (Banana Transfer Protocol) will only be available if the function
+  // btp_unpeel() exists.
+  if (function_exists('btp_unpeel')) {
+    $backends['btp'] = t('BTP (Banana Transfer Protocol)');
+  }
+
+  return $backends;
+}
+
+/**
  * Perform periodic actions.
  *
  * Modules that require to schedule some commands to be executed at regular
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.671
diff -u -p -r1.671 system.module
--- modules/system/system.module	9 Mar 2009 11:44:54 -0000	1.671
+++ modules/system/system.module	11 Mar 2009 18:38:58 -0000
@@ -228,6 +228,27 @@ function system_rdf_namespaces() {
 }
 
 /**
+ * Implementation of hook_backend_plugin().
+ */
+function system_backend_plugin() {
+  $backends = array();
+
+  // FTP backend is only available if the proper PHP extension is installed or
+  // allow_url_fopen is on.
+  if (function_exists('ftp_connect') || ini_get('allow_url_fopen')) {
+    $backends['ftp'] = t('FTP');
+  }
+
+  // SSH2 lib backend is only available if the proper PHP extension is
+  // installed.
+  if (function_exists('ssh2_connect')) {
+    $backends['ssh'] = t('SSH');
+  }
+
+  return $backends;
+}
+
+/**
  * Implementation of hook_elements().
  */
 function system_elements() {
Index: modules/update/update.fetch.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/update/update.fetch.inc,v
retrieving revision 1.15
diff -u -p -r1.15 update.fetch.inc
--- modules/update/update.fetch.inc	14 Jan 2009 21:13:42 -0000	1.15
+++ modules/update/update.fetch.inc	10 Mar 2009 21:50:03 -0000
@@ -77,7 +77,7 @@ function _update_refresh() {
  * @see update_get_projects()
  */
 function _update_build_fetch_url($project, $site_key = '') {
-  $default_url = variable_get('update_fetch_url', UPDATE_DEFAULT_URL);
+  $default_url = variable_get('update_fetch_url', PLUGIN_DEFAULT_URL);
   if (!isset($project['info']['project status url'])) {
     $project['info']['project status url'] = $default_url;
   }
Index: modules/update/update.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/update/update.module,v
retrieving revision 1.30
diff -u -p -r1.30 update.module
--- modules/update/update.module	22 Jan 2009 03:11:54 -0000	1.30
+++ modules/update/update.module	10 Mar 2009 21:48:52 -0000
@@ -9,11 +9,6 @@
  * (admin/reports/status), the module and theme pages, and optionally via email.
  */
 
-/**
- * URL to check for updates, if a given project doesn't define its own.
- */
-define('UPDATE_DEFAULT_URL', 'http://updates.drupal.org/release-history');
-
 // These are internally used constants for this code, do not modify.
 
 /**
