Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.187
diff -u -F^f -r1.187 file.inc
--- includes/file.inc	25 Aug 2009 21:53:47 -0000	1.187
+++ includes/file.inc	27 Aug 2009 20:40:06 -0000
@@ -285,13 +285,28 @@ function file_stream_wrapper_get_instanc
 }
 
 /**
- * Creates the web accessible URL to a stream.
+ * Creates the web accessible URL to a stream, which may be an external file
+ * or a local file (i.e. a file in Drupal). A local file can be either a
+ * shipped or a created file.
  *
  * Compatibility: normal paths and stream wrappers.
  * @see http://drupal.org/node/515192
  *
+ * There are two kinds of local 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 $uri
- *   The URI to for which we need an external URL.
+ *   The URI to a file for which we need an external URL, or the path to a
+ *   shipped file.
  * @return
  *   A string containing a URL that may be used to access the file.
  *   If the provided string already contains a preceding 'http', nothing is done
@@ -299,11 +314,29 @@ function file_stream_wrapper_get_instanc
  *   found to generate an external URL, then FALSE will be returned.
  */
 function file_create_url($uri) {
+  $old_uri = $uri;
+
+  drupal_alter('file_url', $uri);
+  
+  // If any module has altered the path, then return the alteration, which
+  // must be a valid URL.
+  if ($uri != $old_uri) {
+    return $uri;
+  }
+
+  // Otherwise serve the file from Drupal's web server. This point will only
+  // be reached when either no hook_file_url_alter() implementations are
+  // defined, or when that function returns 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.
+
   $scheme = file_uri_scheme($uri);
 
   if (!$scheme) {
-    // If this is not a properly formatted stream return the URI with the base
-    // url prepended.
+    // If this is not a properly formatted stream, then it is a shipped file.
+    // Therefor, return the URI with the base URL prepended.
     return $GLOBALS['base_url'] . '/' . $uri;
   }
   elseif ($scheme == 'http' || $scheme == 'https') {
@@ -320,9 +353,6 @@ function file_create_url($uri) {
       return FALSE;
     }
   }
-
-  // @todo Implement CDN integration hook stuff in this function.
-  // @see http://drupal.org/node/499156
 }
 
 /**
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.40
diff -u -F^f -r1.40 file.test
--- modules/simpletest/tests/file.test	19 Aug 2009 08:38:09 -0000	1.40
+++ modules/simpletest/tests/file.test	27 Aug 2009 20:40:10 -0000
@@ -1879,6 +1879,26 @@ function file_test_file_scan_callback_re
   }
 
   /**
+   * 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);
+    $this->assertEqual($GLOBALS['base_url'] . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a URL for a created file.'));
+    $this->drupalHead($url);
+    $this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the 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).
+    $filepath = 'misc/jquery.js';
+    $url = file_create_url($filepath);
+    $this->assertEqual($GLOBALS['base_url'] . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.'));
+    $this->drupalHead($url);
+    $this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the shipped file.'));
+  }
+
+  /**
    * Test the private file transfer system.
    */
   function testPrivateFileTransfer() {
@@ -1908,6 +1928,47 @@ function file_test_file_scan_callback_re
 }
 
 /**
+ * Tests for file URL rewriting.
+ */
+class FileURLRewritingTest extends FileTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => t('File URL rewriting'),
+      'description' => t('Tests for file URL rewriting.'),
+      'group' => t('File'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('file_test', 'file_url_test');
+  }
+
+  /**
+   * Test the generating of rewritten shipped file URLs.
+   */
+  function testShippedFileURL()  {
+    // 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).
+    $filepath = 'misc/jquery.js';
+    $url = file_create_url($filepath);
+    $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.'));
+    $filepath = 'misc/favicon.ico';
+    $url = file_create_url($filepath);
+    $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.'));
+  }
+
+  /**
+   * Test the generating of rewritten public created file URLs.
+   */
+  function testPublicCreatedFileURL() {
+    // Test generating an URL to a created file.
+    $file = $this->createFile();
+    $url = file_create_url($file->uri);
+    $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a URL for a created 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	27 Aug 2009 20:40:10 -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	27 Aug 2009 20:40:10 -0000
@@ -0,0 +1,53 @@
+<?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(&$uri) {
+  $cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png');
+
+  // Most CDNs don't support private file transfers without a lot of hassle,
+  // so don't support this in the common case.
+  $schemes = array('public');
+
+  $scheme = file_uri_scheme($uri);
+
+  // Only serve shipped files and public created files from the CDN.
+  if (!$scheme || in_array($scheme, $schemes)) {
+    // Shipped files.
+    if (!$scheme) {
+      $path = $uri;
+    }
+    // Public created files.
+    else {
+      $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
+      $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
+    }
+
+    // Clean up Windows paths.
+    $path = str_replace('\\', '/', $path);
+
+    // Serve 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)) {
+      $uri = FILE_URL_TEST_CDN_1 . '/' . $path;
+    }
+    else {
+      $uri = 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.65
diff -u -F^f -r1.65 system.api.php
--- modules/system/system.api.php	26 Aug 2009 03:09:12 -0000	1.65
+++ modules/system/system.api.php	27 Aug 2009 20:40:12 -0000
@@ -1434,6 +1434,67 @@ function hook_file_download($filepath) {
 }
 
 /**
+ * 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 URI, 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 $uri
+ *   The URI to a file for which we need an external URL, or the path to a
+ *   shipped file.
+ */
+function hook_file_url_alter(&$uri) {
+  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 CDNs don't support private file transfers without a lot of hassle,
+  // so don't support this in the common case.
+  $schemes = array('public');
+
+  $scheme = file_uri_scheme($uri);
+
+  // Only serve shipped files and public created files from the CDN.
+  if (!$scheme || in_array($scheme, $schemes)) {
+    // Shipped files.
+    if (!$scheme) {
+      $path = $uri;
+    }
+    // Public created files.
+    else {
+      $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
+      $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
+    }
+
+    // Clean up Windows paths.
+    $path = str_replace('\\', '/', $path);
+
+    // Serve 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)) {
+      $uri = $cdn1 . '/' . $path;
+    }
+    else {
+      $uri = $cdn2 . '/' . $path;
+    }
+  }
+}
+                                                                                                      /**
  * Check installation requirements and do status reporting.
  *
  * This hook has two closely related uses, determined by the $phase argument:
