I have a bug on a drupal site where an uploaded image is truncated when viewed in a browser at /system/files/Alphabet.jpg. Some investigation shows that the headers sent by drupal are reporting the wrong filesize (24875 instead of the actual size of 32408). Running "SELECT * FROM files WHERE filename = 'Alphabet.jpg';" in mysql shows the following:

+------+-------+--------------+-----------------------+------------+----------+
| fid  | nid   | filename     | filepath                           | filemime   | filesize |
+------+-------+--------------+-----------------------+------------+----------+
| 7654 | 15750 | alphabet.jpg | ../files/alphabet.jpg | image/jpeg |    24875 | 
| 7837 | 15396 | Alphabet.jpg | ../files/Alphabet.jpg | image/jpeg |    32408 | 
+------+-------+--------------+------------------------------------+------------+----------+

So here's what's happening:

  1. My browser asks for /system/files/Alphabet.jpg
  2. file_download in includes/file.inc calls hook_file_download on all modules
  3. upload_file_download uses the sql query "SELECT f.* FROM {files} f WHERE filepath = '%s'" which returns two results
  4. upload_file_download returns headers with Content-Length set to alphabet.jpg's filesize instead of Alphabet.jpg's filesize
  5. the image is truncated in my browser

One way to fix this would be to do a case-insensitive check in file_copy so that files with the same spelling but different capitalisation can't be created.

A better solution in my view is to make the following change in upload_file_download:

 function upload_file_download($file) {
-  $file = file_create_path($file);
-  $result = db_query("SELECT f.* FROM {files} f WHERE filepath = '%s'", $file);
-  if ($file = db_fetch_object($result)) {
-    if (user_access('view uploaded files')) {
-      $node = node_load($file->nid);
-      if (node_access('view', $node)) {
-        $type = mime_header_encode($file->filemime);
-        return array(
-          'Content-Type: '. $type,
-          'Content-Length: '. $file->filesize,
-        );
+  $path = file_create_path($file);
+  $result = db_query("SELECT f.* FROM {files} f WHERE filepath = '%s'", $path);
+  while ($file = db_fetch_object($result)) {
+    // the sql query above is case insensitive, but we want to be case sensitive:
+    if ($file->filepath == $path) {
+      if (user_access('view uploaded files')) {
+        $node = node_load($file->nid);
+        if (node_access('view', $node)) {
+          $type = mime_header_encode($file->filemime);
+          return array(
+            'Content-Type: '. $type,
+            'Content-Length: '. $file->filesize,
+          );
+        }
+        else {
+          return -1;
+        }
       }
       else {
         return -1;
       }
     }
-    else {
-      return -1;
-    }
   }
 }

All I've really done here is change the 'if' to a 'while', and checked the filepath in php as well as sql, to make sure that the result is the same capitalisation as the requested file.

This bug is probably in drupal 6 as well, judging by the current implementation of upload_file_download.

Comments

wrunt’s picture

This could also be fixed by changing the case sensitivity of sql queries. See the Case Sensitivity in Searches section of the mysql manual.

jpl-2’s picture

The suggested fix using MySQL configuration is not acceptable, as it has unwanted side effects. The originally proposed solution is good.

The problem is serious, as it may not only lead to reporting of wrong file size, but also, as in my case, serving the wrong file (and thus either violating confidentiality or denying access to the legitimate owner).

A simpler solution for MySQL would be to rewrite the query to use a case-sensitive comparison where such comparison is due: WHERE BINARY filepath = '%s'. This is precisely what I did to fix it in my application. However, it is a MySQL kludge, which is not portable to other databases.

jpl-2’s picture

I forgot to mention: this problem still exists in both Drupal 5.22 and Drupal 6.17.

mithy’s picture

Version: 5.3 » 6.x-dev
Status: Active » Closed (fixed)

Indeed this is a serious issue. It has been fixed in version 5.23 and 6.19.