Force download of large files on a shared server - php

I am trying to add a download link to a large video file (approx 300MB) on someone's site but unfortunately they're on shared hosting (i've told them they will have to upgrade if they get many people downloading it). I don't want people to have to 'Save Target As' and I usually use this code to force downloads:
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // some day in the past
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename={$file}");
header("Content-Transfer-Encoding: binary");
readfile($file);
This works fine with smaller files but not with larger ones and even after turning errors on I get no errors and no error log. I'm sure this is to do with the shared memory limit (or possibly a timeout) but does anyone know how I go about forcing downloads of large files on shared servers, ideally without javascript as i'm sure i won't be able to set the memory limits to be high enough?
Thanks very much,
Dave

The usual solution is to do the file output yourself and let the web server's own buffers handle things:
$fh = fopen($file, 'rb') or die("Unable to open $file");
while($data = fread($fh, 10240)) { // 10kbyte chunks.
echo $data;
}
fclose($fh);

Related

File download stops either with php or xsendfile

I have this remote Windows 2012 (see last edit) server with latest xampp installed.
The users can normally download files (small and very big ones) from there but there are some files (pdf and zips containing the same pdfs, but not rar with the same files inside) whose download stops always at the same size.
Basically the download start normally but at a certain size, always the same on the same file, it hang for a while then stop.
This is the current code:
header("X-Sendfile: ".$pathTofile);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$filename\"");
Some things I'm sure of:
File size does not matter. The problem show either with big or small files and I've non problem with other very big files
I'v tried at least 5 different php scripts all with the same result
I've implemented xsendfile. Same result
If I try to view the pdf in the browser, it won't work either
If I download the files directly from the server they work properly in every aspect
I've tried different browser and different ISP. Same result
If I do the same operation on the site, but locally on server, everything works perfectly
EDIT:
I've changed webserver in favor of Uniform Server (see last edit) and was tryng another php script:
function send_file($position, $name) {
ob_end_clean();
$path = $position.$name;
if (!is_file($path) or connection_status()!=0) return(FALSE);
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Content-Type: application/octet-stream");
$filesize=filesize($path);
header("Content-Length: ".$filesize);
header("Content-Disposition: inline; filename=$name");
header("Content-Transfer-Encoding: binary\n");
if ($file = fopen($path, 'rb')) {
while(!feof($file) and (connection_status()==0)) {
print(fread($file, 1024*8));
flush();
}
fclose($file);
}
return((connection_status()==0) and !connection_aborted());
}
Strangley the page ignored the Content-Length header and the download worked!!
Even more strangley, I've tried to made Content-Length working by adding this line:
header("Content-Range: 0-".($filesize-1)."/".$filesize);
or/and disabling gzip compression like this:
apache_setenv('no-gzip', '1');
in both cases the Content-Length worked but the download stopped at the same point!!!
It's driving me crazy!!!
EDIT 23-10-2018: we recently switch on a debian server with fresh apache installation. The problem seems to remain. I've also try the same identical script on a shared webserver from a local provider and surprisingly it work perfectly.
Any help appreciated....

Firefox downloads(tries to open) .zip file as .HTM

I am creating downloadable zip file, it works fine almost everywhere. But in Mozilla Firefox on save of this zip I get strange message that my_zip.zip is HTM file (sorry for the language, but I hope it is pretty understandable):
If I choose save option it will be saved as normal zip (no sign of HTM at all), but in "open as" section there are only programms for opening HTM
So, the question is How to make Firefox detect this zip as zip?
I am currently using this headers (set by PHP):
header('Content-Description: File Transfer');
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=$zipFileName");
header("Content-Transfer-Encoding: binary");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
header("Cache-Control: no-store");
header('Pragma: no-cache');
header("Content-length: " . filesize($zipFileName));
readfile($zipFileName);
Already tried using header("Content-Type: application/zip"); , does not work; plus application/zip is not standart (as I read here in some headers related question).
I am using Mozilla Firefox v40.0.3, the php project is using Laravel 5.1 (I doubt it has anything to do with this)
UPDATE:
While trying different application\[format]s , I added a dump and die command after headers
//bunch of kosher headers here...
readfile($zipFileName);
dd(headers_list());//dumps and dies
And I get a zip type in download window. Then I figured out that after die or exit I will always get right download type of zip; Then I deleted all dump-and-die sections , but download type remains as zip. I have no idea what i have fixed by this manipulations.
I would love to have an explanation of this strange situation
A quick google search suggests the Content-Type seems to be the culprit
header("Content-Type: application/octet-stream");
Try setting it to application/x-zip-compressed ?
ALso the comments in this bug report may be useful: https://bugzilla.mozilla.org/show_bug.cgi?id=540900

Large file download taking more time

