From f21e5c01bc09b6d5706523c8e8d006a9b406ec80 Mon Sep 17 00:00:00 2001
From: Jared Hancock <gravydish@gmail.com>
Date: Tue, 4 Sep 2012 22:56:35 -0500
Subject: [PATCH] Fetch file data in chunks for downloads

MySQL has a limit on the maximum amount that can be transferred in one
statement. It's the max_allowed_packet setting. The value of this setting
will be the approximate upper limit of attachments that can be handled by
the database given the current access model for osTicket.

The issue came up for attachment uploads and was corrected, so that uploads
are chunk inserted into the database. Downloads, however, were forgotten.
Strangely, it took quite a bit of debugging to track down the problem.

This patch corrects attachment downloads by fetching 256kB chunks of the
attachment at a time and sending them directly to the client. This will also
overcome PHP's memory limit which would be the second-level blocker of
attachment sizes. Lastly, the AttachmentFile::getData() method is simulated
using output buffering. This will provide the same access as the previous
getData() method; however, it is still subject ot PHP's memory limits.
---
 include/class.file.php | 29 +++++++++++++++++++++++------
 1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/include/class.file.php b/include/class.file.php
index e4012bb4b..4873ffb45 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -27,7 +27,8 @@ class AttachmentFile {
         if(!$id && !($id=$this->getId()))
             return false;
 
-        $sql='SELECT f.*, count(DISTINCT c.canned_id) as canned, count(DISTINCT t.ticket_id) as tickets '
+        $sql='SELECT id, type, size, name, hash, f.created, '
+            .' count(DISTINCT c.canned_id) as canned, count(DISTINCT t.ticket_id) as tickets '
             .' FROM '.FILE_TABLE.' f '
             .' LEFT JOIN '.CANNED_ATTACHMENT_TABLE.' c ON(c.file_id=f.id) '
             .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' t ON(t.file_id=f.id) '
@@ -90,12 +91,28 @@ class AttachmentFile {
         return $this->ht['hash'];
     }
 
-    function getBinary() {
-        return $this->ht['filedata'];
+    function sendData() {
+        # XXX: For maximum efficiency,
+        #      do "show variables like 'max_allowed_packet'", and use the
+        #      lesser of half of PHP's memory limit and that value as the
+        #      chunk_size
+        $chunk_size = 256 * 1024;
+        for ($start=1; $start<$this->getSize(); $start+=$chunk_size) {
+            list($data) = db_fetch_row(db_query(
+                'SELECT SUBSTRING(filedata,'.$start.','.$chunk_size
+                .') FROM '.FILE_TABLE.' WHERE id='.db_input($this->getId())));
+            echo $data;
+        }
     }
 
     function getData() {
-        return $this->getBinary();
+        # XXX: This is horrible, and is subject to php's memory
+        #      restrictions, etc. Don't use this function!
+        ob_start();
+        $this->sendData();
+        $data = &ob_get_contents();
+        ob_end_clean();
+        return $data;
     }
 
     function delete() {
@@ -110,7 +127,7 @@ class AttachmentFile {
 
         header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream'));
         header('Content-Length: '.$this->getSize());
-        echo $this->getData();
+        $this->sendData();
         exit();
     }
 
@@ -132,7 +149,7 @@ class AttachmentFile {
         
         header('Content-Transfer-Encoding: binary');
         header('Content-Length: '.$this->getSize());
-        echo $this->getBinary();
+        $this->sendData();
         exit();
     }
 
-- 
GitLab