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	28 Aug 2009 09:14:53 -0000
@@ -285,13 +285,23 @@ function file_stream_wrapper_get_instanc
 }
 
 /**
- * Creates the web accessible URL to a stream.
+ * Creates a web-accessible URL for a stream to an external or local 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.
+ *
  * @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 +309,15 @@ function file_stream_wrapper_get_instanc
  *   found to generate an external URL, then FALSE will be returned.
  */
 function file_create_url($uri) {
+  // Allow the URI to be altered, e.g. to serve a file from a CDN or static
+  // file server.
+  drupal_alter('file_url', $uri);
+  
   $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 +334,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	28 Aug 2009 09:14:57 -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,48 @@ 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');
+    variable_set('file_test_hook_file_url_alter', TRUE);
+  }
+
+  /**
+   * 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_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file_test.module,v
retrieving revision 1.13
diff -u -F^f -r1.13 file_test.module
--- modules/simpletest/tests/file_test.module	19 Aug 2009 08:38:09 -0000	1.13
+++ modules/simpletest/tests/file_test.module	28 Aug 2009 09:14:57 -0000
@@ -9,6 +9,11 @@
  * calling file_test_get_calls() or file_test_set_return().
  */
 
+
+define('FILE_URL_TEST_CDN_1', 'http://cdn1.example.com');
+define('FILE_URL_TEST_CDN_2', 'http://cdn2.example.com');
+
+
 /**
  * Implement hook_menu().
  */
@@ -265,6 +270,51 @@ function file_test_file_delete($file) {
 }
 
 /**
+ *  Implement hook_file_url_alter().
+ */
+function file_test_file_url_alter(&$uri) {
+  // Only run this hook when this variable is set. Otherwise, we'd have to add
+  // another hidden test module just for this hook.
+  if (!variable_get('file_test_hook_file_url_alter', FALSE)) {
+    return;
+  }
+
+  $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;
+    }
+  }
+}
+
+/**
  * Helper class for testing the stream wrapper registry.
  *
  * Dummy stream wrapper implementation (dummy://).
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	28 Aug 2009 09:14:59 -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:
