Downloadable Products Large Download Fails

After getting a few reports from one of my long term customers about their customers having troubles downloading files (different ones) I decided to see if this was a common issue or not. It so happens that a lot of people have had this issue with Joomla 1.5.x and Virtuemart 1.1.x. So that you fully understand the issue I have attached a more detailed description below. This is an issue that can be fixed with 4 lines of code.

Issue:

Customer visits website and orders a downloadable product which has a file size exceeding 8-10MB. The customer goes to download the product and everything appears to work well until they go to open the actual file. This in turn makes the file look corrupt or damaged and in turn you get a message that basically says that. The issue that actually takes place is that if a file is physically 14.593MB it will only download say 14.586MB or similar. While it is theoretically around the same size those few extra bytes of data still count.

Who is in the wrong?

Typically throughout the research I have done on the virtuemart forums and other google search results I found that everyone tended to agree that it was a HOSTING related issue. Rather than actually having a good look at the issue people took it as gospel. I did some further testing (really simple test actually) by downloading directly via URL eg (http://www.clientsite.com.au/products/download.zip) as well as doing a dummy order. When I did this test I found that downloading directly via URL I got the full file and opened it without issue. When I did it via Virtuemart it failed. So the problem is with Virtuemart (VM) 1.1.x and NOT HOSTING. Whoever diagnosed it as hosting needs to really check the issue out in more detail (no hints there VM support team)

Solution:

After playing around with a few files and following the download process I stumbled across a couple of very important files that handle the download segments of VM. More importantly I found the following:

In the file /administrator/components/com_virtuemart/classes/connectionTools.class.php at around LINE 261:

function sendFile($file,$mime, $overrideFileName=''){
        global $vm_mainframe;
        // send headers
        header("Content-Type: $mime");
        
        list($start,$len) = vmConnector::http_rangeRequest(filesize($file));
        
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Accept-Ranges: bytes');

        //application mime type is downloadable
        if(strtolower(substr($mime,0,11)) == 'application'){
            if( $overrideFileName == '') {
                $filename = basename($file);
            } else {
                $filename = $overrideFileName;
            }
            header('Content-Disposition: attachment; filename="'.$filename.'";');
        }
        
        $chunksize = 1*(1024*1024);
        // send file contents
        $fp = @fopen($file,"rb");
        if($fp){
            fseek($fp,$start); //seek to start of range

            $chunk = ($len > $chunksize) ? $chunksize : $len;
            while (!feof($fp) && $chunk > 0) {
                @set_time_limit(0); // large files can take a lot of time
                print fread($fp, $chunk);
                flush();
                $len -= $chunk;
                $chunk = ($len > $chunksize) ? $chunksize : $len;
            }
            fclose($fp);
        }else{
            header("HTTP/1.0 500 Internal Server Error");
            print "Could not read $file - bad permissions?";
            $vm_mainframe->close(true);
        }
    }

 

This needs to be changed to the following:

 

function sendFile($file,$mime, $overrideFileName=''){
        global $vm_mainframe;


        // send headers
        header("Content-Type: $mime");

 

        //Nultz Large File Download Modification
        $filen = str_replace("/home/user/public_html/","http://www.clientsite.com.au/", $file);
        list($start,$len) = vmConnector::http_rangeRequest(filesize($filen));
        header ('location:' . $filen);
        //end of Nultz Large File Download Modification


        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Accept-Ranges: bytes');


        //application mime type is downloadable
        if(strtolower(substr($mime,0,11)) == 'application'){
            if( $overrideFileName == '') {
                $filename = basename($file);
            } else {
                $filename = $overrideFileName;
            }
            header('Content-Disposition: attachment; filename="'.$filename.'";');
        }
        
        $chunksize = 120*(10240*10240);

        // send file contents
        $fp = @fopen($file,"rb");
        if($fp){
            fseek($fp,$start); //seek to start of range

            $chunk = ($len > $chunksize) ? $chunksize : $len;
            while (!feof($fp) && $chunk > 0) {
                @set_time_limit(); // large files can take a lot of time
                print fread($fp, $chunk);
                flush();
                $len -= $chunk;
                $chunk = ($len > $chunksize) ? $chunksize : $len;
            }
            fclose($fp);
        }else{
            header("HTTP/1.0 500 Internal Server Error");
            print "Could not read $file - bad permissions?";
            $vm_mainframe->close(true);
        }
    }

In short the above code has a modification I have made which forces the browser to redirect to download the file rather than getting PHP and Virtuemart to handle it.

From what I can gather this issue has been an issue bothering people for quite sometime. It just sucks that no one bothered to really search for a viable solution. It only took 4 lines of code.