Skip to content

Instantly share code, notes, and snippets.

@kalamun
Created October 7, 2018 16:08
Show Gist options
  • Save kalamun/fb7b08c222bd6554f813ae938aecb087 to your computer and use it in GitHub Desktop.
Save kalamun/fb7b08c222bd6554f813ae938aecb087 to your computer and use it in GitHub Desktop.
/*
* Load local files and stream it chunked
* Tested also with media and large files
*/
function stream_file( $file, $is_resume = true )
{
//First, see if the file exists
if (!is_file($file))
{
header('HTTP/1.0 404 Not found');
die("File not found");
}
//Gather relevent info about file
$size = filesize($file);
$fileinfo = pathinfo($file);
$chunk_length = $size;
//workaround for IE filename bug with multiple periods / multiple dots in filename
//that adds square brackets to filename - eg. setup.abc.exe becomes setup[1].abc.exe
$filename = (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) ?
preg_replace('/\./', '%2e', $fileinfo['basename'], substr_count($fileinfo['basename'], '.') - 1) :
$fileinfo['basename'];
// get mime type
$finfo = finfo_open( FILEINFO_MIME_TYPE );
$mime_type = finfo_file( $finfo, $file );
finfo_close( $finfo );
//check if http_range is sent by browser (or download manager)
if($is_resume && isset( $_SERVER['HTTP_RANGE'] ) )
{
list( $size_unit, $range_orig ) = explode( '=', $_SERVER['HTTP_RANGE'], 2 );
if( $size_unit == 'bytes' )
{
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list( $range, $extra_ranges ) = explode( ',', $range_orig, 2 );
}
else
{
$range = '';
}
//figure out download piece from range (if set)
list( $seek_start, $seek_end ) = explode( '-', $range, 2 );
} else {
$range = '';
}
//set start and end based on range (if set), else set defaults
//also check for invalid ranges.
$seek_end = ( empty( $seek_end ) ) ? ($size - 1) : min(abs( intval( $seek_end ) ), ( $size - 1 ) );
$seek_start = ( empty( $seek_start ) || $seek_end < abs( intval( $seek_start ) ) ) ? 0 : max( abs( intval( $seek_start ) ), 0 );
//add headers if resumable
if( $is_resume )
{
//Only send partial content header if downloading a piece of the file (IE workaround)
if( !empty( $seek_start ) || $seek_end < ( $size - 1 ) )
header('HTTP/1.1 206 Partial Content' );
else
header('HTTP/1.0 200 OK' );
header( 'Accept-Ranges: bytes' );
header( 'Content-Range: bytes ' . $seek_start . '-' . $seek_end . '/' . $size );
$chunk_length = $seek_end - $seek_start + 1;
}
if( !empty( $_REQUEST['download'] ) )
{
header( "Content-Disposition: attachment; filename=" . urlencode( basename( $file ) ) );
header( "Content-Type: application/download" );
header( "Content-Description: File Transfer" );
header( "Content-Length: " . $chunk_length );
} else {
header("Content-Type: " . $mime_type );
header("Content-Length: ". $chunk_length );
}
//open the file
$fp = fopen( $file, 'rb' );
//seek to start of missing part
fseek( $fp, $seek_start );
//start buffered download
while( !feof( $fp ) )
{
//reset time limit for big files
set_time_limit( 0 );
print( fread( $fp, 1024*8 ) );
flush();
ob_flush();
}
fclose( $fp );
exit;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment