Index: .htaccess
===================================================================
RCS file: /cvs/drupal/drupal/.htaccess,v
retrieving revision 1.104
diff -u -9 -p -r1.104 .htaccess
--- .htaccess	16 Aug 2009 12:10:36 -0000	1.104
+++ .htaccess	5 Sep 2009 13:47:57 -0000
@@ -82,17 +82,16 @@ DirectoryIndex index.php index.html inde
   # VirtualDocumentRoot and the rewrite rules are not working properly.
   # For example if your site is at http://example.com/drupal uncomment and
   # modify the following line:
   # RewriteBase /drupal
   #
   # If your site is running in a VirtualDocumentRoot at http://example.com/,
   # uncomment the following line:
   # RewriteBase /
 
-  # Rewrite URLs of the form 'x' to the form 'index.php?q=x'.
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteCond %{REQUEST_URI} !=/favicon.ico
-  RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
+  RewriteRule ^ index.php [L]
 </IfModule>
 
 # $Id: .htaccess,v 1.104 2009/08/16 12:10:36 dries Exp $
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.303
diff -u -9 -p -r1.303 bootstrap.inc
--- includes/bootstrap.inc	5 Sep 2009 13:05:30 -0000	1.303
+++ includes/bootstrap.inc	5 Sep 2009 13:47:58 -0000
@@ -463,18 +463,32 @@ function drupal_environment_initialize()
       exit;
     }
   }
   else {
     // Some pre-HTTP/1.1 clients will not send a Host header. Ensure the key is
     // defined for E_ALL compliance.
     $_SERVER['HTTP_HOST'] = '';
   }
 
+  // When clean URLs are enabled, emulate ?q=foo/bar using REQUEST_URI. It is
+  // not possible to append the query string using mod_rewrite without the B
+  // flag (this was added in Apache 2.2.8), because mod_rewrite unescapes the
+  // path before passing it on to PHP.
+  if (!isset($_GET['q']) && isset($_SERVER['REQUEST_URI'])) {
+    $request_path = $_SERVER['REQUEST_URI'];
+    $query_pos = strpos($_SERVER['REQUEST_URI'], '?');
+    if ($query_pos !== FALSE) {
+      $request_path = substr($request_path, 0, $query_pos);
+    }
+    $base_path_len = strlen(trim(dirname($_SERVER['SCRIPT_NAME']), '\\/'));
+    $_GET['q'] = substr(urldecode($request_path), $base_path_len + 1);
+  }
+
   // Enforce E_ALL, but allow users to set levels not part of E_ALL.
   error_reporting(E_ALL | error_reporting());
 
   // Override PHP settings required for Drupal to work properly.
   // sites/default/default.settings.php contains more runtime settings.
   // The .htaccess file contains settings that cannot be changed at runtime.
 
   // Prevent PHP from generating HTML error messages.
   ini_set('html_errors', 0);
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.983
diff -u -9 -p -r1.983 common.inc
--- includes/common.inc	5 Sep 2009 13:05:30 -0000	1.983
+++ includes/common.inc	5 Sep 2009 13:47:59 -0000
@@ -2256,19 +2256,19 @@ function url($path = NULL, array $option
   }
 
   if (function_exists('custom_url_rewrite_outbound')) {
     // Modules may alter outbound links by reference.
     custom_url_rewrite_outbound($path, $options, $original_path);
   }
 
   $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
   $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
