Index: .htaccess
===================================================================
RCS file: /cvs/drupal/drupal/.htaccess,v
retrieving revision 1.100
diff -u -9 -p -r1.100 .htaccess
--- .htaccess	22 Apr 2009 09:45:02 -0000	1.100
+++ .htaccess	27 Apr 2009 22:56:22 -0000
@@ -78,17 +81,16 @@ DirectoryIndex index.php
   # 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.100 2009/04/22 09:45:02 dries Exp $
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.277
diff -u -9 -p -r1.277 bootstrap.inc
--- includes/bootstrap.inc	24 Apr 2009 08:15:50 -0000	1.277
+++ includes/bootstrap.inc	27 Apr 2009 22:56:23 -0000
@@ -412,18 +412,26 @@ function drupal_initialize_variables() {
       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'])) {
+    $_GET['q'] = rawurldecode(substr(strtok($_SERVER['REQUEST_URI'], '?#'), strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '/')) + 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.888
diff -u -9 -p -r1.888 common.inc
--- includes/common.inc	27 Apr 2009 20:19:35 -0000	1.888
+++ includes/common.inc	27 Apr 2009 22:56:23 -0000
@@ -2846,45 +2846,30 @@ 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() that does not escape slashes (for esthetic
+ * reasons).
  *
  * 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.
- *
  * @param $text
  *   String to encode
  */
 function drupal_urlencode($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));
