Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.842
diff -u -9 -p -r1.842 common.inc
--- includes/common.inc	5 Jan 2009 22:23:58 -0000	1.842
+++ includes/common.inc	7 Jan 2009 20:46:02 -0000
@@ -1666,32 +1666,26 @@ function url($path = NULL, array $option
     if ($options['query']) {
       $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;
 
   // The special path '<front>' links to the default front page.
@@ -1705,19 +1699,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_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'];
     }
   }
   else {
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.151
diff -u -9 -p -r1.151 file.inc
--- includes/file.inc	6 Jan 2009 12:00:40 -0000	1.151
+++ includes/file.inc	7 Jan 2009 20:46:02 -0000
@@ -84,19 +84,43 @@ 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);
+      $path = file_directory_path() . '/' . str_replace('\\', '/', $path);
+      $options = array(
+        'fragment' => '',
+        'query' => '',
+        'absolute' => TRUE,
+        'alias' => FALSE,
+        'prefix' => '',
+        'base_url' => $GLOBALS['base_url'],
+      );
+      if (function_exists('custom_url_rewrite_outbound')) {
+        // Modules may alter outbound links by reference.
+        custom_url_rewrite_outbound($path, $options, $path);
+      }
+
+      $base = $options['absolute'] ? $options['base_url'] .'/' : base_path();
+      $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
+      $path = str_replace('%2F', '/', rawurlencode($prefix . $path));
+
+      if ($options['query']) {
+        return $base . $path . '?' . $options['query'] . $options['fragment'];
+      }
+      else {
+        return $base . $path . $options['fragment'];
+      }
+      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.
  *
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.19
diff -u -9 -p -r1.19 file.test
--- modules/simpletest/tests/file.test	6 Jan 2009 12:00:40 -0000	1.19
+++ modules/simpletest/tests/file.test	7 Jan 2009 20:46:02 -0000
@@ -1206,18 +1206,88 @@ 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().
+ */
+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');
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PUBLIC.
+   */
+  function testFileCreateUrlPublic() {
+    global $base_path;
+    variable_set('file_downloads', FILE_DOWNLOADS_PUBLIC);
+
+    $path = file_directory_path() . '/' . $this->randomName();
+    custom_url_rewrite_outbound_set($path, 'foo1', array('query' => 'a=1&b=1', 'absolute' => FALSE));
+    $this->assertEqual(file_create_url($path), $base_path . 'foo1?a=1&b=1', t('Generated URL matches expected URL'));
+
+    $path = file_directory_path() . '/' . $this->randomName();
+    custom_url_rewrite_outbound_set($path, $path, array('base_url' => 'http://example.org', 'absolute' => TRUE));
+    $this->assertEqual(file_create_url($path), 'http://example.org/' . $path, t('Generated URL matches expected URL'));
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs enabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlEnabled() {
+    global $base_path;
+    variable_set('clean_url', '1');
+    variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE);
+
+    $file = $this->randomName();
+    custom_url_rewrite_outbound_set('system/files/' . $file, 'foo2', array('query' => 'a=2&b=2', 'absolute' => FALSE));
+    $this->assertEqual(file_create_url(file_directory_path() . '/' . $file), $base_path . 'foo2?a=2&b=2', t('Generated URL matches expected URL'));
+
+    $file = $this->randomName();
+    $path = 'system/files/' . $file;
+    custom_url_rewrite_outbound_set($path, $path, array('base_url' => 'http://example.org', 'absolute' => TRUE));
+    $this->assertEqual(file_create_url(file_directory_path() . '/' . $file), 'http://example.org/' . $path, t('Generated URL matches expected URL'));
+  }
+
+  /**
+   * Test file_create_url() using FILE_DOWNLOADS_PRIVATE and clean URLs
+   * disabled.
+   */
+  function testFileCreateUrlPrivateCleanUrlDisabled() {
+    global $base_path;
+    variable_set('clean_url', '0');
+    variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE);
+
+    $file = $this->randomName();
+    custom_url_rewrite_outbound_set('system/files/' . $file, 'foo3', array('query' => 'a=3&b=3', 'absolute' => FALSE));
+    $this->assertEqual(file_create_url(file_directory_path() . '/' . $file), $base_path . '?q=foo3&a=3&b=3', t('Generated URL matches expected URL'));
+
+    $file = $this->randomName();
+    $path = 'system/files/' . $file;
+    custom_url_rewrite_outbound_set($path, $path, array('base_url' => 'http://example.org', 'absolute' => TRUE));
+    $this->assertEqual(file_create_url(file_directory_path() . '/' . $file), 'http://example.org/?q=' . $path, t('Generated URL matches expected URL'));
+  }
+}
+
+/**
  * Tests for file_munge_filename() and file_unmunge_filename().
  */
 class FileNameMungingTest extends FileTestCase {
   function getInfo() {
     return array(
       'name' => t('File naming'),
       'description' => t('Test filename munging and unmunging.'),
       'group' => t('File'),
     );
Index: modules/simpletest/tests/file_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file_test.module,v
retrieving revision 1.6
diff -u -9 -p -r1.6 file_test.module
--- modules/simpletest/tests/file_test.module	31 Dec 2008 11:08:47 -0000	1.6
+++ modules/simpletest/tests/file_test.module	7 Jan 2009 20:46:02 -0000
@@ -209,9 +209,35 @@ function file_test_file_move($file, $sou
   _file_test_log_call('move', array($file, $source));
 }
 
 /**
  * Implementation of hook_file_delete().
  */
 function file_test_file_delete($file) {
   _file_test_log_call('delete', array($file));
 }
+
+/**
+ * Assign path aliases that custom_url_rewrite_outbound() should use.
+ *
+ * @param $original_path
+ *   Path that should be rewritten.
+ * @param $new_path
+ *   Path for to rewrite to.
+ * @param $options
+ *   Additional options to set, see url().
+ */
+function custom_url_rewrite_outbound_set($original_path, $new_path, array $options) {
+  global $file_test_rewrite_outbound;
+  $file_test_rewrite_outbound[$original_path] = array($new_path, $options);
+}
+
+/**
+ * Implement custom_url_rewrite_outbound() "pseudo-hook".
+ */
+function custom_url_rewrite_outbound(&$path, &$options, $original_path) {
+  global $file_test_rewrite_outbound;
+  if (isset($file_test_rewrite_outbound[$path])) {
+    list($path, $new_options) = $file_test_rewrite_outbound[$path];
+    $options = $new_options + $options;
+  }
+}