I have used following code to download approximate 920MB file,
set_time_limit(0);
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("X-Sendfile: $zipname"); // For Large Files
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=\"".$zipname."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($directory_location . '/' . $zipname));
ob_end_flush();
readfile($directory_location . '/' . $zipname);
Before this code i did some study with the following links Using X-Sendfile with Apache/PHP, Streaming a large file using PHP and Limit download speed using PHP but not much helpful to me because file download still takes more time with just (2MB) file. It's not showing and transfer rate or anything else. I want download start to serve file with around 60Kbps, with all files (Large or small)
UPDATE: One more thing i noticed its not showing any download process just executing and after sometime display the pop-up to choose the location, and after hitting save button its direct save to the computer without any downloading process window :(
Please help me to guide the right way.
Based on above comments there are two solutions:
1) Just download the file directly. You don't appear to be doing any validation, so if not, then just pass the user to the file to download and let apache handle it.
2) If you do need validation / pre-processing, then check mod_xsendfile - adding the header isn't enough, you actually need to add the mod to apache. If you're in Linux then compile from source (https://tn123.org/mod_xsendfile/). If you're not in Linux then mod_xsendfile for Win x64? has a response from the author saying he can provide binaries - but that's in 2010. There's a bit of advice around the web - although it's been a while since I looked at it so can't really help much more.

Forced downloading large file with php

Many users of my site have reported problems downloading a large file (80 MB). I am using a forced download using headers. I can provide additional php settings if necessary. I am using the CakePHP framework, but this code is all regular php. I am using php 5.2 with apache on a dedicated virtual server from media temple, CentOS Linux. Do you see any problems with the following code:
set_time_limit(1500);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . basename($file_path) . "\"");
header("Content-Length: ".$content_length);
header("Content-Transfer-Encoding: binary");
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false);
header('Pragma: public');
header('Expires: 0');
//Change this part
$handle = fopen($file_path, 'rb');
while (!feof($handle))
{
echo fread($handle, 4096);
ob_flush();
flush();
}
fclose($handle);
exit;
Basically, the problem being reported is that the download starts and then stops in the middle. I was thinking it was a problem with the time limit, so I add the set_time_limit code. I was using the php readfile function before, but that also did not work smoothly.
The problem with PHP-initiated http transfers is that they seldomly support partial requests:
GET /yourfile HTTP/1.1
Range: bytes=31489531-79837582
Whenever a browser encounters a transmission problem, it will try to resume the download. Your php script does not accomodate for that (it's not trivial, so nobody does).
So really avoid that. Redirect users to a static file and let your webserver handle it. If you need to handle authorization, use tricks like symlinks or rewriterules that check for session cookies or even a static permission file (./allowed/178.224.2.55-file-1). Any required extra HTTP headers can be injected likewise, or with a .meta file.
I don't see any trouble, but for S&G's try placing the set_time_limit inside the while loop. This ensures they don't hit a hard limit and (as long as the client's taking the information) the time-limit gets extended.

problem downloading file

I write a script to force download mp3 files from a site. The code is working very fine but the problem is that it can't download large files. I tried it with a file of 9.21mb and it downloaded correctly, but whenever i try to use the code to download a file of 25mb, it simply gives me a cannot find server page or The website cannot display the page. So i now know it has problems downloading large files. Below is the code snippet that does the downloading of files.
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-type: application/force-download");
header("Content-Disposition: attachment; filename=\"".$dname.".mp3\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($secretfile));
$downloaded=readfile($secretfile);
The displayed error is: HTTP 500 Internal Server Error
thank u very much for ur time guys.
It could be memory limits, but usually PHP will output an error saying that the memory limit has been reached.
Also, before all of that you should disable output compression if it's enabled:
if(ini_get('zlib.output_compression')) {
ini_set('zlib.output_compression', 'Off');
}
Sometimes IE can screw up if output compression is enabled.
Watch your PHP configuration for memory limits and timeouts
In php.ini :
memory_limit = 32M
max_execution_time = 300
Note that if you want to go really high in execution time you also need to change your web server timeout.
i simply gives me a cannot find server page or The website cannot display the page
Is this the error as displayed by Internet Explorer? Do you get any server-side errors? Did you check your server logs?
Try this:
// empty output buffer
while (ob_get_level()) {
ob_end_clean();
}
if (ini_get('output_buffering')) {
ini_get('output_buffering', false);
}
// function to encode quoted-string tokens
function rfc2822_quoteString($string) {
return '"'.preg_replace('/[^\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7F]/', '\\\$0', $string).'"';
}
// HTTP headers
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.rfc2822_quoteString($dname.'.mp3'));
header('Content-Length: '.filesize($secretfile));
// send file
readfile($secretfile);
exit;

Categories