I'm using forced download to download mostly zips and mp3s on site i did (http://pr1pad.kissyour.net) - to track downloads in google analytics, in database and to hide real download path:
It's this:
extending CI model
... - bunch of code
function _fullread ($sd, $len) {
$ret = '';
$read = 0;
while ($read < $len && ($buf = fread($sd, $len - $read))) {
$read += strlen($buf);
$ret .= $buf;
}
return $ret;
}
function download(){
/* DOWNLOAD ITSELF */
ini_set('memory_limit', '160M');
apache_setenv('no-gzip', '1');
ob_end_flush();
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public",FALSE);
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
if (isset($_SERVER['HTTP_USER_AGENT']) &&
(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false))
header('Content-Type: application/force-download'); //IE HEADER
header("Accept-Ranges: bytes");
header("Content-Disposition: attachment; filename=\"" . basename("dir-with- files/".$filename) . "\";");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize("dir-with-files/".$filename));
// Send file for download
if ($stream = fopen("dir-with-files/$filename", 'rb')){
while(!feof($stream) && connection_status() == 0){
//reset time limit for big files
set_time_limit(0);
print($this->_fullread($stream,1024*16));
flush();
}
fclose($stream);
}
}
It's on LAMP with CI 1.7.2 - It's my own method put together from various how-tos all over the internet, because during developement, these problems came up:
- server limit. ini_set
haven't helped, so I used buffered _fullread
instead normal fread
, which was used insted of @readonly
- ob_end_flush(), because site is did in CI1.7.2 and i needed to clean buffer
Now... It doesn't work. It did, then it stopped showing expected size/download time - I tried to clean it up and while I was cleaning up the code, something happened, I don't know what and in any previous version - it haven't worked (no change in settings whatsoever) - edit: don't work = outputs everything into browser window.
So I said, screw it, I'll look here.
So, I basically look for script or function, which i can put to my output model and will do:
Thank you in advance.
Edit: Now I feel more screwed then ever/before, since I tried to force download with .htaccess - while it worked, it had few minor/major (pick yours) problems
Now, although I deleted .htaccess, it still waits until download is complete (just as if it was downloading to cache first) and it just get's connected
and show open/save dialog.
So, I used this code (It's modified version of resumable http download found on internet)
function _output_file($file, $path)
{
$size = filesize($path.$file);
@ob_end_clean(); //turn off output buffering to decrease cpu usage
// required for IE, otherwise Content-Disposition may be ignored
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header("Content-Transfer-Encoding: binary");
header('Accept-Ranges: bytes');
/* The three lines below basically make the
download non-cacheable */
header("Cache-control: no-cache, pre-check=0, post-check=0");
header("Cache-control: private");
header('Pragma: private');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
// multipart-download and download resuming support
if(isset($_SERVER['HTTP_RANGE']))
{
list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2);
list($range) = explode(",",$range,2);
list($range, $range_end) = explode("-", $range);
$range=intval($range);
if(!$range_end) {
$range_end=$size-1;
} else {
$range_end=intval($range_end);
}
$new_length = $range_end-$range+1;
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $range-$range_end/$size");
} else {
$new_length=$size;
header("Content-Length: ".$size);
}
/* output the file itself */
$chunksize = 1*(1024*1024); //you may want to change this
$bytes_send = 0;
if ($file = fopen($path.$file, 'rb'))
{
if(isset($_SERVER['HTTP_RANGE']))
fseek($file, $range);
while
(!feof($file) &&
(!connection_aborted()) &&
($bytes_send<$new_length) )
{
$buffer = fread($file, $chunksize);
print($buffer); //echo($buffer); // is also possible
flush();
$bytes_send += strlen($buffer);
}
fclose($file);
} else die('Error - can not open file.');
die();
}
and then in model:
function download_file($filename){
/*
DOWNLOAD
*/
$path = "datadirwithmyfiles/"; //directory
//track analytics
include('includes/Galvanize.php'); //great plugin
$GA = new Galvanize('UA-XXXXXXX-7');
$GA->trackPageView();
$this->_output_file($filename, $path);
}
It works as expected in all mentiond browser on Win / MAC - so far, no problems with it.
Okay, this is an old question and Adam already accepted his own answer, so presumably he got this working for himself, but he didn't explain why it worked. One thing the I noticed was in the question he used the headers:
header("Pragma: public");
header("Cache-Control: public",FALSE);
Whereas in the solution he used:
header("Cache-control: private");
header('Pragma: private');
He didn't explain why he changed these but I suspect it relates to the use of SSL. I recently solved a similar problem in software that needs to enable download over both HTTP and HTTPS, using the following to add the correct header:
if(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) {
header("Cache-control: private");
header('Pragma: private');
} else {
header('Pragma: public');
}
Hopefully someone will find the information in this answer a useful addition to the above.
There's one thing I find weird: You are calling ob_end_flush()
at the start of the function. This actually cleans the output buffer, but it also outputs everything to the client first (I assume including Content-Headers set by CodeIgniter). Change the call to ob_end_clean()
, it clears the buffer and discards it. This will give you a clean start for generating your own headers.
Another tip:
Instead of reading the file as a stream and passing it on block-wise, you could give this function a try:
// ...
if (file_exists("dir-with-files/$filename")) {
readfile($file);
}
This takes care of nearly everything.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With