Parsing Http_Range Header in PHP

Parsing HTTP_RANGE header in PHP

Rather use regex to test it before sending a 416. Then just parse it by exploding on the comma , and the hyphen -. I also see that you used \d+ in your regex, but those are actually not required. When either of the range indexes is omitted, then it just means "first byte" or "last byte". You should cover that in your regex as well. Also see the Range header in the HTTP spec how you're supposed to handle it.

Kickoff example:

if (isset($_SERVER['HTTP_RANGE'])) {
if (!preg_match('^bytes=\d*-\d*(,\d*-\d*)*$', $_SERVER['HTTP_RANGE'])) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header('Content-Range: bytes */' . filelength); // Required in 416.
exit;
}

$ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));
foreach ($ranges as $range) {
$parts = explode('-', $range);
$start = $parts[0]; // If this is empty, this should be 0.
$end = $parts[1]; // If this is empty or greater than than filelength - 1, this should be filelength - 1.

if ($start > $end) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header('Content-Range: bytes */' . filelength); // Required in 416.
exit;
}

// ...
}
}

Edit: $start must always be less than $end

HTTP Range header

It's a syntactically valid request, but not a satisfiable request. If you look further in that section you see:

If a syntactically valid byte-range-set includes at least one byte- range-spec whose first-byte-pos is less than the current length of the entity-body, or at least one suffix-byte-range-spec with a non- zero suffix-length, then the byte-range-set is satisfiable. Otherwise, the byte-range-set is unsatisfiable. If the byte-range-set is unsatisfiable, the server SHOULD return a response with a status of 416 (Requested range not satisfiable). Otherwise, the server SHOULD return a response with a status of 206 (Partial Content) containing the satisfiable ranges of the entity-body.

So I think in your example, the server should return a 416 since it's not a valid byte range for that file.

Handling byte-range requests with cURL

I fixed this by simply downloading the video from the external source (the whole thing), opening a stream on that data, and then printing out the requested bytes from the byte range request. To do this, I modified the answer to this answer, so it would just open a stream based on the data I downloaded:

function rangeDownload($file)
{
$fp = fopen('php://memory', 'r+');
fwrite($fp, $file);
rewind($fp);

$size = strlen($file);
$length = $size;
$start = 0;
$end = $size - 1;
header("Accept-Ranges: 0-$length");
if (isset($_SERVER['HTTP_RANGE']))
{
$c_start = $start;
$c_end = $end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false)
{
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}

if ($range == '-')
$c_start = $size - substr($range, 1);
else
{

$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
}
$c_end = ($c_end > $end) ? $end : $c_end;
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size)
{

header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1;
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");

$buffer = 1024 * 8;
while (!feof($fp) && ($p = ftell($fp)) <= $end)
{
if ($p + $buffer > $end)
$buffer = $end - $p + 1;
set_time_limit(0);
echo fread($fp, $buffer);
flush();
}

fclose($fp);
}

This code is assuming that the argument passed to rangeDownload is a string of data. This is an example of usage:

// curl initialization here...
$result = curl_exec($ch);
rangeDownload($result);

The rangeDownload will handle echoing the data out, and parsing the HTTP Range. This method is not the best, but I discovered that my external host didn't support byte range requests, so I couldn't have done it a better way (besides maybe caching). I would not use this method unless you cannot control whether or not the place you are downloading data from handles byte range requests.

How to download large file from a link by php?

I found the answer myself, I used a function to get the status of url if it exist and to get the file size:

/* You may need these ini settings too */
set_time_limit(0);
ini_set('memory_limit', '512M');

//THE DOWNLOAD SCRIPT
$filePath = "http://down.egyu.net/Movies/The.Gambler.2014.720p.BluRay.x264.EGFire.CoM.mp4"; // set your download file path here.

download($filePath); // calls download function
function download($filePath)
{
if(!empty($filePath))
{
$fileInfo = pathinfo($filePath);
$fileName = $fileInfo['basename'];
$fileExtnesion = $fileInfo['extension'];
$default_contentType = "application/octet-stream";
$content_types_list = mimeTypes();
// to find and use specific content type, check out this IANA page : http://www.iana.org/assignments/media-types/media-types.xhtml
if (array_key_exists($fileExtnesion, $content_types_list))
{
$contentType = $content_types_list[$fileExtnesion];
}
else
{
$contentType = $default_contentType;
}

$exist = is_url_exist($fileInfo['dirname']."/".$fileInfo['basename']);

if($exist['response'])
{
$size = $exist['size'];
$offset = 0;
$length = $size;
//HEADERS FOR PARTIAL DOWNLOAD FACILITY BEGINS
if(isset($_SERVER['HTTP_RANGE']))
{
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
$offset = intval($matches[1]);
$length = intval($matches[2]) - $offset;
$fhandle = fopen($filePath, 'r');
fseek($fhandle, $offset); // seek to the requested offset, this is 0 if it's not a partial content request
$data = fread($fhandle, $length);
fclose($fhandle);
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $size);
}//HEADERS FOR PARTIAL DOWNLOAD FACILITY BEGINS
//USUAL HEADERS FOR DOWNLOAD
header("Content-Disposition: attachment;filename=".$fileName);
header('Content-Type: '.$contentType);
header("Accept-Ranges: bytes");
header("Pragma: public");
header("Expires: -1");
header("Cache-Control: no-cache");
header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
header("Content-Length: ".$size);
$chunksize = 8 * (1024 * 1024); //8MB (highest possible fread length)
if ($size > $chunksize)
{
$handle = fopen($_FILES["file"]["tmp_name"], 'rb');
$buffer = '';
while (!feof($handle) && (connection_status() === CONNECTION_NORMAL))
{
$buffer = fread($handle, $chunksize);
print $buffer;
ob_flush();
flush();
}
if(connection_status() !== CONNECTION_NORMAL)
{
echo "Connection aborted";
}
fclose($handle);
}
else
{
ob_clean();
flush();
readfile($filePath);
}
}
else
{
echo 'File does not exist!';
}
}
else
{
echo 'There is no file to download!';
}
}

function is_url_exist($url){

$array = array();

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$size = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);

if($code == 200){
$array['response'] = true;
}else{
$array['response'] = false;
}

$array['size'] = $size;

curl_close($ch);

return $array;
}


Related Topics



Leave a reply



Submit