Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.938
diff -u -F^f -r1.938 common.inc
--- includes/common.inc	22 Jul 2009 04:45:35 -0000	1.938
+++ includes/common.inc	22 Jul 2009 10:22:15 -0000
@@ -2487,15 +2487,15 @@ function drupal_get_css($css = NULL) {
             // If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*,
             // regardless of whether preprocessing is on or off.
             if (!$preprocess && $type == 'module') {
-              $no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $data . $query_string . '" />' . "\n";
+              $no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . file_create_url($data) . $query_string . '" />' . "\n";
             }
             // If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*,
             // regardless of whether preprocessing is on or off.
             elseif (!$preprocess && $type == 'theme') {
-              $no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $data . $query_string . '" />' . "\n";
+              $no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . file_create_url($data) . $query_string . '" />' . "\n";
             }
             else {
-              $output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $data . $query_string . '" />' . "\n";
+              $output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . file_create_url($data) . $query_string . '" />' . "\n";
             }
           }
         }
@@ -2507,7 +2507,7 @@ function drupal_get_css($css = NULL) {
       // starting with "ad*".
       $filename = 'css_' . md5(serialize($types) . $query_string) . '.css';
       $preprocess_file = drupal_build_css_cache($types, $filename);
-      $output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $preprocess_file . '" />' . "\n";
+      $output .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . file_create_url($preprocess_file) . '" />' . "\n";
     }
   }
   if (!empty($no_inline_preprocess)) {
@@ -2953,7 +2953,7 @@ function drupal_get_js($scope = 'header'
 
       case 'file':
         if (!$item['preprocess'] || !$is_writable || !$preprocess_js) {
-          $no_preprocess .= '<script type="text/javascript"' . ($item['defer'] ? ' defer="defer"' : '') . ' src="' . base_path() . $item['data'] . ($item['cache'] ? $query_string : '?' . REQUEST_TIME) . "\"></script>\n";
+          $no_preprocess .= '<script type="text/javascript"' . ($item['defer'] ? ' defer="defer"' : '') . ' src="' . file_create_url($item['data']) . ($item['cache'] ? $query_string : '?' . REQUEST_TIME) . "\"></script>\n";
         }
         else {
           $files[$item['data']] = $item;
@@ -2973,7 +2973,7 @@ function drupal_get_js($scope = 'header'
     // starting with "ad*".
     $filename = 'js_' . md5(serialize($files) . $query_string) . '.js';
     $preprocess_file = drupal_build_js_cache($files, $filename);
-    $preprocessed .= '<script type="text/javascript" src="' . base_path() . $preprocess_file . '"></script>' . "\n";
+    $preprocessed .= '<script type="text/javascript" src="' . file_create_url($preprocess_file) . '"></script>' . "\n";
   }
 
   // Keep the order of JS files consistent as some are preprocessed and others are not.
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.177
diff -u -F^f -r1.177 file.inc
--- includes/file.inc	20 Jul 2009 19:02:38 -0000	1.177
+++ includes/file.inc	22 Jul 2009 10:22:16 -0000
@@ -79,18 +79,60 @@
 /**
  * Create the download path to a file.
  *
- * @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.
+ * There are two kinds of files:
+ * - "created files", i.e. those in the files directory (which is stored in
+ *   the file_directory_path variable and can be retrieved using
+ *   file_directory_path()). These are files that have either been uploaded by
+ *   users or were generated automatically (for example through CSS
+ *   aggregation).
+ * - "shipped files", i.e. those outside of the files directory, which ship as
+ *   part of Drupal core or contributed modules or themes.
+ * When a hook_file_url_alter() function is defined and overrides the path,
+ * then that rewritten path is used instead of creating a URL for the file at
+ * the given path.
+ *
+ * @param $path
+ *   A string containing the Drupal path (i.e. path relative to the Drupal
+ *   root directory) of the file to generate the 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.
-  $path = file_directory_strip($path);
-  switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
-    case FILE_DOWNLOADS_PUBLIC:
-      return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('\\', '/', $path);
-    case FILE_DOWNLOADS_PRIVATE:
-      return url('system/files/' . $path, array('absolute' => TRUE));
+  // Clean up Windows paths.
+  $old_path = $path = str_replace('\\', '/', $path);
+
+  drupal_alter('file_url', $path);
+
+  // If any module has altered the path, then return the alteration.
+  if ($path != $old_path) {
+    return $path;
+  }
+
+  // Otherwise serve the file from Drupal's web server. This point will only
+  // be reached when either no custom_file_url_rewrite() function has been
+  // defined, or when that function returned FALSE, thereby indicating that it
+  // cannot (or doesn't wish to) rewrite the URL. This is typically because
+  // the file doesn't match some conditions to be served from a CDN or static
+  // file server, or because the file has not yet been synced to the CDN or
+  // static file server.
+
+  // Shipped files.
+  if (strpos($path, file_directory_path() . '/') !== 0) {
+    return base_path() . $path;
+  }
+  // Created files.
+  else {
+    switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
+      case FILE_DOWNLOADS_PUBLIC:
+        return $GLOBALS['base_url'] . '/' . $path;
+      case FILE_DOWNLOADS_PRIVATE:
+        // Strip file_directory_path from $path. Private downloads' URLs are
+        // rewritten to be served relatively to system/files (which is a menu
+        // callback that streams the file) instead of relatively to the file
+        // directory path.
+        $path = file_directory_strip($path);
+        return url('system/files/' . $path, array('absolute' => TRUE));
+    }
   }
 }
 
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.351
diff -u -F^f -r1.351 form.inc
--- includes/form.inc	18 Jul 2009 02:41:05 -0000	1.351
+++ includes/form.inc	22 Jul 2009 10:22:19 -0000
@@ -2447,7 +2447,7 @@ function theme_image_button($element) {
     (!empty($element['#value']) ? ('value="' . check_plain($element['#value']) . '" ') : '') .
     'id="' . $element['#id'] . '" ' .
     drupal_attributes($element['#attributes']) .
-    ' src="' . base_path() . $element['#src'] . '" ' .
+    ' src="' . file_create_url($element['#src']) . '" ' .
     (!empty($element['#title']) ? 'alt="' . check_plain($element['#title']) . '" title="' . check_plain($element['#title']) . '" ' : '' ) .
     "/>\n";
 }
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.499
diff -u -F^f -r1.499 theme.inc
--- includes/theme.inc	15 Jul 2009 17:40:17 -0000	1.499
+++ includes/theme.inc	22 Jul 2009 10:22:21 -0000
@@ -1097,24 +1097,24 @@ function theme_get_setting($setting_name
 
     if ($settings['toggle_logo']) {
       if ($settings['default_logo']) {
-        $settings['logo'] = base_path() . dirname($theme_object->filename) . '/logo.png';
+        $settings['logo'] = file_create_url(dirname($theme_object->filename) . '/logo.png');
       }
       elseif ($settings['logo_path']) {
-        $settings['logo'] = base_path() . $settings['logo_path'];
+        $settings['logo'] = file_create_url($settings['logo_path']);
       }
     }
 
     if ($settings['toggle_favicon']) {
       if ($settings['default_favicon']) {
         if (file_exists($favicon = dirname($theme_object->filename) . '/favicon.ico')) {
-          $settings['favicon'] = base_path() . $favicon;
+          $settings['favicon'] = file_create_url($favicon);
         }
         else {
-          $settings['favicon'] = base_path() . 'misc/favicon.ico';
+          $settings['favicon'] = file_create_url('misc/favicon.ico');
         }
       }
       elseif ($settings['favicon_path']) {
-        $settings['favicon'] = base_path() . $settings['favicon_path'];
+        $settings['favicon'] = file_create_url($settings['favicon_path']);
       }
       else {
         $settings['toggle_favicon'] = FALSE;
@@ -1338,7 +1338,7 @@ function theme_links($links, $attributes
 function theme_image($path, $alt = '', $title = '', $attributes = array(), $getsize = TRUE) {
   if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
     $attributes = drupal_attributes($attributes);
-    $url = (url($path) == $path) ? $path : (base_path() . $path);
+    $url = file_create_url($path);
     return '<img src="' . check_url($url) . '" alt="' . check_plain($alt) . '" title="' . check_plain($title) . '" ' . (isset($image_attributes) ? $image_attributes : '') . $attributes . ' />';
   }
 }
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.36
diff -u -F^f -r1.36 file.test
--- modules/simpletest/tests/file.test	20 Jul 2009 19:02:38 -0000	1.36
+++ modules/simpletest/tests/file.test	22 Jul 2009 10:22:23 -0000
@@ -1913,6 +1913,25 @@ function file_test_file_scan_callback_re
   }
 
   /**
+   * Test the generating of public file URLs.
+   */
+  function testPublicFileURL() {
+    // Set file downloads to public.
+    variable_set('file_downloads', FILE_DOWNLOADS_PUBLIC);
+
+    // Test generating an URL to a created file.
+    $file = $this->createFile();
+    $url = file_create_url($file->filepath);
+    $this->assertEqual($GLOBALS['base_url'] . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a URL for a created file.'));
+
+    // Test generating an URL to a shipped file (i.e. a file that is part of
+    // Drupal core, a module or a theme, for example a JavaScript file).
+    $file = 'misc/jquery.js';
+    $url = file_create_url($file);
+    $this->assertEqual(base_path() . $file, $url, t('Correctly generated a URL for a shipped file.'));
+  }
+
+  /**
    * Test the private file transfer system.
    */
   function testPrivateFileTransfer() {
@@ -1921,7 +1940,7 @@ function file_test_file_scan_callback_re
 
     // Create a file.
     $file = $this->createFile();
-    $url = file_create_url($file->filename);
+    $url = file_create_url($file->filepath);
 
     // Set file_test access header to allow the download.
     file_test_set_return('download', array('x-foo' => 'Bar'));
@@ -1943,6 +1962,45 @@ function file_test_file_scan_callback_re
 }
 
 /**
+ * Tests for file URL rewriting.
+ */
+class FileURLRewritingTest extends FileDownloadTest {
+  public static function getInfo() {
+    return array(
+      'name' => t('File URL rewriting'),
+      'description' => t('Tests for file URL rewriting.'),
+      'group' => t('File'),
+    );
+  }
+
+  function setUp() {
+    DrupalWebTestCase::setUp('file_test', 'file_url_test');
+  }
+
+  /**
+   * Test the generating of rewritten public file URLs.
+   */
+  function testPublicFileURL() {
+    // Set file downloads to public.
+    variable_set('file_downloads', FILE_DOWNLOADS_PUBLIC);
+
+    // Test generating an URL to a created file.
+    $file = $this->createFile();
+    $url = file_create_url($file->filepath);
+    $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $file->filepath, $url, t('Correctly generated a URL for a created file.'));
+
+    // Test generating an URL to a shipped file (i.e. a file that is part of
+    // Drupal core, a module or a theme, for example a JavaScript file).
+    $file = 'misc/jquery.js';
+    $url = file_create_url($file);
+    $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $file, $url, t('Correctly generated a URL for a shipped file.'));
+    $file = 'misc/favicon.ico';
+    $url = file_create_url($file);
+    $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $file, $url, t('Correctly generated a URL for a shipped file.'));
+  }
+}
+
+/**
  * Tests for file_munge_filename() and file_unmunge_filename().
  */
 class FileNameMungingTest extends FileTestCase {
Index: modules/simpletest/tests/file_url_test.info
===================================================================
RCS file: modules/simpletest/tests/file_url_test.info
diff -N modules/simpletest/tests/file_url_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file_url_test.info	22 Jul 2009 10:22:23 -0000
@@ -0,0 +1,8 @@
+; $Id$
+name = "File URL test"
+description = "Support module for file URL rewrite tests."
+package = Testing
+version = VERSION
+core = 7.x
+files[] = file_url_test.module
+hidden = TRUE
Index: modules/simpletest/tests/file_url_test.module
===================================================================
RCS file: modules/simpletest/tests/file_url_test.module
diff -N modules/simpletest/tests/file_url_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/file_url_test.module	22 Jul 2009 10:22:23 -0000
@@ -0,0 +1,37 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Helper module for the file tests.
+ *
+ * This allows testing for file URL rewriting, such as by a CDN.
+ */
+
+
+define('FILE_URL_TEST_CDN_1', 'http://cdn1.example.com');
+define('FILE_URL_TEST_CDN_2', 'http://cdn2.example.com');
+
+
+/**
+ *  Implement hook_file_url_alter().
+ */
+function file_url_test_file_url_alter(&$path) {
+  $cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png');
+
+  // Most CDN's don't support private file transfers without a lot of hassle,
+  // so don't support this in the common case.
+  if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
+    return;
+  }
+
+  // Serve files without extension and files with one of the CDN extensions
+  // from CDN 1, all others from CDN 2.
+  $pathinfo = pathinfo($path);
+  if (!array_key_exists('extension', $pathinfo) || in_array($pathinfo['extension'], $cdn_extensions)) {
+    $path = FILE_URL_TEST_CDN_1 . '/' . $path;
+  }
+  else {
+    $path = FILE_URL_TEST_CDN_2 . '/' . $path;
+  }
+}
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.49
diff -u -F^f -r1.49 system.api.php
--- modules/system/system.api.php	15 Jul 2009 02:08:41 -0000	1.49
+++ modules/system/system.api.php	22 Jul 2009 10:22:25 -0000
@@ -1162,6 +1162,51 @@ function custom_url_rewrite_inbound(&$re
 }
 
 /**
+ * Alter the URL to a file.
+ *
+ * This hook is called from file_create_url(), and  is called fairly frequently
+ * (10+ times per page), depending on how many files there are in a given page.
+ * If CSS and JS aggregation are disabled, this can become very frequently (50+
+ * times per page) so performance is critical.
+ *
+ * This function should alter the path, if it wants to rewrite the file URL.
+ * If it does so, no other hook_file_url_alter() implementation will be
+ * allowed to further alter the path.
+ *
+ * @param $path
+ *   A string containing the Drupal path (i.e. path relative to the Drupal
+ *   root directory) of the file to generate the URL for.
+ */
+function hook_file_url_alter(&$path) {
+  global $user;
+
+  // User 1 will always see the local file in this example.
+  if ($user->uid == 1) {
+    return;
+  }
+
+  $cdn1 = 'http://cdn1.example.com';
+  $cdn2 = 'http://cdn2.example.com';
+  $cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png');
+
+  // Most CDN's don't support private file transfers without a lot of hassle,
+  // so don't support this in the common case.
+  if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
+    return;
+  }
+
+  // Serve files without extension and files with one of the CDN extensions
+  // from CDN 1, all others from CDN 2.
+  $pathinfo = pathinfo($path);
+  if (!array_key_exists('extension', $pathinfo) || in_array($pathinfo['extension'], $cdn_extensions)) {
+    $path = $cdn1 . '/' . $path;
+  }
+  else {
+    $path = $cdn2 . '/' . $path;
+  }
+}
+
+/**
  * Load additional information into file objects.
  *
  * file_load_multiple() calls this hook to allow modules to load
Index: themes/garland/template.php
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/template.php,v
retrieving revision 1.22
diff -u -F^f -r1.22 template.php
--- themes/garland/template.php	2 Jun 2009 03:57:22 -0000	1.22
+++ themes/garland/template.php	22 Jul 2009 10:22:25 -0000
@@ -91,9 +91,9 @@ function garland_node_submitted($node) {
 function garland_get_ie_styles() {
   global $language;
 
-  $ie_styles = '<link type="text/css" rel="stylesheet" media="all" href="' . base_path() . path_to_theme() . '/fix-ie.css" />' . "\n";
+  $ie_styles = '<link type="text/css" rel="stylesheet" media="all" href="' . file_create_url(path_to_theme() . '/fix-ie.css') . '" />' . "\n";
   if ($language->direction == LANGUAGE_RTL) {
-    $ie_styles .= '      <style type="text/css" media="all">@import "' . base_path() . path_to_theme() . '/fix-ie-rtl.css";</style>' . "\n";
+    $ie_styles .= '      <style type="text/css" media="all">@import "' . file_create_url(path_to_theme() . '/fix-ie-rtl.css') . '";</style>' . "\n";
   }
 
   return $ie_styles;