-  $path = drupal_encode_path($prefix . $path);
+  $path = drupal_urlencode($prefix . $path);
 
   if (variable_get('clean_url', '0')) {
     // With Clean URLs.
     if ($options['query']) {
       return $base . $path . '?' . $options['query'] . $options['fragment'];
     }
     else {
       return $base . $path . $options['fragment'];
     }
@@ -3549,47 +3549,31 @@ function drupal_json($var = NULL) {
   // We are returning JavaScript, so tell the browser.
   drupal_set_header('Content-Type', 'text/javascript; charset=utf-8');
 
   if (isset($var)) {
     echo drupal_to_js($var);
   }
 }
 
 /**
- * Wrapper around urlencode() which avoids Apache quirks.
+ * Wrapper around rawurlencode().
+ *
+ * For aesthetic reasons slashes are not escaped.
  *
  * Should be used when placing arbitrary data in an URL. Note that Drupal paths
  * are urlencoded() when passed through url() and do not require urlencoding()
  * of individual components.
  *
- * Notes:
- * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature'
- *   in Apache where it 404s on any path containing '%2F'.
- * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean
- *   URLs are used, which are interpreted as delimiters by PHP. These
- *   characters are double escaped so PHP will still see the encoded version.
- * - With clean URLs, Apache changes '//' to '/', so every second slash is
- *   double escaped.
- * - This function should only be used on paths, not on query string arguments,
- *   otherwise unwanted double encoding will occur.
- *
  * @param $text
  *   String to encode
  */
-function drupal_encode_path($text) {
-  if (variable_get('clean_url', '0')) {
-    return str_replace(array('%2F', '%26', '%23', '//'),
-                        array('/', '%2526', '%2523', '/%252F'),
-                        rawurlencode($text));
-  }
-  else {
-    return str_replace('%2F', '/', rawurlencode($text));
-  }
+function drupal_urlencode($text) {
+  return str_replace('%2F', '/', rawurlencode($text));
 }
 
 /**
  * Returns a string of highly randomized bytes (over the full 8-bit range).
  *
  * This function is better than simply calling mt_rand() or any other built-in
  * PHP function because it can return a long string of bytes (compared to < 4
  * bytes normally from mt_rand()) and uses the best available pseudo-random source.
  *
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.190
diff -u -9 -p -r1.190 file.inc
--- includes/file.inc	31 Aug 2009 05:47:33 -0000	1.190
+++ includes/file.inc	5 Sep 2009 13:47:59 -0000
@@ -862,18 +862,21 @@ function file_unmunge_filename($filename
  * @param $basename
  *   String filename
  * @param $directory
  *   String containing the directory or parent URI.
  * @return
  *   File path consisting of $directory and a unique filename based off
  *   of $basename.
  */
 function file_create_filename($basename, $directory) {
+  // Strip control characters.
+  $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
+
   // A URI or path may already have a trailing slash or look like "public://".
   if (substr($directory, -1) == '/') {
     $separator = '';
   }
   else {
     $separator = '/';
   }
 
   $destination = $directory . $separator . $basename;
Index: includes/stream_wrappers.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/stream_wrappers.inc,v
retrieving revision 1.6
diff -u -9 -p -r1.6 stream_wrappers.inc
--- includes/stream_wrappers.inc	31 Aug 2009 05:47:33 -0000	1.6
+++ includes/stream_wrappers.inc	5 Sep 2009 13:47:59 -0000
@@ -574,19 +574,19 @@ class DrupalPublicStreamWrapper extends 
   }
 
   /**
    * Overrides getExternalUrl().
    *
    * Return the HTML URI of a public file.
    */
   function getExternalUrl() {
     $path = str_replace('\\', '/', file_uri_target($this->uri));
-    return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . $path;
+    return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_urlencode($path);
   }
 }
 
 
 /**
  * Drupal private (private://) stream wrapper class.
  *
  * Provides support for storing privately accessible files with the Drupal file
  * interface.
Index: misc/autocomplete.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/autocomplete.js,v
retrieving revision 1.34
diff -u -9 -p -r1.34 autocomplete.js
--- misc/autocomplete.js	5 Sep 2009 12:03:31 -0000	1.34
+++ misc/autocomplete.js	5 Sep 2009 13:47:59 -0000
@@ -270,19 +270,19 @@ Drupal.ACDB.prototype.search = function 
   if (this.timer) {
     clearTimeout(this.timer);
   }
   this.timer = setTimeout(function () {
     db.owner.setStatus('begin');
 
     // Ajax GET request for autocompletion.
     $.ajax({
       type: 'GET',
-      url: db.uri + '/' + Drupal.encodePath(searchString),
+      url: db.uri + '/' + Drupal.encodeURIComponent(searchString),
       dataType: 'json',
       success: function (matches) {
         if (typeof matches.status == 'undefined' || matches.status != 0) {
           db.cache[searchString] = matches;
           // Verify if these are still the matches the user wants to see.
           if (db.searchString == searchString) {
             db.owner.found(matches);
           }
           db.owner.setStatus('found');
Index: misc/drupal.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.js,v
retrieving revision 1.58
diff -u -9 -p -r1.58 drupal.js
--- misc/drupal.js	31 Aug 2009 05:51:07 -0000	1.58
+++ misc/drupal.js	5 Sep 2009 13:47:59 -0000
@@ -261,26 +261,25 @@ Drupal.freezeHeight = function () {
 
 /**
  * Unfreeze the body height.
  */
 Drupal.unfreezeHeight = function () {
   $('#freeze-height').remove();
 };
 
 /**
- * Wrapper around encodeURIComponent() which avoids Apache quirks (equivalent of
- * drupal_encode_path() in PHP). This function should only be used on paths, not
- * on query string arguments.
+ * Wrapper around encodeURIComponent().
+ *
+ * For aesthetic reasons slashes are not escaped.
  */
-Drupal.encodePath = function (item, uri) {
+Drupal.encodeURIComponent = function (item, uri) {
   uri = uri || location.href;
-  item = encodeURIComponent(item).replace(/%2F/g, '/');
-  return (uri.indexOf('?q=') != -1) ? item : item.replace(/%26/g, '%2526').replace(/%23/g, '%2523').replace(/\/\//g, '/%252F');
+  return encodeURIComponent(item).replace(/%2F/g, '/');
 };
 
 /**
  * Get the text selection in a textarea.
  */
 Drupal.getSelection = function (element) {
   if (typeof element.selectionStart != 'number' && document.selection) {
     // The current selection.
     var range1 = document.selection.createRange();
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.43
diff -u -9 -p -r1.43 file.test
--- modules/simpletest/tests/file.test	31 Aug 2009 05:47:34 -0000	1.43
+++ modules/simpletest/tests/file.test	5 Sep 2009 13:47:59 -0000
@@ -1874,18 +1874,20 @@ class FileDownloadTest extends FileTestC
     return array(
       'name' => 'File download',
       'description' => 'Tests for file download/transfer functions.',
       'group' => 'File API',
     );
   }
 
   function setUp() {
     parent::setUp('file_test');
+    // Clear out any hook calls.
+    file_test_reset();
   }
 
   /**
    * Test the public file transfer system.
    */
   function testPublicFileTransfer() {
     // Test generating an URL to a created file.
     $file = $this->createFile();
     $url = file_create_url($file->uri);
@@ -1923,18 +1925,81 @@ class FileDownloadTest extends FileTestC
     file_test_set_return('download', -1);
     $this->drupalHead($url);
     $this->assertResponse(403, t('Correctly denied access to a file when file_test sets the header to -1.'));
 
     // Try non-existent file.
     $url = file_create_url('private://' . $this->randomName());
     $this->drupalHead($url);
     $this->assertResponse(404, t('Correctly returned 404 response for a non-existent file.'));
   }
+
+  /**
+   * Test file_create_url().
+   */
+  function testFileCreateUrl() {
+    global $base_url;
+
+    $basename = " -._~!$'\"()*@[]?&+%#,;=:\n\x00" . // "Special" ASCII characters.
+      "%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
+      "éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
+    $basename_encoded = '%20-._%7E%21%24%27%22%28%29%2A%40%5B%5D%3F%26%2B%25%23%2C%3B%3D%3A__' .
+      '%2523%2525%2526%252B%252F%253F' .
+      '%C3%A9%C3%B8%C3%AF%D0%B2%CE%B2%E4%B8%AD%E5%9C%8B%E6%9B%B8%DB%9E';
+
+    $this->checkUrl('public', '', $basename, $base_url . '/' . file_directory_path() . '/' . $basename_encoded);
+    $this->checkUrl('private', '', $basename, $base_url . '/system/files/' . $basename_encoded);
+    $this->checkUrl('private', '', $basename, $base_url . '/?q=system/files/' . $basename_encoded, '0');
+  }
+
+  /**
+   * Download a file from the URL generated by file_create_url().
+   *
+   * Create a file with the specified scheme, directory and filename; check that
+   * the URL generated by file_create_url() for the specified file equals the
+   * specified URL; fetch the URL and then compare the contents to the file.
+   *
+   * @param $scheme
+   *   A scheme, e.g. "public"
+   * @param $directory
+   *   A directory, possibly ""
+   * @param $filename
+   *   A filename
+   * @param $expected_url
+   *   The expected URL
+   * @param $clean_url
+   *   The value of the clean_url setting
+   */
+  private function checkUrl($scheme, $directory, $filename, $expected_url, $clean_url = '1') {
+    variable_set('clean_url', $clean_url);
+
+    // Convert $path to a valid filename, i.e. strip characters not supported
+    // by the filesystem, and create the file.
+    $filepath = file_create_filename($filename, $directory);
+    $directory_uri = $scheme . '://' . dirname($filepath); 
+    file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY);
+    $file = $this->createFile($filepath, NULL, $scheme);
+
+    $url = file_create_url($file->uri);
+    $this->assertEqual($url, $expected_url, t('Generated URL matches expected URL.'));
+
+    if ($scheme == 'private') {
+      // Tell the implementation of hook_file_download() in file_test.module
+      // that this file may be downloaded.
+      file_test_set_return('download', array('x-foo' => 'Bar'));
+    }
+
+    $this->drupalGet($url);
+    if ($this->assertResponse(200) == 'pass') {
+      $this->assertRaw(file_get_contents($file->uri), t('Contents of the file are correct.'));
+    }
+
+    file_delete($file);
+  }
 }
 
 /**
  * Tests for file URL rewriting.
  */
 class FileURLRewritingTest extends FileTestCase {
   public static function getInfo() {
     return array(
       'name' => t('File URL rewriting'),
