321 lines
8.6 KiB
PHP
321 lines
8.6 KiB
PHP
<?php
|
|
/**
|
|
* @brief HTTP Ranges Request/Response Class
|
|
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
|
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
|
* @license http://www.invisionpower.com/legal/standards/
|
|
* @package IPS Social Suite
|
|
* @since 19 May 2014
|
|
* @version SVN_VERSION_NUMBER
|
|
* @note This class facilitates parsing of HTTP Range requests and sending the appropriate response
|
|
*/
|
|
|
|
namespace IPS\Http;
|
|
|
|
/* To prevent PHP errors (extending class does not exist) revealing path */
|
|
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
|
{
|
|
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* HTTP Ranges Request/Response Class
|
|
*/
|
|
class _Ranges extends Response
|
|
{
|
|
/**
|
|
* @brief File object
|
|
*/
|
|
public $file = NULL;
|
|
|
|
/**
|
|
* @brief Is this a range request?
|
|
*/
|
|
public $ranged = FALSE;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param \IPS\File $file File object we are sending
|
|
* @param int $throttle Throttle speed (kb/sec)
|
|
* @return void
|
|
*/
|
|
public function __construct( $file, $throttle=0 )
|
|
{
|
|
/* Send some headers */
|
|
\IPS\Output::i()->sendHeader( 'Accept-Ranges: bytes' );
|
|
\IPS\Output::i()->sendHeader( "Content-Transfer-Encoding: binary" );
|
|
|
|
/* We will need the file size */
|
|
$filesize = $file->filesize();
|
|
$length = $filesize;
|
|
$range = 0;
|
|
$start_range = 0;
|
|
|
|
/* Was this a ranged request? */
|
|
if( isset( $_SERVER['HTTP_RANGE'] ) )
|
|
{
|
|
/* Get the range */
|
|
list( $a, $range ) = explode( "=", $_SERVER['HTTP_RANGE'] );
|
|
|
|
/* Check for multiple ranges */
|
|
$ranges = explode( ",", $range );
|
|
|
|
/* Did we only find one range? */
|
|
if( count($ranges) == 1 )
|
|
{
|
|
/* Get the actual range start and end */
|
|
list( $start_range, $end_range ) = explode( "-", $range );
|
|
|
|
/* If there is no start, we want the last x bytes */
|
|
if( \strlen( $start_range ) == 0 )
|
|
{
|
|
$size = $filesize - 1;
|
|
$length = $end_range + 1;
|
|
}
|
|
/* Or did we want the first x bytes? */
|
|
else if( \strlen( $end_range ) == 0 )
|
|
{
|
|
$size = $filesize - 1;
|
|
$length = $size - $start_range + 1;
|
|
}
|
|
/* Or a specific range with start and end specified */
|
|
else
|
|
{
|
|
$size = $end_range;
|
|
$length = $end_range - $start_range + 1;
|
|
}
|
|
|
|
/* If the range is invalid we need to send a 416 header and let the client know the valid range */
|
|
if( $start_range > $size OR $end_range > $size )
|
|
{
|
|
\IPS\Output::i()->sendStatusCodeHeader( 416 );
|
|
\IPS\Output::i()->sendHeader( "Content-Range: */{$filesize}" );
|
|
return;
|
|
}
|
|
/* Otherwise send the 206 header */
|
|
else
|
|
{
|
|
\IPS\Output::i()->sendStatusCodeHeader( 206 );
|
|
\IPS\Output::i()->sendHeader( "Content-Range: bytes {$start_range}-{$size}/{$filesize}" );
|
|
}
|
|
|
|
/* A couple more headers for good measure */
|
|
if( !ini_get('zlib.output_compression') OR ini_get('zlib.output_compression') == 'off' )
|
|
{
|
|
\IPS\Output::i()->sendHeader( "Content-Length: " . $filesize );
|
|
}
|
|
}
|
|
/* Or did they get froggy and request multiple ranges? */
|
|
else
|
|
{
|
|
/* Start parsing the ranges */
|
|
$the_responses = array();
|
|
|
|
foreach( $ranges as $arange )
|
|
{
|
|
/* Get the actual range start and end */
|
|
list( $start_range, $end_range ) = explode( "-", $arange );
|
|
|
|
/* If there is no start, we want the last x bytes */
|
|
if( \strlen( $start_range ) == 0 )
|
|
{
|
|
$size = $filesize - 1;
|
|
$length = $end_range + 1;
|
|
}
|
|
/* Or did we want the first x bytes? */
|
|
else if( \strlen( $end_range ) == 0 )
|
|
{
|
|
$size = $filesize - 1;
|
|
$length = $size - $start_range + 1;
|
|
}
|
|
/* Or a specific range with start and end specified */
|
|
else
|
|
{
|
|
$size = $end_range;
|
|
$length = $end_range - $start_range + 1;
|
|
}
|
|
|
|
/* If the range is invalid we need to send a 416 header and let the client know the valid range */
|
|
if( $start_range > $size OR $end_range > $size )
|
|
{
|
|
\IPS\Output::i()->sendStatusCodeHeader( 416 );
|
|
\IPS\Output::i()->sendHeader( "Content-Range: */{$filesize}" );
|
|
return;
|
|
}
|
|
/* Otherwise store this range */
|
|
else
|
|
{
|
|
$the_responses[] = array( $start_range, $size, $length );
|
|
}
|
|
}
|
|
|
|
/* Did we still only have one range? */
|
|
if( count($the_responses) == 1 )
|
|
{
|
|
$length = $the_responses[0][2];
|
|
$start_range = $the_responses[0][0];
|
|
$size = $the_responses[0][1];
|
|
|
|
$the_responses = array();
|
|
|
|
\IPS\Output::i()->sendStatusCodeHeader( 206 );
|
|
\IPS\Output::i()->sendHeader( "Content-Range: bytes {$start_range}-{$size}/{$filesize}" );
|
|
|
|
/* A couple more headers for good measure */
|
|
if( !ini_get('zlib.output_compression') OR ini_get('zlib.output_compression') == 'off' )
|
|
{
|
|
\IPS\Output::i()->sendHeader( "Content-Length: " . $filesize );
|
|
}
|
|
}
|
|
/* Now we're working with something exciting */
|
|
else if( count($the_responses) > 1 )
|
|
{
|
|
$content_length = 0;
|
|
|
|
foreach( $the_responses as $part )
|
|
{
|
|
$content_length += \strlen( "\r\n--IPDOWNLOADSBOUNDARYMARKER\r\n" );
|
|
$content_length += \strlen( "Content-Type: " . \IPS\File::getMimeType( $file->originalFilename ) . "\r\n" );
|
|
$content_length += \strlen( "Content-Range: bytes {$part[0]}-{$part[1]}/{$filesize}\r\n\r\n" );
|
|
$content_length += $part[2];
|
|
}
|
|
|
|
$content_length += \strlen( "\r\n--IPDOWNLOADSBOUNDARYMARKER--\r\n" );
|
|
|
|
\IPS\Output::i()->sendStatusCodeHeader( 206 );
|
|
\IPS\Output::i()->sendHeader( "Content-Type: multipart/x-byteranges; boundary=IPDOWNLOADSBOUNDARYMARKER" );
|
|
|
|
if( !ini_get('zlib.output_compression') OR ini_get('zlib.output_compression') == 'off' )
|
|
{
|
|
\IPS\Output::i()->sendHeader( "Content-Length: " . $content_length );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* No range */
|
|
else
|
|
{
|
|
$size = $filesize - 1;
|
|
|
|
\IPS\Output::i()->sendStatusCodeHeader( 200 );
|
|
|
|
if( !ini_get('zlib.output_compression') OR ini_get('zlib.output_compression') == 'off' )
|
|
{
|
|
\IPS\Output::i()->sendHeader( "Content-Length: " . $filesize );
|
|
}
|
|
|
|
\IPS\Output::i()->sendHeader( "Content-Range: bytes 0-{$size}/{$filesize}" );
|
|
}
|
|
|
|
/* Not sending multiple ranges */
|
|
if( !isset( $the_responses ) OR !count( $the_responses ) )
|
|
{
|
|
\IPS\Output::i()->sendHeader( 'Content-Type: ' . \IPS\File::getMimeType( $file->originalFilename ) );
|
|
\IPS\Output::i()->sendHeader( 'Content-Disposition: ' . \IPS\Output::getContentDisposition( 'attachment', $file->originalFilename ) );
|
|
}
|
|
|
|
/* Turn off output buffering if it is on */
|
|
while( ob_get_level() > 0 )
|
|
{
|
|
ob_end_clean();
|
|
}
|
|
|
|
/* Throttling? */
|
|
$throttle = ( $throttle > 0 ) ? ( $throttle * 1024 ) : NULL;
|
|
|
|
/* Do we have multiple ranges? */
|
|
if( isset( $the_responses ) AND count($the_responses) )
|
|
{
|
|
/* Loop over each range */
|
|
foreach( $the_responses as $part )
|
|
{
|
|
$length = $part[2];
|
|
|
|
echo "\r\n--IPDOWNLOADSBOUNDARYMARKER\r\n";
|
|
echo "Content-Type: " . \IPS\File::getMimeType( $file->originalFilename ) . "\r\n";
|
|
echo "Content-Range: bytes {$part[0]}-{$part[1]}/{$filesize}\r\n\r\n";
|
|
|
|
$file->printFile( $part[0], $part[2], $throttle );
|
|
}
|
|
|
|
echo "\r\n--IPDOWNLOADSBOUNDARYMARKER--\r\n";
|
|
}
|
|
/* Just a single range? */
|
|
else
|
|
{
|
|
$file->printFile( $start_range, $length, $throttle );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if this is a range request for the start of the file
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function isStartOfFile()
|
|
{
|
|
/* Was this a ranged request? */
|
|
if( isset( $_SERVER['HTTP_RANGE'] ) )
|
|
{
|
|
/* Get the range */
|
|
list( $a, $range ) = explode( "=", $_SERVER['HTTP_RANGE'] );
|
|
|
|
/* Check for multiple ranges */
|
|
$ranges = explode( ",", $range );
|
|
|
|
/* Did we only find one range? */
|
|
if( count($ranges) == 1 )
|
|
{
|
|
/* Get the actual range start and end */
|
|
list( $start_range, $end_range ) = explode( "-", $range );
|
|
|
|
if( \strlen( $start_range ) == 0 )
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if( \strlen( $end_range ) == 0 )
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return ( $start_range == 0 ) ? TRUE : FALSE;
|
|
}
|
|
}
|
|
/* Or did they get froggy and request multiple ranges? */
|
|
else
|
|
{
|
|
foreach( $ranges as $arange )
|
|
{
|
|
/* Get the actual range start and end */
|
|
list( $start_range, $end_range ) = explode( "-", $arange );
|
|
|
|
if( \strlen( $start_range ) == 0 )
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if( \strlen( $end_range ) == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if( $start_range == 0 )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
/* No range */
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
} |