Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.128
diff -u -8 -p -r1.128 file.inc
--- includes/file.inc	14 Aug 2008 12:10:47 -0000	1.128
+++ includes/file.inc	21 Aug 2008 21:18:47 -0000
@@ -78,17 +78,17 @@ define('FILE_STATUS_PERMANENT', 1);
  */
 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())), '\\/');
   }
   switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
     case FILE_DOWNLOADS_PUBLIC:
-      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('\\', '/', $path);
+      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace(array('%2F', '%5C'), '/', 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.
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.788
diff -u -8 -p -r1.788 common.inc
--- includes/common.inc	21 Aug 2008 19:36:36 -0000	1.788
+++ includes/common.inc	21 Aug 2008 21:18:48 -0000
@@ -1325,30 +1325,24 @@ function url($path = NULL, $options = ar
       $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query'];
     }
     // Reassemble.
     return $path . $options['fragment'];
   }
 
   global $base_url;
   static $script;
-  static $clean_url;
 
   if (!isset($script)) {
     // On some web servers, such as IIS, we can't omit "index.php". So, we
     // generate "index.php?q=foo" instead of "?q=foo" on anything that is not
     // Apache.
     $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : '';
   }
 
-  // Cache the clean_url variable to improve performance.
-  if (!isset($clean_url)) {
-    $clean_url = (bool)variable_get('clean_url', '0');
-  }
-
   if (!isset($options['base_url'])) {
     // The base_url might be rewritten from the language rewrite in domain mode.
     $options['base_url'] = $base_url;
   }
 
   // Preserve the original path before aliasing.
   $original_path = $path;
 
@@ -1364,17 +1358,17 @@ function url($path = NULL, $options = ar
     // 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_urlencode($prefix . $path);
 
-  if ($clean_url) {
+  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'];
     }
   }
@@ -2326,29 +2320,30 @@ function drupal_json($var = NULL) {
  *
  * 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
+ * - When clean URLs are used, mod_rewrite unescapes %-encoded occurences of the
+ *   characters  % / & # +  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'),
+    // Decoded:              %        /      &        #        +        //
+    return str_replace(array('%25',   '%2F', '%26',   '%23',   '%2B',   '//'),
+                       array('%2525', '/',   '%2526', '%2523', '%252B', '/%252F'),
                        rawurlencode($text));
   }
   else {
     return str_replace('%2F', '/', rawurlencode($text));
   }
 }
 
 /**
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: modules/simpletest/tests/file.test
diff -N modules/simpletest/tests/file.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file.test	21 Aug 2008 21:18:48 -0000
@@ -0,0 +1,91 @@
+<?php
+// $Id: file.test,v 1.1 2008/08/08 08:08:08 nobody Exp $
+
+/**
+ * @file
+ * Unit tests for the Drupal File API.
+ */
+
+class FileCreateUrlTestCase extends DrupalWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('URL generation'),
+      'description' => t('Verify that URLs generated by file_create_url() point to the right file on the webserver'),
+      'group' => t('File API'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    parent::setUp('file_test');
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PUBLIC.
+   */
+  function testFileCreateUrlPublic() {
+    $this->_testFileCreateUrl(FILE_DOWNLOADS_PUBLIC);
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs enabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlEnabled() {
+    variable_set('clean_url', '1');
+    $this->_testFileCreateUrl(FILE_DOWNLOADS_PRIVATE);
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs disabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlDisabled() {
+    variable_set('clean_url', '0');
+    $this->_testFileCreateUrl(FILE_DOWNLOADS_PRIVATE);
+  }
+
+  /**
+   * Test file_create_url().
+   * @param $file_downloads
+   *   Download method, either FILE_DOWNLOADS_PUBLIC or FILE_DOWNLOADS_PRIVATE
+   */
+  private function _testFileCreateUrl($file_downloads) {
+    $files = array(
+      'foo.txt',
+      'dir/file',
+      'dir/subdir/file',
+      ' -._~%!$',
+      '\'"()*@[]',
+      '?&+%#',
+      ',;=:',
+      '¤£^§½|`´',
+      '%23%25%26%2B%2F%3F',
+      'æøåéüöï',
+    );
+
+    variable_set('file_downloads', $file_downloads);
+    if ($file_downloads == FILE_DOWNLOADS_PRIVATE) {
+      variable_set('file_test_file_download', $files);
+    }
+
+    foreach ($files as $file) {
+      file_check_directory(file_create_path(dirname($file)), FILE_CREATE_DIRECTORY);
+      $path = file_create_path($file);
+      $this->assertTrue($path, t('Got valid path for file ' . $file));
+
+      $content = 'Generated by ' . __CLASS__ . '() at ' . microtime(true) . ': ' . $path;
+      $ok = file_put_contents($path, $content);
+      $this->assertTrue($ok, 'Saved file ' . $path);
+
+      $url = file_create_url($path);
+      $this->assertFalse(preg_match('@(?<=^|/)\.{1,2}(?=/|$)@', $url), 'No /./ or /../ segments');
+      $this->drupalGet($url);
+
+      if ($this->assertNoRaw(t('Page not found'), t('File found')) == 'pass') {
+        $this->assertEqual($content, $this->drupalGetContent());
+      }
+    }
+  }
+}
Index: modules/simpletest/tests/file_test.info
===================================================================
RCS file: modules/simpletest/tests/file_test.info
diff -N modules/simpletest/tests/file_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file_test.info	21 Aug 2008 21:18:48 -0000
@@ -0,0 +1,8 @@
+; $Id: file_test.info,v 1.1 2008/08/08 08:08:08 nobody Exp $
+name = File API test
+description = Support module for File API tests
+package = Testing
+version = VERSION
+core = 7.x
+files[] = file_test.module
+hidden = TRUE
Index: modules/simpletest/tests/file_test.module
===================================================================
RCS file: modules/simpletest/tests/file_test.module
diff -N modules/simpletest/tests/file_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file_test.module	21 Aug 2008 21:18:48 -0000
@@ -0,0 +1,18 @@
+<?php
+
+// $Id: file_test.module,v 1.1 2008/08/08 08:08:08 nobody Exp $
+
+/**
+ * Implementation of hook_file_download().
+ */
+function file_test_file_download($filepath) {
+  $files = variable_get('file_test_file_download', array());
+  if (in_array($filepath, $files)) {
+    // Return non-empty array to tell file_download() that download is permitted
+    return array(
+      'X-Foo: Bar',
+    );
+  }
+}
+
+?>
\ No newline at end of file
