git-svn-id: https://192.168.0.254/svn/Proyectos.Incam_IntranetNueva/trunk@62 77cfc57b-8ef4-1849-9df6-4a38aa5da120
200 lines
6.8 KiB
PHP
200 lines
6.8 KiB
PHP
<?php
|
|
/**
|
|
* @author Rasmus Schultz
|
|
* @link http://mindplay.dk
|
|
* @copyright Copyright © 2010 Rasmus Schultz
|
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
*/
|
|
|
|
/**
|
|
* This class allows multi-threaded file downloads, and regular file downloads.
|
|
*
|
|
* You can use this class when you need to control which users are allowed to
|
|
* download a file from a protected area of the local filesystem.
|
|
*
|
|
* Note that downloading files in this way does result in some memory and processor
|
|
* overhead - you should not use this class sporadically for all downloads in your
|
|
* application, only when a regular download is not possible for some reason (usually
|
|
* because the file in question must not be made available to the general public).
|
|
*
|
|
* Avoid using this class when you need to log access to a public file - you're
|
|
* probably better off using a pre-download logging action, which then redirects to
|
|
* the actual file download.
|
|
*/
|
|
class GDownloadHelper {
|
|
|
|
/**
|
|
* Buffer size (memory requirement per download / user / thread)
|
|
*/
|
|
const BUFFER_SIZE = 32768; # = 1024*32
|
|
|
|
/**
|
|
* Time limit (time allowed to send one buffer)
|
|
*/
|
|
const TIME_LIMIT = 30;
|
|
|
|
/**
|
|
* Sends a file to the client.
|
|
*
|
|
* This is a blocking method, which means that the method-call will not return until
|
|
* the client has completed the download of the file (or segment), or until the client
|
|
* is disconnected and the connection/download is aborted.
|
|
*
|
|
* Check the return value of this method to see if the download was successful - this
|
|
* if useful, for example, if you need to count and limit the number of times a user can
|
|
* download a file; you should increase your counters only if the call returns TRUE.
|
|
*
|
|
* It is important that your action produces <strong>no other output</strong> before
|
|
* or after calling this method, as this will corrupt the downloaded binary file.
|
|
*
|
|
* Output buffers will be cleared and disabled, and any active CWebLogRoute instances
|
|
* (which might otherwise output logging information at the end of the request) will
|
|
* be detected and disabled.
|
|
*
|
|
* If your application might produce any other output after your action completes, you
|
|
* should suppress this by using the exit statement at the end of your action.
|
|
*
|
|
* This method throws a CException if the specified path does not point to a valid file.
|
|
*
|
|
* This method throws a CHttpException (416) if the requested range is invalid.
|
|
*
|
|
* @param string full path to a file on the local filesystem being sent to the client.
|
|
* @param string optional, alternative filename as the client will see it (defaults to the local filename specified in $path)
|
|
* @return boolean true if the download succeeded, false if the connection was aborted prematurely.
|
|
*/
|
|
public static function send($path, $name=null)
|
|
{
|
|
// turn off output buffering
|
|
while (ob_get_level())
|
|
ob_end_clean();
|
|
|
|
// disable any CWebLogRoutes to prevent them from outputting at the end of the request
|
|
foreach (Yii::app()->log->routes as $route)
|
|
if ($route instanceof CWebLogRoute)
|
|
$route->enabled = false;
|
|
|
|
// obtain headers:
|
|
$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";
|
|
}
|
|
}
|
|
|
|
// obtain filename, if needed:
|
|
if (is_null($name))
|
|
$name = basename($path);
|
|
|
|
// verify path and connection status:
|
|
if (!is_file($path) || !is_readable($path) || connection_status()!=0)
|
|
throw new CException('GDownload::send() : unable to access local file "'.$path.'"');
|
|
|
|
// obtain filesize:
|
|
$size = filesize($path);
|
|
|
|
// configure download range for multi-threaded / resumed downloads:
|
|
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;
|
|
}
|
|
|
|
// produce required headers for partial downloads:
|
|
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;
|
|
}
|
|
|
|
// check range:
|
|
if (($begin > $size-1) || ($end > $size-1) || ($begin > $end))
|
|
throw new CHttpException(416,'Requested range not satisfiable');
|
|
|
|
// suppress client-side caching:
|
|
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");
|
|
|
|
// send a generic content-type:
|
|
header("Content-Type: application/octet-stream");
|
|
|
|
// send content-range header, if present:
|
|
if ($header) header($header);
|
|
|
|
// send content-length header:
|
|
header("Content-Length: ".$size);
|
|
|
|
// send content-disposition, with special handling for IE:
|
|
if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE)
|
|
{
|
|
header("Content-Disposition: inline; filename=".str_replace(' ', '%20', $name));
|
|
}
|
|
else
|
|
{
|
|
header("Content-Disposition: inline; filename=\"$name\"");
|
|
}
|
|
|
|
// set encoding:
|
|
header("Content-Transfer-Encoding: binary\n");
|
|
|
|
// stream out the binary data:
|
|
if ($file = fopen($path, 'rb'))
|
|
{
|
|
fseek($file, $begin);
|
|
$sent = 0;
|
|
while ($sent < $size)
|
|
{
|
|
set_time_limit(self::TIME_LIMIT);
|
|
$bytes = $end - ftell($file) + 1;
|
|
if ($bytes > self::BUFFER_SIZE)
|
|
$bytes = self::BUFFER_SIZE;
|
|
echo fread($file, $bytes);
|
|
$sent += $bytes;
|
|
flush();
|
|
if (connection_aborted())
|
|
break;
|
|
}
|
|
fclose($file);
|
|
}
|
|
|
|
// check connection status and return:
|
|
$status = (connection_status()==0) and !connection_aborted();
|
|
return $status;
|
|
}
|
|
|
|
}
|