Index: modules/image/image.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.module,v
retrieving revision 1.47
diff -u -r1.47 image.module
--- modules/image/image.module	22 Aug 2010 22:04:18 -0000	1.47
+++ modules/image/image.module	1 Sep 2010 12:10:00 -0000
@@ -287,6 +287,9 @@
     array_shift($args);
     // Get the style name from the second part.
     $style_name = array_shift($args);
+    // Remove the scheme from the path.
+    array_shift($args);
+
     // Then the remaining parts are the path to the image.
     $original_uri = file_uri_scheme($uri) . '://' . implode('/', $args);
 
Index: modules/image/image.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.test,v
retrieving revision 1.27
diff -u -r1.27 image.test
--- modules/image/image.test	22 Aug 2010 22:04:18 -0000	1.27
+++ modules/image/image.test	1 Sep 2010 12:10:00 -0000
@@ -620,13 +620,22 @@
     );
   }
 
+  function testImageFieldFormattersPublic() {
+    $this->_testImageFieldFormatters('public');
+  }
+
+  function testImageFieldFormattersPrivate() {
+    // Remove access content permission from anon users.
+    user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array('access content' => FALSE));
+    $this->_testImageFieldFormatters('private');
+  }
+
   /**
    * Test image formatters on node display.
    */
-  function testImageFieldFormatters() {
+  function _testImageFieldFormatters($scheme) {
     $field_name = strtolower($this->randomName());
-    $this->createImageField($field_name, 'article');
-
+    $this->createImageField($field_name, 'article', array('uri_scheme' => $scheme));
     // Create a new node with an image attached.
     $test_image = current($this->drupalGetTestFiles('image'));
     $nid = $this->uploadNodeImage($test_image, $field_name, 'article');
@@ -649,6 +658,23 @@
     $default_output = l(theme('image', $image_info), file_create_url($image_uri), array('html' => TRUE));
     $this->drupalGet('node/' . $nid);
     $this->assertRaw($default_output, t('Image linked to file formatter displaying correctly on full node view.'));
+    // Verify that the image can be downloaded.
+    $this->assertEqual(file_get_contents($test_image->uri), $this->drupalGet(file_create_url($image_uri)), t('File was downloaded successful.'));
+    // Only verify HTTP headers when using private scheme and the headers are
+    // sent by Drupal.
+    if ($scheme == 'private') {
+      $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png; name="' . $test_image->filename . '"', t('Content-Type header was sent.'));
+      $this->assertEqual($this->drupalGetHeader('Content-Disposition'), 'inline; filename="' . $test_image->filename . '"', t('Content-Disposition header was sent.'));
+      $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'private', t('Cache-Control header was sent.'));
+
+      // Logout and try to access the file.
+      $this->drupalLogout();
+      $this->drupalGet(file_create_url($image_uri));
+      $this->assertResponse('403', t('Access denied to original image as anonymous user.'));
+
+      // Log in again.
+      $this->drupalLogin($this->admin_user);
+    }
 
     // Test the image linked to content formatter.
     $instance['display']['default']['settings']['image_link'] = 'content';
@@ -669,6 +695,13 @@
     $default_output = theme('image', $image_info);
     $this->drupalGet('node/' . $nid);
     $this->assertRaw($default_output, t('Image style thumbnail formatter displaying correctly on full node view.'));
+
+    if ($scheme == 'private') {
+      // Logout and try to access the file.
+      $this->drupalLogout();
+      $this->drupalGet(image_style_url('thumbnail', $image_uri));
+      $this->assertResponse('403', t('Access denied to image style thumbnail as anonymous user.'));
+    }
   }
 
   /**
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.37
diff -u -r1.37 file.module
--- modules/file/file.module	27 Aug 2010 11:54:32 -0000	1.37
+++ modules/file/file.module	1 Sep 2010 12:09:59 -0000
@@ -141,6 +141,12 @@
   // Find out which (if any) file fields contain this file.
   $references = file_get_file_references($file, NULL, FIELD_LOAD_REVISION, $field_type);
 
+  // If there are no references, stop processing to avoid returning the headers
+  // multiple times.
+  if (empty($references)) {
+    return;
+  }
+
   // Default to allow access.
   $denied = FALSE;
   // Loop through all references of this file. If a reference explicitly allows
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.226
diff -u -r1.226 file.inc
--- includes/file.inc	22 Aug 2010 13:52:58 -0000	1.226
+++ includes/file.inc	1 Sep 2010 12:09:59 -0000
@@ -1800,9 +1800,19 @@
   $uri = $scheme . '://' . $target;
   if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
     // Let other modules provide headers and controls access to the file.
-    $headers = module_invoke_all('file_download', $uri);
-    if (in_array(-1, $headers)) {
-      return drupal_access_denied();
+    // module_invoke_all() uses array_merge_recursive() which merges header
+    // values into a new array. To avoid that and allow modules to override
+    // headers instead, use array_merge() to merge the returned arrays.
+    $headers = array();
+    foreach (module_implements('file_download') as $module) {
+      $function = $module . '_file_download';
+      $result = $function($uri);
+      if ($result == -1) {
+        return drupal_access_denied();
+      }
+      if (isset($result) && is_array($result)) {
+        $headers = array_merge($headers, $result);
+      }
     }
     if (count($headers)) {
       file_transfer($uri, $headers);