-  }
+  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.167
diff -u -9 -p -r1.167 file.inc
--- includes/file.inc	22 Apr 2009 09:45:02 -0000	1.167
+++ includes/file.inc	27 Apr 2009 22:56:23 -0000
@@ -82,21 +82,40 @@ define('FILE_STATUS_PERMANENT', 1);
  * @param $path A string containing the path of the file to generate URL for.
  * @return A string containing a URL that can be used to download the file.
  */
 function file_create_url($path) {
   // Strip file_directory_path from $path. We only include relative paths in
   // URLs.
   if (strpos($path, file_directory_path() . '/') === 0) {
     $path = trim(substr($path, strlen(file_directory_path())), '\\/');
   }
+  if (substr(PHP_OS, 0, 3) == 'WIN') {
+    // On Windows, both "/" and "\" may be used as directory separator, but
+    // use "/" for prettier URLs.
+    $path = strtr($path, '\\', '/');
+  }
   switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
     case FILE_DOWNLOADS_PUBLIC:
-      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('\\', '/', $path);
+      if (substr(PHP_OS, 0, 3) == 'WIN') {
+        // The filesystem functions in PHP 5 on Windows do not support
+        // UTF-8-encoded filenames but always assume that filenames are encoded
+        // in Windows-1252. In order to support filenames containing characters
+        // not in Windows-1252, Drupal passes UTF-8-encoded filenames to the
+        // filesystem functions, making PHP treat each octet in the
+        // UTF-8-encoded string as a Windows-1252 character. This will make the
+        // filenames look mangled (like viewing an UTF-8-encoded file in a
+        // text editor without support for UTF-8) when viewed with external
+        // programs, e.g. Windows Explorer. The web server does not know about
+        // this convention, so we need to make the URL reflecting the actual
+        // filenames when using public files.
+        $path = drupal_convert_to_utf8($path, 'Windows-1252');
+      }
+      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('%2F', '/', rawurlencode($path));
     case FILE_DOWNLOADS_PRIVATE:
       return url('system/files/' . $path, array('absolute' => TRUE));
   }
 }
 
 /**
  * Make sure the destination is a complete path and resides in the file system
  * directory, if it is not prepend the file system directory.
  *
@@ -692,18 +711,24 @@ function file_unmunge_filename($filename
  * @param $basename
  *   String filename
  * @param $directory
  *   String directory
  * @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);
+  if (substr(PHP_OS, 0, 3) == 'WIN') {
+    // These characters are not allowed in filenames on Windows.
+    $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
+  }
   $destination = $directory . '/' . $basename;
 
   if (file_exists($destination)) {
     // Destination file already exists, generate an alternative.
     $pos = strrpos($basename, '.');
     if ($pos !== FALSE) {
       $name = substr($basename, 0, $pos);
       $ext = substr($basename, $pos);
     }
@@ -1918,10 +1943,10 @@ function drupal_chmod($path, $mode = NUL
     return TRUE;
   }
 
   watchdog('file', 'The file permissions could not be set on %path.', array('%path' => $path), WATCHDOG_ERROR);
   return FALSE;
 }
 
 /**
  * @} End of "defgroup file".
- */
\ No newline at end of file
+ */
Index: misc/drupal.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.js,v
retrieving revision 1.54
diff -u -9 -p -r1.54 drupal.js
--- misc/drupal.js	27 Apr 2009 20:19:35 -0000	1.54
+++ misc/drupal.js	27 Apr 2009 22:56:23 -0000
@@ -257,25 +257,23 @@ Drupal.freezeHeight = function () {
 
 /**
  * Unfreeze the body height.
  */
 Drupal.unfreezeHeight = function () {
   $('#freeze-height').remove();
 };
 
 /**
- * Wrapper to address the mod_rewrite url encoding bug
- * (equivalent of drupal_urlencode() in PHP).
+ * URL-encode string (equivalent of drupal_urlencode() in PHP).
  */
 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.29
diff -u -9 -p -r1.29 file.test
--- modules/simpletest/tests/file.test	22 Apr 2009 09:45:03 -0000	1.29
+++ modules/simpletest/tests/file.test	27 Apr 2009 22:56:24 -0000
@@ -1932,18 +1932,194 @@ class FileDownloadTest extends FileTestC
 
     // Try non-existent file.
     $url = file_create_url($this->randomName());
     $this->drupalHead($url);
     $this->assertResponse(404, t('Correctly returned 404 response for a non-existent file.'));
   }
 }
 
 /**
+ * Test file_create_url() by making round-trips through the web server.
+ */
+class FileCreateUrlUnitTest extends FileTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('URL generation'),
+      'description' => t('Test URL generation'),
+      'group' => t('File'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('file_test');
+    // Clear out any hook calls.
+    file_test_reset();
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PUBLIC.
+   */
+  function testFileCreateUrlPublic() {
+    global $base_url;
+    variable_set('file_downloads', FILE_DOWNLOADS_PUBLIC);
+
+    $file = " -._~!$'\"()*@[]?&+%#,;=:\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.
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      $expected_url = $base_url . '/' . file_directory_path() . '/' .
+        '%20-._%7E%21%24%27_%28%29_%40%5B%5D_%26%2B%25%23%2C%3B%3D___' .
+        '%2523%2525%2526%252B%252F%253F' .
+        '%C3%83%C2%A9%C3%83%C2%B8%C3%83%C2%AF%C3%90%C2%B2%C3%8E%C2%B2%C3%A4%C2%B8%C2%AD%C3%A5%C5%93%E2%80%B9%C3%A6%E2%80%BA%C2%B8%C3%9B%C5%BE';
+    }
+    else {
+      $expected_url = $base_url . '/' . file_directory_path() . '/' .
+        '%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($file, $expected_url);
+
+    // On Windows, "\" works as path separator; on other platforms it is
+    // treated as any other character.
+    $file = 'foo\bar';
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      $expected_url = $base_url . '/' . file_directory_path() . '/foo/bar';
+    }
+    else {
+      $expected_url = $base_url . '/' . file_directory_path() . '/foo%5Cbar';
+    }
+    $this->checkUrl($file, $expected_url);
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs enabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlEnabled() {
+    global $base_url;
+    variable_set('clean_url', '1');
+    variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE);
+
+    $file = " -._~!$'\"()*@[]?&+%#,;=:\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.
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      $expected_url = $base_url . '/system/files/' .
+        '%20-._%7E%21%24%27_%28%29_%40%5B%5D_%26%2B%25%23%2C%3B%3D___' .
+        '%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';
+    }
+    else {
+      $expected_url = $base_url . '/system/files/' .
+        '%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($file, $expected_url);
+
+    // "0" is tricky because "0" == FALSE.
+    $file = '0';
+    $expected_url = $base_url . '/system/files/0';
+    $this->checkUrl($file, $expected_url);
+
+    // On Windows, "\" works as path separator; on other platforms it is
+    // treated as any other character.
+    $file = 'foo\bar';
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      $expected_url = $base_url . '/system/files/foo/bar';
+    }
+    else {
+      $expected_url = $base_url . '/system/files/foo%5Cbar';
+    }
+    $this->checkUrl($file, $expected_url);
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs
+   * disabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlDisabled() {
+    global $base_url;
+    variable_set('clean_url', '0');
+    variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE);
+
+    $file = " -._~!$'\"()*@[]?&+%#,;=:\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.
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      $expected_url = $base_url . '/?q=system/files/' .
+        '%20-._%7E%21%24%27_%28%29_%40%5B%5D_%26%2B%25%23%2C%3B%3D___' .
+        '%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';
+    }
+    else {
+      $expected_url = $base_url . '/?q=system/files/' .
+        '%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($file, $expected_url);
+
+    // "0" is tricky because "0" == FALSE.
+    $file = '0';
+    $expected_url = $base_url . '/?q=system/files/0';
+    $this->checkUrl($file, $expected_url);
+
+    // On Windows, "\" works as path separator; on other platforms it is
+    // treated as any other character.
+    $file = 'foo\bar';
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      $expected_url = $base_url . '/?q=system/files/foo/bar';
+    }
+    else {
+      $expected_url = $base_url . '/?q=system/files/foo%5Cbar';
+    }
+    $file = 'foo\bar';
+    $this->checkUrl($file, $expected_url);
+  }
+
+  /**
+   * Check that the URL generated by file_create_url() for the specified file
+   * equals the specified URL, then fetch the URL and compare the contents to
+   * the file.
+   *
+   * @param $path
+   *   A filepath.
+   * @param $expected_url
+   *   The expected URL.
+   */
+  private function checkUrl($path, $expected_url) {
+    // Convert $path to a valid filename, i.e. strip characters not supported
+    // by the filesystem, and create the file.
+    $filepath = file_create_filename($path, file_directory_path());
+    $directory = dirname($filepath); 
+    file_check_directory($directory, FILE_CREATE_DIRECTORY);
+    $file = $this->createFile($filepath);
+
+    $url = file_create_url($file->filepath);
+    $this->assertEqual($url, $expected_url, t('Generated URL matches expected URL'));
+
+    if (variable_get('file_downloads', FALSE) == FILE_DOWNLOADS_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->filepath), t('Contents of the file are correct.'));
+    }
+
+    file_delete($file);
+  }
+}
+
+/**
  * Tests for file_munge_filename() and file_unmunge_filename().
  */
 class FileNameMungingTest extends FileTestCase {
   public static function getInfo() {
     return array(
       'name' => t('File naming'),
       'description' => t('Test filename munging and unmunging.'),
       'group' => t('File'),
     );
