downloading a file

Heh, downloading a file, this would sound like a trivial problem :slight_smile:

But when you want to support threaded downloads with download managers, and resumed downloads, and these downloads need to be authenticated, it’s not quite as simple.

I just dug out a script that I wrote almost 5 years ago, dusted it off and tweaked it a little for PHP 5, and it still works… I remember spending literally weeks addressing this problem - it is far from trivial, and thoroughly testing it with 10 different download managers on different servers and operating systems was a real chore.

I googled a bit, and it seems most frameworks don’t address this problem - and there aren’t any really solid or totally tested or complete implementations of a file downloader for PHP that meets these requirements, that I could find, so I’ve decided to share the source code.




<?php


/*


send_file v2.1


A function to send a file to the client, with support for HTTP resume (download managers)


(C) Copyright 2005-2009, Rasmus Schultz <http://www.mindplay.dk> - all rights reserved.


  This program is free software: you can redistribute it and/or modify

  it under the terms of the GNU Lesser General Public License as published by

  the Free Software Foundation, either version 3 of the License, or

  (at your option) any later version.


  This program is distributed in the hope that it will be useful,

  but WITHOUT ANY WARRANTY; without even the implied warranty of

  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

  GNU Lesser General Public License for more details.


  You should have received a copy of the GNU General Public License

  along with this program.  If not, see <http://www.gnu.org/licenses/>.


*/


define('SEGMENT_SIZE', 1024*32); // buffer size (memory requirement per download / user / thread)


if (!send_file('testfile.txt','testfile-download.txt')) {

	die('error downloading file!');

}


function send_file($path, $name=null) {

	@ob_end_clean();


	$envs = '';

	foreach ($_ENV as $item => $value)

		if (substr($item, 0, 5) == 'HTTP_')

			$envs .= $item.' => '.$value."\n";

	if (function_exists('apache_request_headers')) {

		$headers = apache_request_headers();

		foreach ($headers as $header => $value) {

   		$envs .= "apache: $header = $value\n";

		}

	}

	

	if (is_null($name)) $name = basename($path);

	if (!is_file($path) || !is_readable($path) || connection_status()!=0)

		return false;

	

	$size = filesize($path);


	if(isset($_ENV['HTTP_RANGE'])) {

		list($a, $range) = explode("=", $_ENV['HTTP_RANGE']);

	} else if (function_exists('apache_request_headers')) {

		$headers = apache_request_headers();

		if (isset($headers['Range'])) {

			list($a, $range) = explode("=", $headers['Range']);

		} else {

			$range = false;

		}

	} else {

		$range = false;

	}


	if ($range) {

		header('HTTP/1.1 206 Partial content');

		list($begin, $end) = explode("-", $range);

		if ($begin == '') {

			$begin = $size-$end;

			$end = $size-1;

		} else if ($end == '') {

			$end = $size-1;

		}

		$header = 'Content-Range: bytes '.$begin.'-'.$end.'/'.($size);

		$size = $end-$begin+1;

	} else {

		$header = false;

		$begin = 0;

		$end = $size-1;

	}

	

	if (($begin > $size-1) || ($end > $size-1) || ($begin > $end)) {

		header('HTTP/1.1 416 Requested range not satisfiable');

	} else {

		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");

		if ($header)

			header($header);

		header("Content-Length: ".$size);

		header("Content-Disposition: inline; filename=$name");

		header("Content-Transfer-Encoding: binary\n");

		if ($file = fopen($path, 'rb')) {

			fseek($file, $begin);

			$sent = 0;

			while ($sent < $size) {

				set_time_limit(20);

				$bytes = $end - ftell($file) + 1;

				if ($bytes > SEGMENT_SIZE)

					$bytes = SEGMENT_SIZE;

				echo fread($file, $bytes);

				$sent += $bytes;

				flush();

			}

			fclose($file);

		}

		$status = (connection_status()==0) and !connection_aborted();

		return($status);

	}

}


?>



The function takes two arguments: the path of the file to download, and the filename as you want it to appear to the client.

The rest is totally automated, there’s nothing else to worry about.

Feel free to adopt this for Yii - I think it would be nice for developers not to need to search in vain to address this seemingly trivial problem. I think it’s the kind of thing you want “out of the box” and would expect to “just work”, everywhere, all the time.

I suppose it could be put in the form of a component, but I’m not sure what the point would be? Since it’s literally just one function.

Or maybe it could be part of CController (loading not automatically, but on demand, since it would be rarely needed) which could provide this function as a method.

Or you may not care about issue at all, whatever :wink:

But here it is anyway, in case you care :slight_smile:

This code is now fully integrated with Yii, see below:

http://code.google.com/p/zii/issues/detail?id=24

Hi mindplay ,

          i really appreciate about this download class where i could download it and how to use it in Yii i went to the mentioned url but the repo seems to be discarded <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/sad.gif' class='bbc_emoticon' alt=':(' />, in between i was using  



  public function actionDownload(){

        $file=$_GET['filename'];

$path=realPath(Yii::app()->request->baseURL).DIRECTORY_SEPARATOR.'uploads'.DIRECTORY_SEPARATOR.'credentials'.DIRECTORY_SEPARATOR.$this->getUserType().DIRECTORY_SEPARATOR.$file;

if(file_exists($path)){

return Yii::app()->getRequest()->sendFile("my_doc.pdf",@file_get_contents($path));

}

}



but the download dialoge appear and when clicked on download, it downloads a corrupted pdf file with nothing inside, is this because i am using IDM to download , i am on my local server on my PC

regards,

Omer Aslam