Version 4.0.13.1
This commit is contained in:
1 parent
f9e857d255
commit
c6a86f0d9b
7517 files changed
+597400
-668041
No files matched your search
@@ -0,0 +1,321 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief cURL REST 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 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Http\Request;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* cURL REST Class
|
||||
*/
|
||||
class _Curl
|
||||
{
|
||||
/**
|
||||
* @brief URL
|
||||
*/
|
||||
protected $url = NULL;
|
||||
|
||||
/**
|
||||
* @brief Curl Handle
|
||||
*/
|
||||
protected $curl = NULL;
|
||||
|
||||
/**
|
||||
* @brief Has the Content-Type header been set?
|
||||
* @note Because cURL will automatically set the Content-Type header to multipart/form-data if we send a POST request with an array, we need to change it to a string if we want to send a different Content-Type
|
||||
* @see <a href='http://www.php.net/manual/en/function.curl-setopt.php'>PHP: curl_setopt - Manual</a>
|
||||
*/
|
||||
protected $modifiedContentType = FALSE;
|
||||
|
||||
/**
|
||||
* @brief HTTP Version
|
||||
*/
|
||||
protected $httpVersion = '1.1';
|
||||
|
||||
/**
|
||||
* @brief Timeout
|
||||
*/
|
||||
protected $timeout = 5;
|
||||
|
||||
/**
|
||||
* @brief Follow redirects?
|
||||
*/
|
||||
protected $followRedirects = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Data sent
|
||||
*/
|
||||
protected $dataForLog = NULL;
|
||||
|
||||
/**
|
||||
* Contructor
|
||||
*
|
||||
* @param \IPS\Http\Url $url URL
|
||||
* @param int $timeout Timeout (in seconds)
|
||||
* @param string $httpVersion HTTP Version
|
||||
* @param bool|int $followRedirects Automatically follow redirects? If a number is provided, will follow up to that number of redirects
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $url, $timeout, $httpVersion, $followRedirects )
|
||||
{
|
||||
/* Init */
|
||||
$this->url = $url;
|
||||
$this->curl = curl_init();
|
||||
$this->httpVersion = $httpVersion ?: '1.1';
|
||||
$this->timeout = $timeout;
|
||||
$this->followRedirects = $followRedirects;
|
||||
|
||||
/* Need to adjust if this is FTP */
|
||||
$user = null;
|
||||
$pass = null;
|
||||
|
||||
if( isset( $this->url->data['scheme'] ) AND $this->url->data['scheme'] == 'ftp' )
|
||||
{
|
||||
if( isset( $this->url->data['user'] ) AND $this->url->data['user'] AND isset( $this->url->data['pass'] ) AND $this->url->data['pass'] )
|
||||
{
|
||||
$user = $this->url->data['user'];
|
||||
$pass = $this->url->data['pass'];
|
||||
|
||||
$this->url->data['user'] = null;
|
||||
$this->url->data['pass'] = null;
|
||||
$this->url = $this->url->setFragment( null );
|
||||
}
|
||||
|
||||
/* Set our basic settings */
|
||||
curl_setopt_array( $this->curl, array(
|
||||
CURLOPT_HEADER => TRUE, // Specifies that we want the headers
|
||||
CURLOPT_RETURNTRANSFER => TRUE, // Specifies that we want the response
|
||||
CURLOPT_SSL_VERIFYPEER => FALSE, // Specifies that we don't need to validate the SSL certificate, if applicable (causes issues with, for example, API calls to CPanel in Nexus)
|
||||
CURLOPT_TIMEOUT => $timeout, // The timeout
|
||||
CURLOPT_URL => (string) $this->url, // The URL we're requesting
|
||||
) );
|
||||
|
||||
/* Need to set user and pass if this is FTP */
|
||||
if( $user !== null AND $pass !== null )
|
||||
{
|
||||
curl_setopt( $this->curl, CURLOPT_USERPWD, $user . ':' . $pass );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Work out HTTP version */
|
||||
if( $httpVersion === null )
|
||||
{
|
||||
$version = curl_version();
|
||||
|
||||
/* Before 7.36 there are some issues handling chunked-encoded data */
|
||||
if( version_compare( $version['version'], '7.36', '>=' ) )
|
||||
{
|
||||
$httpVersion = '1.1';
|
||||
}
|
||||
else
|
||||
{
|
||||
$httpVersion = '1.0';
|
||||
}
|
||||
}
|
||||
|
||||
$httpVersion = ( $httpVersion == '1.1' ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0 );
|
||||
|
||||
/* Set our basic settings */
|
||||
curl_setopt_array( $this->curl, array(
|
||||
CURLOPT_HEADER => TRUE, // Specifies that we want the headers
|
||||
CURLOPT_HTTP_VERSION => $httpVersion, // Sets the HTTP version
|
||||
CURLOPT_RETURNTRANSFER => TRUE, // Specifies that we want the response
|
||||
CURLOPT_SSL_VERIFYPEER => FALSE, // Specifies that we don't need to validate the SSL certificate, if applicable (causes issues with, for example, API calls to CPanel in Nexus)
|
||||
CURLOPT_TIMEOUT => $timeout, // The timeout
|
||||
CURLOPT_URL => (string) $this->url, // The URL we're requesting
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
curl_close( $this->curl );
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
*
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return \IPS\Http\Request\Curl
|
||||
*/
|
||||
public function login( $username, $password )
|
||||
{
|
||||
curl_setopt_array( $this->curl, array(
|
||||
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
|
||||
CURLOPT_USERPWD => "{$username}:{$password}"
|
||||
) );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Headers
|
||||
*
|
||||
* @param array $headers Key/Value pair of headers
|
||||
* @return \IPS\Http\Request\Curl
|
||||
*/
|
||||
public function setHeaders( $headers )
|
||||
{
|
||||
$extra = array();
|
||||
|
||||
foreach ( $headers as $k => $v )
|
||||
{
|
||||
switch ( $k )
|
||||
{
|
||||
case 'Cookie':
|
||||
curl_setopt( $this->curl, CURLOPT_COOKIE, $v );
|
||||
break;
|
||||
|
||||
case 'Accept-Encoding':
|
||||
curl_setopt( $this->curl, CURLOPT_ENCODING, $v );
|
||||
break;
|
||||
|
||||
case 'Referer':
|
||||
curl_setopt( $this->curl, CURLOPT_REFERER, $v );
|
||||
break;
|
||||
|
||||
case 'User-Agent':
|
||||
curl_setopt( $this->curl, CURLOPT_USERAGENT, $v );
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( $k === 'Content-Type' )
|
||||
{
|
||||
$this->modifiedContentType = TRUE;
|
||||
}
|
||||
$extra[] = "{$k}: {$v}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !empty( $extra ) )
|
||||
{
|
||||
curl_setopt( $this->curl, CURLOPT_HTTPHEADER, $extra );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP GET
|
||||
*
|
||||
* @return \IPS\Http\Response
|
||||
* @throws \IPS\Http\Request\CurlException
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
/* Specify that this is a GET request */
|
||||
curl_setopt( $this->curl, CURLOPT_HTTPGET, TRUE );
|
||||
$this->dataForLog = NULL;
|
||||
|
||||
/* Execute */
|
||||
$response = $this->_execute();
|
||||
|
||||
/* Either return it or follow it */
|
||||
if ( $this->followRedirects and ( $response->httpResponseCode == 302 OR $response->httpResponseCode == 301 ) )
|
||||
{
|
||||
$newRequest = \IPS\Http\Url::external( $response->httpHeaders['Location'] )->request( $this->timeout, $this->httpVersion, is_int( $this->followRedirects ) ? ( $this->followRedirects - 1 ) : $this->followRedirects );
|
||||
return $newRequest->get();
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP POST
|
||||
*
|
||||
* @param mixed $data Data to post (can be array or string)
|
||||
* @return \IPS\Http\Response
|
||||
* @throws \IPS\Http\Request\CurlException
|
||||
*/
|
||||
public function post( $data=NULL )
|
||||
{
|
||||
/* Specify that this is a POST request */
|
||||
curl_setopt( $this->curl, CURLOPT_POST, TRUE );
|
||||
|
||||
/* Set the data */
|
||||
$this->dataForLog = $data;
|
||||
if ( !$this->modifiedContentType and is_array( $data ) )
|
||||
{
|
||||
$data = http_build_query( $data, '', '&' );
|
||||
}
|
||||
curl_setopt( $this->curl, CURLOPT_POSTFIELDS, $data );
|
||||
|
||||
/* Execute */
|
||||
$response = $this->_execute();
|
||||
|
||||
/* Either return it or follow it */
|
||||
if ( $this->followRedirects and ( $response->httpResponseCode == 302 OR $response->httpResponseCode == 301 ) )
|
||||
{
|
||||
$newRequest = \IPS\Http\Url::external( $response->httpHeaders['Location'] )->request( $this->timeout, $this->httpVersion, is_int( $this->followRedirects ) ? ( $this->followRedirects - 1 ) : $this->followRedirects );
|
||||
return call_user_func_array( array( $newRequest, 'post' ), $data );
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Method: __call
|
||||
* Used for other HTTP methods (like PUT and DELETE)
|
||||
*
|
||||
* @param string $method Method (A HTTP method)
|
||||
* @param array $params Parameters (a single parameter with data to post, which can be an array or a string)
|
||||
* @return \IPS\Http\Response
|
||||
* @throws \IPS\Http\Request\CurlException
|
||||
*/
|
||||
public function __call( $method, $params )
|
||||
{
|
||||
/* Specify the request method */
|
||||
curl_setopt( $this->curl, CURLOPT_CUSTOMREQUEST, mb_strtoupper( $method ) );
|
||||
|
||||
/* If we have any data to send, set it */
|
||||
if ( isset( $params[0] ) )
|
||||
{
|
||||
curl_setopt( $this->curl, CURLOPT_POSTFIELDS, $params[0] );
|
||||
|
||||
$this->dataForLog = $params[0];
|
||||
}
|
||||
|
||||
/* Execute */
|
||||
$response = $this->_execute();
|
||||
|
||||
/* Either return it or follow it */
|
||||
if ( $this->followRedirects and ( $response->httpResponseCode == 302 OR $response->httpResponseCode == 301 ) )
|
||||
{
|
||||
$newRequest = \IPS\Http\Url::external( $response->httpHeaders['Location'] )->request( $this->timeout, $this->httpVersion, is_int( $this->followRedirects ) ? ( $this->followRedirects - 1 ) : $this->followRedirects );
|
||||
return call_user_func_array( array( $newRequest, $method ), $params );
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the request
|
||||
*
|
||||
* @return \IPS\Http\Response
|
||||
* @throws \IPS\Http\Request\CurlException
|
||||
*/
|
||||
protected function _execute()
|
||||
{
|
||||
/* Execute */
|
||||
$output = curl_exec( $this->curl );
|
||||
|
||||
/* Log */
|
||||
try
|
||||
{
|
||||
\IPS\Log::i( LOG_DEBUG )->write( "\n\n------------------------------------\ncURL REQUEST: {$this->url}\n------------------------------------\n\n" . var_export( $this->dataForLog, TRUE ) . "\n\n------------------------------------\nRESPONSE\n------------------------------------\n\n" . $output, 'request' );
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
|
||||
/* Errors? */
|
||||
if ( $output === FALSE )
|
||||
{
|
||||
throw new CurlException( curl_error( $this->curl ), curl_errno( $this->curl ) );
|
||||
}
|
||||
|
||||
/* If this is FTP we need to fudge the headers a little */
|
||||
if( $this->url->data['scheme'] == 'ftp' )
|
||||
{
|
||||
$output = "HTTP/1.1 200 OK\nFTP: True\r\n\r\n" . $output;
|
||||
}
|
||||
|
||||
/* Return it */
|
||||
return new \IPS\Http\Response( $output );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CURL Exception Class
|
||||
*/
|
||||
class CurlException extends \IPS\Http\Request\Exception { }
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Request Exception 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 26 Aug 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Http\Request;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request Exception Class
|
||||
*/
|
||||
abstract class _Exception extends \RuntimeException { }
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Sockets REST 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 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Http\Request;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sockets REST Class
|
||||
*/
|
||||
class _Sockets
|
||||
{
|
||||
/**
|
||||
* @brief URL
|
||||
*/
|
||||
protected $url = NULL;
|
||||
|
||||
/**
|
||||
* @brief HTTP Version
|
||||
*/
|
||||
protected $httpVersion = '1.1';
|
||||
|
||||
/**
|
||||
* @brief Timeout
|
||||
*/
|
||||
protected $timeout = 5;
|
||||
|
||||
/**
|
||||
* @brief Headers
|
||||
*/
|
||||
protected $headers = array();
|
||||
|
||||
/**
|
||||
* @brief Follow redirects?
|
||||
*/
|
||||
protected $followRedirects = TRUE;
|
||||
|
||||
/**
|
||||
* Contructor
|
||||
*
|
||||
* @param \IPS\Http\Url $url URL
|
||||
* @param int $timeout Timeout (in seconds)
|
||||
* @param string $httpVersion HTTP Version
|
||||
* @param bool|int $followRedirects Automatically follow redirects? If a number is provided, will follow up to that number of redirects
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $url, $timeout, $httpVersion, $followRedirects )
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->httpVersion = $httpVersion ?: '1.1';
|
||||
$this->timeout = $timeout;
|
||||
$this->followRedirects = $followRedirects;
|
||||
$this->contextOptions['http']['ignore_errors'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
*
|
||||
* @param string Username
|
||||
* @param string Password
|
||||
* @retrun \IPS\Http\Request\Socket (for daisy chaining)
|
||||
*/
|
||||
public function login( $username, $password )
|
||||
{
|
||||
$this->setHeaders( array( 'Authorization' => 'Basic ' . base64_encode( "{$username}:{$password}" ) ) );
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Headers
|
||||
*
|
||||
* @param array Key/Value pair of headers
|
||||
* @return \IPS\Http\Request\Socket
|
||||
*/
|
||||
public function setHeaders( $headers )
|
||||
{
|
||||
$this->headers = array_merge( $this->headers, $headers );
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Method: __call
|
||||
*
|
||||
* @param string $method
|
||||
* @param array Params
|
||||
* @return \IPS\Http\Response
|
||||
*/
|
||||
public function __call( $method, $params )
|
||||
{
|
||||
/* The data (string or array) will be the first parameter */
|
||||
if ( isset( $params[0] ) && is_array( $params[0] ) )
|
||||
{
|
||||
$this->setHeaders( array( 'Content-Type' => 'application/x-www-form-urlencoded' ) );
|
||||
$data = http_build_query( $params[0], '', '&' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$data = ( isset( $params[0] ) ? $params[0] : NULL );
|
||||
}
|
||||
|
||||
/* Set the method */
|
||||
$this->contextOptions['http']['method'] = mb_strtoupper( $method );
|
||||
|
||||
/* Set the Content-Length header automatically if this is a POST */
|
||||
if( $this->contextOptions['http']['method'] == 'POST' )
|
||||
{
|
||||
$this->setHeaders( array( 'Content-Length' => \strlen( $data ) ) );
|
||||
}
|
||||
|
||||
/* Parse URL */
|
||||
if ( isset( $this->url->data['user'] ) or isset( $this->url->data['pass'] ) )
|
||||
{
|
||||
$this->login( isset( $this->url->data['user'] ) ? $this->url->data['user'] : NULL, isset( $this->url->data['pass'] ) ? $this->url->data['pass'] : NULL );
|
||||
}
|
||||
|
||||
/* Open connection */
|
||||
$resource = @\fsockopen( ( $this->url->data['scheme'] === 'https' ? 'ssl://' : '' ) . $this->url->data['host'], isset( $this->url->data['port'] ) ? $this->url->data['port'] : ( $this->url->data['scheme'] === 'http' ? 80 : 443 ), $errno, $errstr, $this->timeout );
|
||||
if ( $resource === FALSE )
|
||||
{
|
||||
throw new SocketsException( $errstr, $errno );
|
||||
}
|
||||
|
||||
/* Work out location to use. Ideally use it exactly as $this->url has it as some servers (Google is one) will be sensitive about the encoding
|
||||
and we will want to use whatever was passed to \IPS\Url::__construct. If we can't figure it out, construct manually */
|
||||
$pathPos = ( isset( $this->url->data['path'] ) ) ? mb_strpos( $this->url, $this->url->data['path'] ) : FALSE;
|
||||
if ( $pathPos !== FALSE )
|
||||
{
|
||||
$location = mb_substr( $this->url, $pathPos );
|
||||
}
|
||||
else
|
||||
{
|
||||
$location = ( isset( $this->url->data['path'] ) ? $this->url->data['path'] : '' ) . ( !empty( $this->url->queryString ) ? ( '?' . http_build_query( $this->url->queryString ) ) : '' ) . ( isset( $this->url->data['fragment'] ) ? "#{$this->url->data['fragment']}" : '' );
|
||||
}
|
||||
|
||||
/* Send request */
|
||||
$request = mb_strtoupper( $method ) . ' ' . $location . " HTTP/{$this->httpVersion}\r\n";
|
||||
$request .= "Host: {$this->url->data['host']}\r\n";
|
||||
foreach ( $this->headers as $k => $v )
|
||||
{
|
||||
$request .= "{$k}: {$v}\r\n";
|
||||
}
|
||||
$request .= "Connection: Close\r\n";
|
||||
$request .= "\r\n";
|
||||
if ( $data )
|
||||
{
|
||||
$request .= $data;
|
||||
}
|
||||
\fwrite( $resource, $request );
|
||||
|
||||
/* Read response */
|
||||
stream_set_timeout( $resource, $this->timeout );
|
||||
$status = stream_get_meta_data( $resource );
|
||||
$response = '';
|
||||
while( !feof($resource) and !$status['timed_out'] )
|
||||
{
|
||||
$response .= \fgets( $resource, 8192 );
|
||||
$status = stream_get_meta_data( $resource );
|
||||
}
|
||||
|
||||
/* Close connection */
|
||||
\fclose( $resource );
|
||||
|
||||
/* Log */
|
||||
try
|
||||
{
|
||||
\IPS\Log::i( LOG_DEBUG )->write( "\n\n------------------------------------\nSOCKETS REQUEST: {$this->url}\n------------------------------------\n\n{$request}\n\n------------------------------------\nRESPONSE\n------------------------------------\n\n" . $response, 'request' );
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
|
||||
/* Interpret response */
|
||||
$response = new \IPS\Http\Response( $response );
|
||||
|
||||
/* Either return it or follow it */
|
||||
if ( $this->followRedirects and ( $response->httpResponseCode == 302 OR $response->httpResponseCode == 301 ) )
|
||||
{
|
||||
$newRequest = \IPS\Http\Url::external( $response->httpHeaders['Location'] )->request( $this->timeout, $this->httpVersion, is_int( $this->followRedirects ) ? ( $this->followRedirects - 1 ) : $this->followRedirects );
|
||||
return call_user_func_array( array( $newRequest, $method ), $params );
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sockets Exception Class
|
||||
*/
|
||||
class SocketsException extends \IPS\Http\Request\Exception { }
|
||||
Whitespace-only changes.
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief HTTP 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 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
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 Response Class
|
||||
*/
|
||||
class _Response
|
||||
{
|
||||
/**
|
||||
* @brief HTTP Response Version
|
||||
*/
|
||||
public $httpResponseVersion = NULL;
|
||||
|
||||
/**
|
||||
* @brief HTTP Response Code
|
||||
*/
|
||||
public $httpResponseCode = NULL;
|
||||
|
||||
/**
|
||||
* @brief HTTP Response Text
|
||||
*/
|
||||
public $httpResponseText = NULL;
|
||||
|
||||
/**
|
||||
* @brief HTTP Headers
|
||||
*/
|
||||
public $httpHeaders = NULL;
|
||||
|
||||
/**
|
||||
* @brief Response Content
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $content Response Content
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $content )
|
||||
{
|
||||
/* Do we have any content? */
|
||||
if( !$content )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extract the HTTP response information */
|
||||
do
|
||||
{
|
||||
if( !$content )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
$firstLineBreak = mb_strpos( $content, "\n" );
|
||||
if ( preg_match( "/^HTTP\/(.+?) (\d+?) (.+?)$/", mb_substr( $content, 0, $firstLineBreak ), $matches ) )
|
||||
{
|
||||
$this->httpResponseVersion = $matches[1];
|
||||
$this->httpResponseCode = $matches[2];
|
||||
$this->httpResponseText = $matches[3];
|
||||
}
|
||||
|
||||
/* If we have received a 1xx response code, that means it's not the final response */
|
||||
if ( mb_substr( $this->httpResponseCode, 0, 1 ) == 1 )
|
||||
{
|
||||
$content = trim( mb_substr( $content, $firstLineBreak ) );
|
||||
}
|
||||
/* Or a 3xx response with another response */
|
||||
elseif ( mb_substr( $this->httpResponseCode, 0, 1 ) == 3 AND mb_strpos( mb_substr( $content, $firstLineBreak ), "HTTP/" ) !== FALSE )
|
||||
{
|
||||
$_content = trim( mb_substr( $content, $firstLineBreak ) );
|
||||
if ( $_content )
|
||||
{
|
||||
$content = $_content;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while ( TRUE );
|
||||
|
||||
/* Split into headers and content */
|
||||
$split = preg_split( "/\r\n\r\n/", mb_substr( $content, $firstLineBreak ), 2 );
|
||||
if ( isset( $split[1] ) )
|
||||
{
|
||||
$this->content = $split[1];
|
||||
}
|
||||
foreach ( explode( "\n", $split[0] ) as $line )
|
||||
{
|
||||
$line = trim( $line );
|
||||
|
||||
if ( $line !== '' and preg_match( "/^(.+?): (.+?)$/", $line, $matches ) )
|
||||
{
|
||||
$this->httpHeaders[ $matches[1] ] = $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
/* Fix chunked encoding */
|
||||
if( isset( $this->httpHeaders['transfer-encoding'] ) )
|
||||
{
|
||||
$this->httpHeaders['Transfer-Encoding'] = $this->httpHeaders['transfer-encoding'];
|
||||
}
|
||||
|
||||
if( isset( $this->httpHeaders['Transfer-Encoding'] ) and mb_strtolower( trim( $this->httpHeaders['Transfer-Encoding'] ) ) == 'chunked' )
|
||||
{
|
||||
if ( $decoded = $this->decodeChunked( $this->content ) )
|
||||
{
|
||||
$this->content = $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode chunked response body from HTTP 1.1 response
|
||||
*
|
||||
* @see <a href='http://stackoverflow.com/questions/10793017/how-to-easily-decode-http-chunked-encoded-string-when-making-raw-http-request'>Decoding chunked response in PHP</a>
|
||||
* @param string $body Response body
|
||||
* @return string
|
||||
* @note We are working with bytes and intentionally are not using mb* functions
|
||||
*/
|
||||
public function decodeChunked( $body )
|
||||
{
|
||||
$response = '';
|
||||
$initial = $body;
|
||||
|
||||
for( $response = ''; !empty($body); $body = trim($body) )
|
||||
{
|
||||
$_pos = \strpos( $body, "\r\n" );
|
||||
|
||||
/* The last line could be 0000000 which is valid but doesn't have \r\n */
|
||||
if( trim( $body, '0' ) === '' )
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
/* Make sure this is valid hex */
|
||||
if( $_pos === FALSE OR !preg_match( "/^([a-f0-9]+)$/mi", \substr( $body, 0, $_pos ) ) )
|
||||
{
|
||||
return $response ?: $initial;
|
||||
}
|
||||
|
||||
$_len = (int) hexdec( \substr( $body, 0, $_pos ) );
|
||||
|
||||
if( $_len < 1 )
|
||||
{
|
||||
return $initial;
|
||||
}
|
||||
|
||||
$response .= \substr( $body, $_pos + 2, $_len );
|
||||
$body = \substr( $body, $_pos + 2 + $_len );
|
||||
$body = trim($body);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Method: String Value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode JSON
|
||||
*
|
||||
* @param bool $asArray Whether to decode as an array or not
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @li BAD_JSON
|
||||
*/
|
||||
public function decodeJson( $asArray=TRUE )
|
||||
{
|
||||
if ( !( $json = json_decode( $this->content, $asArray ) ) )
|
||||
{
|
||||
throw new \RuntimeException('BAD_JSON');
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode XML
|
||||
*
|
||||
* @return \IPS\Xml\SimpleXML
|
||||
* @throws \RuntimeException
|
||||
* @li BAD_XML
|
||||
*/
|
||||
public function decodeXml()
|
||||
{
|
||||
try
|
||||
{
|
||||
$xml = \IPS\Xml\SimpleXML::loadString( $this->content );
|
||||
}
|
||||
catch ( \InvalidArgumentException $e )
|
||||
{
|
||||
throw new \RuntimeException('BAD_XML');
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Query String
|
||||
*
|
||||
* @param string $expectedKey If provided, the return value will check for an element with this key and if it isn't present, throw an Exception
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @li EXPECTED_KEY_NOT_PRESENT
|
||||
*/
|
||||
public function decodeQueryString( $expectedKey=NULL )
|
||||
{
|
||||
@parse_str( $this->content, $queryString );
|
||||
|
||||
if ( $expectedKey !== NULL and !array_key_exists( $expectedKey, $queryString ) )
|
||||
{
|
||||
throw new \RuntimeException( 'EXPECTED_KEY_NOT_PRESENT' );
|
||||
}
|
||||
|
||||
return $queryString;
|
||||
}
|
||||
}
|
||||
+1196
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief User-Agent Management 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 20 Aug 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* User-Agent Management Class
|
||||
*/
|
||||
class _Useragent
|
||||
{
|
||||
/**
|
||||
* @brief Search engine spider?
|
||||
*/
|
||||
public $spider = FALSE;
|
||||
|
||||
/**
|
||||
* @brief Mobile or tablet device?
|
||||
*/
|
||||
public $mobile = FALSE;
|
||||
|
||||
/**
|
||||
* @brief Browser or user agent key
|
||||
*/
|
||||
public $useragentKey = NULL;
|
||||
|
||||
/**
|
||||
* @brief Browser version
|
||||
*/
|
||||
public $useragentVersion = NULL;
|
||||
|
||||
/**
|
||||
* @brief Full user agent string
|
||||
*/
|
||||
public $useragent = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $userAgent The user agent string
|
||||
* @return void
|
||||
*/
|
||||
protected function __construct( $userAgent )
|
||||
{
|
||||
$this->useragent = $userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $userAgent The user agent to parse (defaults to $_SERVER['HTTP_USER_AGENT'] if none supplied)
|
||||
* @return \IPS\Http\Useragent
|
||||
*/
|
||||
public static function parse( $userAgent=NULL )
|
||||
{
|
||||
$userAgent = $userAgent ?: ( ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) ? $_SERVER['HTTP_USER_AGENT'] : '' );
|
||||
|
||||
$obj = new static( $userAgent );
|
||||
$obj->parseUserAgent();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-Readable Browser Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if ( \IPS\Member::loggedIn()->language()->checkKeyExists( 'useragent-' . $this->useragentKey ) )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->get( 'useragent-' . $this->useragentKey ) . ( $this->useragentVersion ? " {$this->useragentVersion}" : '' );
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->useragentKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the user agent data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parseUserAgent()
|
||||
{
|
||||
/* Is this a spider? */
|
||||
foreach( array_merge( $this->searchEngineUseragents, $this->mobileEngineUseragents, $this->standardEngineUseragents ) as $key => $regex )
|
||||
{
|
||||
if( is_array( $regex ) )
|
||||
{
|
||||
foreach( $regex as $_expression )
|
||||
{
|
||||
if( preg_match( "#" . $_expression . "#im", $this->useragent, $matches ) )
|
||||
{
|
||||
$this->useragentKey = $key;
|
||||
|
||||
if( isset( $matches[1] ) AND !empty( $matches[1] ) )
|
||||
{
|
||||
$this->useragentVersion = $matches[1];
|
||||
}
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( preg_match( "#" . $regex . "#im", $this->useragent, $matches ) )
|
||||
{
|
||||
$this->useragentKey = $key;
|
||||
|
||||
if( isset( $matches[1] ) AND !empty( $matches[1] ) )
|
||||
{
|
||||
$this->useragentVersion = $matches[1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set mobile/spider flag as appropriate */
|
||||
if( $this->useragentKey )
|
||||
{
|
||||
if( array_key_exists( $this->useragentKey, $this->searchEngineUseragents ) )
|
||||
{
|
||||
$this->spider = TRUE;
|
||||
}
|
||||
else if( array_key_exists( $this->useragentKey, $this->mobileEngineUseragents ) )
|
||||
{
|
||||
$this->mobile = TRUE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->useragentKey = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief List of search engine user agent strings with regex to parse out the data.
|
||||
* @note Matches will be checked based on the order of this list - put more specific matches first and more generic matches later
|
||||
* @note If you wish to capture a version, be sure to have just ONE capturing parenthesis group (i.e. $matches[1])
|
||||
* @note You may put multiple regex definitions for a single key into an array
|
||||
* @note If the UA matches an entry in this array, $this->spider will be set to TRUE
|
||||
*/
|
||||
protected $searchEngineUseragents = array(
|
||||
'about' => "Libby[_/ ]([0-9.]{1,10})",
|
||||
'adsense' => array( "Mediapartners-Google/([0-9.]{1,10})", "Mediapartners-Google" ),
|
||||
'ahrefs' => "AhrefsBot",
|
||||
'alexa' => "^ia_archive",
|
||||
'altavista' => "Scooter[ /\-]*[a-z]*([0-9.]{1,10})",
|
||||
'ask' => "Ask[ \-]?Jeeves",
|
||||
'baidu' => "^baiduspider\-",
|
||||
'bing' => array( "bingbot[ /]([0-9.]{1,10})", "msnbot(?:-media)?[ /]([0-9.]{1,10})" ),
|
||||
'brandwatch' => "magpie-crawler",
|
||||
'excite' => "Architext[ \-]?Spider",
|
||||
'google' => array( "Googl(?:e|ebot)(?:-Image|-Video|-News)?/([0-9.]{1,10})", "Googl(?:e|ebot)(?:-Image|-Video|-News)?/?" ),
|
||||
'googlemobile' => array( "Googl(?:e|ebot)(?:-Mobile)?/([0-9.]{1,10})", "Googl(?:e|ebot)(?:-Mobile)?/" ),
|
||||
'facebook' => "facebookexternalhit/([0-9.]{1,10})",
|
||||
'infoseek' => array( "SideWinder[ /]?([0-9a-z.]{1,10})", "Infoseek" ),
|
||||
'inktomi' => "slurp@inktomi\.com",
|
||||
'internetseer' => "^InternetSeer\.com",
|
||||
'look' => "www\.look\.com",
|
||||
'looksmart' => "looksmart-sv-fw",
|
||||
'lycos' => "Lycos_Spider_",
|
||||
'majestic' => "MJ12bot\/v([0-9.]{1,10})",
|
||||
'msproxy' => "MSProxy[ /]([0-9.]{1,10})",
|
||||
'webcrawl' => "webcrawl\.net",
|
||||
'websense' => "(?:Sqworm|websense|Konqueror/3\.(?:0|1)(?:\-rc[1-6])?; i686 Linux; 2002[0-9]{4})",
|
||||
'yahoo' => "Yahoo(?:.*?)(?:Slurp|FeedSeeker)",
|
||||
'yandex' => "Yandex(?:[^\/]+?)\/([0-9.]{1,10})",
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief List of mobile device user agent strings with regex to parse out the data.
|
||||
* @note Matches will be checked based on the order of this list - put more specific matches first and more generic matches later
|
||||
* @note If you wish to capture a version, be sure to have just ONE capturing parenthesis group (i.e. $matches[1])
|
||||
* @note You may put multiple regex definitions for a single key into an array
|
||||
* @note If the UA matches an entry in this array, $this->mobile will be set to TRUE
|
||||
*/
|
||||
protected $mobileEngineUseragents = array(
|
||||
'transformer' => ";\s+?Transformer\s+?",
|
||||
'android' => "\sandroid\s+?([A-Za-z0-9.]{1,10}).+?mobile",
|
||||
'firefoxmobile' => "Mozilla(?:[^\(]+?)\((?:android|tablet)",
|
||||
'androidtablet' => "\sandroid\s+?([A-Za-z0-9.]{1,10})",
|
||||
'blackberry' => "blackberry\s?(\d+?)[\/;]",
|
||||
'iphone' => "iphone;",
|
||||
'ipodtouch' => "ipod;",
|
||||
'iPad' => "iPad;",
|
||||
'sonyericsson' => "^SonyEricsson[A-Za-z0-9]",
|
||||
'nokia' => "Nokia[A-Za-z0-9]",
|
||||
'motorola' => "^mot-[A-Za-z0-9]",
|
||||
'samsung' => "^samsung-[A-Za-z0-9]",
|
||||
'siemens' => "^sie-[A-Za-z0-9]",
|
||||
'htc' => "(?:htc-|htc_)([A-Za-z0-9.]{1,10})",
|
||||
'lg' => "^(?:lg|lge)-[A-Za-z0-9]",
|
||||
'palm' => "(?:palmsource/| pre/[0-9.]{1,10}|palm webos)",
|
||||
'operamini' => "opera (?:mobi|mini)",
|
||||
'windows7' => "Windows Phone OS ([^;]+);",
|
||||
'windows8' => "Windows Phone ([^;]+);",
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief List of other user agent strings (desktop browsers) with regex to parse out the data.
|
||||
* @note Matches will be checked based on the order of this list - put more specific matches first and more generic matches later
|
||||
* @note If you wish to capture a version, be sure to have just ONE capturing parenthesis group (i.e. $matches[1])
|
||||
* @note You may put multiple regex definitions for a single key into an array
|
||||
*/
|
||||
protected $standardEngineUseragents = array(
|
||||
'amaya' => "amaya/([0-9.]{1,10})",
|
||||
'aol' => "aol[ /\-]([0-9.]{1,10})",
|
||||
'camino' => "camino/([0-9.+]{1,10})",
|
||||
'chimera' => "chimera/([0-9.+]{1,10})",
|
||||
'chrome' => "Chrome/([0-9.]{1,10})",
|
||||
'curl' => "curl[ /]([0-9.]{1,10})",
|
||||
'firebird' => "Firebird/([0-9.+]{1,10})",
|
||||
'firefox' => "Firefox/([0-9.+]{1,10})",
|
||||
'lotus' => "Lotus[ \-]?Notes[ /]([0-9.]{1,10})",
|
||||
'konqueror' => "konqueror/([0-9.]{1,10})",
|
||||
'lynx' => "lynx/([0-9a-z.]{1,10})",
|
||||
'maxthon' => " Maxthon[\);]",
|
||||
'omniweb' => "omniweb/[ a-z]?([0-9.]{1,10})$",
|
||||
'opera' => "opera[ /]([0-9.]{1,10})",
|
||||
'safari' => "version/([0-9.]{1,10})\s+?safari/([0-9.]{1,10})",
|
||||
'webtv' => "webtv[ /]([0-9.]{1,10})",
|
||||
'netscape' => "^mozilla/([0-4]\.[0-9.]{1,10})",
|
||||
'mozilla' => "^mozilla/([5-9]\.[0-9a-z.]{1,10})",
|
||||
'xbox' => "\(compatible; MSIE[ /]([0-9.]{1,10})(.*)Xbox",
|
||||
'explorer' => "\(compatible; MSIE[ /]([0-9.]{1,10})",
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief List of Facebook IP addresses
|
||||
* @see <a href='https://developers.facebook.com/docs/ApplicationSecurity/#facebook_scraper'>Facebook application security</a>
|
||||
* @note List pulled via suggested whois command on Sep 26 2013
|
||||
*/
|
||||
protected $facebookIpRange = array(
|
||||
'204.15.20.0/22', '69.63.176.0/20', '66.220.144.0/20', '66.220.144.0/21', '69.63.184.0/21', '69.63.176.0/21', '74.119.76.0/22', '69.171.255.0/24', '173.252.64.0/18',
|
||||
'69.171.224.0/19', '69.171.224.0/20', '103.4.96.0/22', '69.63.176.0/24', '173.252.64.0/19', '173.252.70.0/24', '31.13.64.0/18', '31.13.24.0/21', '66.220.152.0/21',
|
||||
'66.220.159.0/24', '69.171.239.0/24', '69.171.240.0/20', '31.13.64.0/19', '31.13.64.0/24', '31.13.65.0/24', '31.13.67.0/24', '31.13.68.0/24', '31.13.69.0/24',
|
||||
'31.13.70.0/24', '31.13.71.0/24', '31.13.72.0/24', '31.13.73.0/24', '31.13.74.0/24', '31.13.75.0/24', '31.13.76.0/24', '31.13.77.0/24', '31.13.96.0/19', '31.13.66.0/24',
|
||||
'173.252.96.0/19', '69.63.178.0/24', '31.13.78.0/24', '31.13.79.0/24', '31.13.80.0/24', '31.13.82.0/24', '31.13.83.0/24', '31.13.84.0/24', '31.13.85.0/24', '31.13.86.0/24',
|
||||
'31.13.87.0/24', '31.13.88.0/24', '31.13.89.0/24', '31.13.90.0/24', '31.13.91.0/24', '31.13.92.0/24', '31.13.93.0/24', '31.13.94.0/24', '31.13.95.0/24', '69.171.253.0/24',
|
||||
'69.63.186.0/24', '31.13.81.0/24', '69.63.184.0/21', '2620:0:1c00::/40',
|
||||
'2a03:2880::/32', '2401:DB00::/32', '2a03:2880:fffe::/48', '2a03:2880:ffff::/48', '2620:0:1cff::/48', '2a03:2880:f000::/48', '2a03:2880:f001::/48', '2a03:2880:f002::/48',
|
||||
'2a03:2880:f003::/48', '2a03:2880:f004::/48', '2a03:2880:f005::/48', '2a03:2880:f006::/48', '2a03:2880:f007::/48', '2a03:2880:f008::/48', '2a03:2880:f009::/48', '2a03:2880:f00a::/48',
|
||||
'2a03:2880:f00b::/48', '2a03:2880:f00c::/48', '2a03:2880:f00d::/48', '2a03:2880:f00e::/48', '2a03:2880:f00f::/48', '2a03:2880:f010::/48', '2a03:2880:f011::/48', '2a03:2880:f012::/48',
|
||||
'2a03:2880:f013::/48', '2a03:2880:f014::/48', '2a03:2880:f015::/48', '2a03:2880:f016::/48'
|
||||
);
|
||||
|
||||
/**
|
||||
* Verify a supplied IP address is within the Facebook range
|
||||
*
|
||||
* @param string $ip IP address to check
|
||||
* @return bool
|
||||
* @see <a href='http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet'>Stackoverflow: check IPv6 against CIDR</a>
|
||||
* @see <a href='http://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php5'>Stackoverflow: check IPv4 against CIDR</a>
|
||||
*/
|
||||
public function facebookIpVerified( $ip )
|
||||
{
|
||||
/* Is this an IPv6 address? */
|
||||
if( \strpos( $ip, ':' ) !== FALSE )
|
||||
{
|
||||
$ip = $this->_convertCompressedIpv6ToBits( inet_pton( $ip ) );
|
||||
|
||||
foreach( $this->facebookIpRange as $range )
|
||||
{
|
||||
if( \strpos( $range, ':' ) === FALSE )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list( $net, $maskBits ) = explode( '/', $range );
|
||||
|
||||
$net = $this->_convertCompressedIpv6ToBits( inet_pton( $net ) );
|
||||
|
||||
if( $_checkIp == $_checkNet )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( $this->facebookIpRange as $range )
|
||||
{
|
||||
if( \strpos( $range, ':' ) !== FALSE )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list( $net, $maskBits ) = explode( '/', $range );
|
||||
|
||||
if( ( ip2long( $ip ) & ~( ( 1 << ( 32 - $maskBits ) ) - 1 ) ) == ip2long( $net ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an IPv6 address to bits
|
||||
*
|
||||
* @param string $ip Compressed IPv6 address
|
||||
* @return array
|
||||
* @see <a href='http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet'>Stackoverflow: check IPv6 against CIDR</a>
|
||||
*/
|
||||
protected function _convertCompressedIpv6ToBits( $ip )
|
||||
{
|
||||
$unpackedAddress = unpack( 'A16', $ip );
|
||||
$unpackedAddress = str_split( $unpackedAddress[1] );
|
||||
$ipAddress = '';
|
||||
|
||||
foreach( $unpackedAddress as $char )
|
||||
{
|
||||
$ipAddress .= str_pad( decbin( ord( $char ) ), 8, '0', STR_PAD_LEFT );
|
||||
}
|
||||
|
||||
return $ipAddress;
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
Reference in new issue
Block a user