Index: includes/bootstrap.inc
===================================================================
--- includes/bootstrap.inc	(revision 26)
+++ includes/bootstrap.inc	(working copy)
@@ -578,33 +578,16 @@
  * cache of the page, but must revalidate it on every request.  Then,
  * they are given a '304 Not Modified' response as long as they stay
  * logged out and the page has not been modified.
- *
  */
 function drupal_page_cache_header($cache) {
-  // Set default values:
-  $last_modified = gmdate('D, d M Y H:i:s', $cache->created) .' GMT';
-  $etag = '"'. md5($last_modified) .'"';
+  $headers = array();
 
-  // See if the client has provided the required HTTP headers:
-  $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
-  $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;
+  // Set Last-Modified and ETag, or exit with a '304 Not Modified' response.
+  drupal_apply_cache_headers($headers, $cache->created);
 
-  if ($if_modified_since && $if_none_match
-      && $if_none_match == $etag // etag must match
-      && $if_modified_since == $last_modified) {  // if-modified-since must match
-    header('HTTP/1.1 304 Not Modified');
-    // All 304 responses must send an etag if the 200 response for the same object contained an etag
-    header("Etag: $etag");
-    exit();
-  }
-
-  // Send appropriate response:
-  header("Last-Modified: $last_modified");
-  header("ETag: $etag");
-
   // The following headers force validation of cache:
-  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
-  header("Cache-Control: must-revalidate");
+  $headers[] = "Expires: Sun, 19 Nov 1978 05:00:00 GMT";
+  $headers[] = "Cache-Control: must-revalidate";
 
   if (variable_get('page_compression', TRUE)) {
     // Determine if the browser accepts gzipped data.
@@ -613,14 +596,14 @@
       $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
     }
     elseif (function_exists('gzencode')) {
-      header('Content-Encoding: gzip');
+      $headers[] = 'Content-Encoding: gzip';
     }
   }
 
-  // Send the original request's headers. We send them one after
+  // Also send the original request's headers. We send them one after
   // another so PHP's header() function can deal with duplicate
   // headers.
-  $headers = explode("\n", $cache->headers);
+  $headers = array_merge($headers, explode("\n", $cache->headers));
   foreach ($headers as $header) {
     header($header);
   }
@@ -629,6 +612,39 @@
 }
 
 /**
+ * Add the 'Last-Modified' and 'ETag' cache headers to the $headers parameter.
+ * In case the client provides the matching 'If-Modified-Since' and
+ * 'If-None-Match' headers, exit with a '304 Not Modified' response
+ * which causes the client's cached copy to be used.
+ */
+function drupal_apply_cache_headers(&$headers, $last_modified_timestamp) {
+  // Set default values:
+  $last_modified = gmdate('D, d M Y H:i:s', $last_modified_timestamp) .' GMT';
+  $etag = '"'. md5($last_modified) .'"';
+
+  // See if the client has provided the required HTTP headers:
+  $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
+  $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;
+
+  if ($if_modified_since && $if_none_match
+      && $if_none_match == $etag // etag must match
+      && $if_modified_since == $last_modified) {  // if-modified-since must match
+    header('HTTP/1.1 304 Not Modified');
+    // All 304 responses must send an etag if the 200 response for the same object contained an etag
+    header("Etag: $etag");
+    // Callers other than drupal_page_cache_header() need to overwrite
+    // the page's Last-Modified header with the one passed to this function.
+    // For drupal_page_cache_header(), it doesn't make a difference.
+    header("Last-Modified: $last_modified");
+    exit();
+  }
+
+  // Send appropriate response:
+  $headers[] = "Last-Modified: $last_modified";
+  $headers[] = "ETag: $etag";
+}
+
+/**
  * Define the critical hooks that force modules to always be loaded.
  */
 function bootstrap_hooks() {
Index: includes/file.inc
===================================================================
--- includes/file.inc	(revision 26)
+++ includes/file.inc	(working copy)
@@ -833,13 +833,19 @@
   if (isset($_GET['file'])) {
     $filepath =  $_GET['file'];
   }
+  $full_filepath = file_create_path($filepath);
 
-  if (file_exists(file_create_path($filepath))) {
+  if (is_file($full_filepath)) {
     $headers = module_invoke_all('file_download', $filepath);
     if (in_array(-1, $headers)) {
       return drupal_access_denied();
     }
     if (count($headers)) {
+      // Set Last-Modified and ETag, or exit with a '304 Not Modified' response.
+      // For Last-Modified, use the mtime of the file as retrieved by stat().
+      if ($fileinfo = stat($full_filepath)) {
+        drupal_apply_cache_headers($headers, $fileinfo[9]);
+      }
       file_transfer($filepath, $headers);
     }
   }
