Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+22 -2
View File
@@ -2,9 +2,9 @@
/**
* @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.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 19 May 2014
* @version SVN_VERSION_NUMBER
* @note This class facilitates parsing of HTTP Range requests and sending the appropriate response
@@ -53,9 +53,13 @@ class _Ranges extends Response
$range = 0;
$start_range = 0;
\IPS\Log::debug( "Range response initiated", 'ranges' );
/* Was this a ranged request? */
if( isset( $_SERVER['HTTP_RANGE'] ) )
{
\IPS\Log::debug( "This is a range request: " . $_SERVER['HTTP_RANGE'], 'ranges' );
/* Get the range */
list( $a, $range ) = explode( "=", $_SERVER['HTTP_RANGE'] );
@@ -65,6 +69,8 @@ class _Ranges extends Response
/* Did we only find one range? */
if( count($ranges) == 1 )
{
\IPS\Log::debug( "Only one range detected", 'ranges' );
/* Get the actual range start and end */
list( $start_range, $end_range ) = explode( "-", $range );
@@ -90,6 +96,8 @@ class _Ranges extends Response
/* 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\Log::debug( "Invalid range, responding with 416 header. Requested {$start_range} - {$end_range} out of {$size}", 'ranges' );
\IPS\Output::i()->sendStatusCodeHeader( 416 );
\IPS\Output::i()->sendHeader( "Content-Range: */{$filesize}" );
return;
@@ -97,6 +105,8 @@ class _Ranges extends Response
/* Otherwise send the 206 header */
else
{
\IPS\Log::debug( "Sending requested range with 206 header: {$start_range} - {$size} out of {$filesize}", 'ranges' );
\IPS\Output::i()->sendStatusCodeHeader( 206 );
\IPS\Output::i()->sendHeader( "Content-Range: bytes {$start_range}-{$size}/{$filesize}" );
}
@@ -115,6 +125,8 @@ class _Ranges extends Response
foreach( $ranges as $arange )
{
\IPS\Log::debug( "Multiple ranges: {$start_range} - {$end_range}", 'ranges' );
/* Get the actual range start and end */
list( $start_range, $end_range ) = explode( "-", $arange );
@@ -140,6 +152,8 @@ class _Ranges extends Response
/* 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\Log::debug( "This range out of multiple ranges was invalid", 'ranges' );
\IPS\Output::i()->sendStatusCodeHeader( 416 );
\IPS\Output::i()->sendHeader( "Content-Range: */{$filesize}" );
return;
@@ -160,6 +174,8 @@ class _Ranges extends Response
$the_responses = array();
\IPS\Log::debug( "One range out of multiple ranges to respond: {$start_range} - {$size} out of {$filesize}", 'ranges' );
\IPS\Output::i()->sendStatusCodeHeader( 206 );
\IPS\Output::i()->sendHeader( "Content-Range: bytes {$start_range}-{$size}/{$filesize}" );
@@ -174,6 +190,8 @@ class _Ranges extends Response
{
$content_length = 0;
\IPS\Log::debug( "Multiple range responses, using boundary", 'ranges' );
foreach( $the_responses as $part )
{
$content_length += \strlen( "\r\n--IPDOWNLOADSBOUNDARYMARKER\r\n" );
@@ -197,6 +215,8 @@ class _Ranges extends Response
/* No range */
else
{
\IPS\Log::debug( "No range requested, sending entire file", 'ranges' );
$size = $filesize - 1;
\IPS\Output::i()->sendStatusCodeHeader( 200 );
+136 -73
View File
@@ -2,9 +2,9 @@
/**
* @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.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 18 Mar 2013
* @version SVN_VERSION_NUMBER
*/
@@ -128,7 +128,7 @@ class _Curl
}
$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
@@ -215,62 +215,7 @@ class _Curl
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;
}
/**
* Toggle SSL checks
@@ -288,6 +233,89 @@ class _Curl
return $this;
}
/**
* Force TLS
*
* @return \IPS\Http\Request\Socket
*/
public function forceTls()
{
$curlVersionData = curl_version();
if ( preg_match( '/^OpenSSL\/(\d+\.\d+\.\d+)/', $curlVersionData['ssl_version'], $openSSLVersionData ) )
{
if ( version_compare( $openSSLVersionData[1], '1.0.1', '>=' ) )
{
if ( !defined('CURL_SSLVERSION_TLSv1_2') ) // constant not defined in PHP < 5.5
{
define( 'CURL_SSLVERSION_TLSv1_2', 6 );
}
curl_setopt( $this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2 );
}
else
{
if ( !defined('CURL_SSLVERSION_TLSv1') ) // constant not defined in PHP < 5.5
{
define( 'CURL_SSLVERSION_TLSv1', 1 );
}
curl_setopt( $this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1 );
}
}
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;
return $this->_executeAndFollowRedirects( 'GET', NULL );
}
/**
* 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 */
curl_setopt( $this->curl, CURLOPT_POSTFIELDS, $this->_dataToSend( $data ) );
/* Execute */
return $this->_executeAndFollowRedirects( 'POST', $data );
}
/**
* HTTP HEAD
*
* @return \IPS\Http\Response
* @throws \IPS\Http\Request\CurlException
*/
public function head()
{
/* Specify the request method */
curl_setopt( $this->curl, CURLOPT_CUSTOMREQUEST, 'HEAD' );
/* For HEAD requests, do not try to fetch the body or curl times out */
curl_setopt( $this->curl, CURLOPT_NOBODY, true );
/* Execute */
return $this->_executeAndFollowRedirects( 'HEAD', NULL );
}
/**
* Magic Method: __call
* Used for other HTTP methods (like PUT and DELETE)
@@ -301,25 +329,31 @@ class _Curl
{
/* 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];
curl_setopt( $this->curl, CURLOPT_POSTFIELDS, $this->_dataToSend( $params[0] ) );
}
/* Execute */
$response = $this->_execute();
/* Either return it or follow it */
if ( $this->followRedirects and ( $response->httpResponseCode == 302 OR $response->httpResponseCode == 301 ) )
return $this->_executeAndFollowRedirects( mb_strtoupper( $method ), $params );
}
/**
* Data to send
*
* @param mixed $data Data to post (can be array or string)
* @return mixed
*/
protected function _dataToSend( $data=NULL )
{
$this->dataForLog = $data;
if ( !$this->modifiedContentType and is_array( $data ) )
{
$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 );
$data = http_build_query( $data, '', '&' );
}
return $response;
return $data;
}
/**
@@ -333,12 +367,11 @@ class _Curl
/* Execute */
$output = curl_exec( $this->curl );
/* Log */
try
/* Log - but because the output can be large, only do this if we explicitly have debug logging enabled */
if ( defined('\IPS\DEBUG_LOG') and \IPS\DEBUG_LOG )
{
\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' );
\IPS\Log::debug( "\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 )
@@ -355,6 +388,36 @@ class _Curl
/* Return it */
return new \IPS\Http\Response( $output );
}
/**
* Execute the request and follow redirects id necessary
*
* @param string $method Request method to use
* @param NULL|array $params Parameters to send with request
* @return \IPS\Http\Response
* @throws \IPS\Http\Request\CurlException
*/
protected function _executeAndFollowRedirects( $method, $params=NULL )
{
/* Execute */
$response = $this->_execute();
/* Either return it or follow it */
if ( $this->followRedirects and in_array( $response->httpResponseCode, array( 301, 302, 303, 307, 308 ) ) )
{
/* Fix missing hostname in location */
$location = $response->httpHeaders['Location'];
if( parse_url( $location, PHP_URL_HOST ) === NULL )
{
$location = $this->url->data['scheme'] . '://' . $this->url->data['host'] . $location;
}
$newRequest = \IPS\Http\Url::external( $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;
}
}
/**
+3 -3
View File
@@ -2,9 +2,9 @@
/**
* @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.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 26 Aug 2013
* @version SVN_VERSION_NUMBER
*/
@@ -21,4 +21,4 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
/**
* Request Exception Class
*/
abstract class _Exception extends \RuntimeException { }
class _Exception extends \RuntimeException { }
+121 -46
View File
@@ -2,9 +2,9 @@
/**
* @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.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 18 Mar 2013
* @version SVN_VERSION_NUMBER
*/
@@ -22,17 +22,22 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
* Sockets REST Class
*/
class _Sockets
{
{
/**
* @brief URL
*/
protected $url = NULL;
/**
* @brief Stream context
*/
protected $context;
/**
* @brief HTTP Version
*/
protected $httpVersion = '1.1';
/**
* @brief Timeout
*/
@@ -42,7 +47,7 @@ class _Sockets
* @brief Headers
*/
protected $headers = array();
/**
* @brief Follow redirects?
*/
@@ -60,18 +65,31 @@ class _Sockets
public function __construct( $url, $timeout, $httpVersion, $followRedirects )
{
$this->url = $url;
$this->context = stream_context_create();
$this->httpVersion = $httpVersion ?: '1.1';
$this->timeout = $timeout;
$this->followRedirects = $followRedirects;
$this->contextOptions['http']['ignore_errors'] = TRUE;
/* Set our basic settings */
stream_context_set_option( $this->context, array(
'http' => array(
'protocol_version' => $httpVersion,
'follow_location' => $followRedirects,
'timeout' => $timeout,
'ignore_errors' => TRUE,
),
'ssl' => array(
'verify_peer' => FALSE,
)
) );
}
/**
* Login
*
* @param string Username
* @param string Password
* @retrun \IPS\Http\Request\Socket (for daisy chaining)
* @return \IPS\Http\Request\Socket (for daisy chaining)
*/
public function login( $username, $password )
{
@@ -99,19 +117,56 @@ class _Sockets
*/
public function sslCheck( $value=TRUE )
{
/* This is here for compatibility with curl */
stream_context_set_option( $this->context, array(
'ssl' => array(
'verify_peer_name' => ( $value ) ? 2 : FALSE,
'verify_peer' => (boolean) $value,
)
) );
return $this;
}
/**
* Magic Method: __call
* Force TLS
*
* @param string $method
* @param array Params
* @return \IPS\Http\Request\Socket
*/
public function forceTls()
{
if ( defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT') )
{
stream_context_set_option( $this->context, array(
'ssl' => array(
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
)
) );
}
elseif ( defined('STREAM_CRYPTO_METHOD_TLS_CLIENT') )
{
stream_context_set_option( $this->context, array(
'ssl' => array(
'crypto_method' => STREAM_CRYPTO_METHOD_TLS_CLIENT
)
) );
}
return $this;
}
/**
* 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 )
{
$method = mb_strtoupper( $method );
/* The data (string or array) will be the first parameter */
if ( isset( $params[0] ) && is_array( $params[0] ) )
{
@@ -123,87 +178,107 @@ class _Sockets
$data = ( isset( $params[0] ) ? $params[0] : NULL );
}
/* Set the method */
$this->contextOptions['http']['method'] = mb_strtoupper( $method );
/* Set the method and the Content-Length header if this is a POST, PUT or PATCH request */
stream_context_set_option( $this->context, 'http', 'method', $method );
/* Set the Content-Length header automatically if this is a POST */
if( $this->contextOptions['http']['method'] == 'POST' )
if( in_array( $method, array( 'POST', 'PUT', 'PATCH' ) ) )
{
$this->setHeaders( array( 'Content-Length' => \strlen( $data ) ) );
}
/* Parse URL */
/* 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 );
}
$hostname = sprintf( '%s%s:%d',
( $this->url->data['scheme'] === 'https' ) ? 'tls://' : '',
$this->url->data['host'],
isset( $this->url->data['port'] )
? $this->url->data['port']
: ( $this->url->data['scheme'] === 'http' ? 80 : 443 )
);
/* 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 );
try
{
$resource = stream_socket_client( $hostname, $errno, $errstr, $this->timeout, \STREAM_CLIENT_CONNECT, $this->context );
}
/* Catch issues that may arise, such as DNS failure */
catch( \ErrorException $e )
{
throw new SocketsException( $e->getMessage(), $e->getCode() );
}
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";
/* Get the location */
$location = $this->url->data['path'] ? \IPS\Http\Url::encodeComponent( \IPS\Http\Url::COMPONENT_PATH, $this->url->data['path'] ) : '';
$location .= $this->url->data['query'] ? '?' . \IPS\Http\Url::encodeComponent( \IPS\Http\Url::COMPONENT_QUERY, $this->url->data['query'] ) : '';
$location .= $this->url->data['fragment'] ? '#' . \IPS\Http\Url::encodeComponent( \IPS\Http\Url::COMPONENT_FRAGMENT, $this->url->data['fragment'] ) : '';
/* Send request */
$request = mb_strtoupper( $method ) . ' /' . ltrim( $location, '/' ) . " HTTP/{$this->httpVersion}\r\n";
$request .= "Host: {$this->url->data['host']}" . ( isset( $this->url->data['port'] ) ? ":{$this->url->data['port']}" : '' ) . "\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'] )
while( !feof($resource) and !$status['timed_out'] )
{
$response .= \fgets( $resource, 8192 );
$status = stream_get_meta_data( $resource );
}
/* Close connection */
\fclose( $resource );
/* Log */
try
/* Log - but because the output can be large, only do this if we explicitly have debug logging enabled */
if ( defined('\IPS\DEBUG_LOG') and \IPS\DEBUG_LOG )
{
\IPS\Log::i( LOG_DEBUG )->write( "\n\n------------------------------------\nSOCKETS REQUEST: {$this->url}\n------------------------------------\n\n{$request}\n\n------------------------------------\nRESPONSE\n------------------------------------\n\n" . $response, 'request' );
\IPS\Log::debug( "\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 ) )
if ( $this->followRedirects and in_array( $response->httpResponseCode, array( 301, 302, 303, 307, 308 ) ) )
{
$newRequest = \IPS\Http\Url::external( $response->httpHeaders['Location'] )->request( $this->timeout, $this->httpVersion, is_int( $this->followRedirects ) ? ( $this->followRedirects - 1 ) : $this->followRedirects );
/* Fix missing hostname in location */
$location = $response->httpHeaders['Location'];
if( parse_url( $location, PHP_URL_HOST ) === NULL )
{
$location = $this->url->data['scheme'] . '://' . $this->url->data['host'] . $location;
}
$newRequest = \IPS\Http\Url::external( $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;
}
}
/**
+31 -6
View File
@@ -2,9 +2,9 @@
/**
* @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.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 18 Mar 2013
* @version SVN_VERSION_NUMBER
*/
@@ -42,6 +42,11 @@ class _Response
* @brief HTTP Headers
*/
public $httpHeaders = NULL;
/**
* @brief Cookies returned in the response
*/
public $cookies = array();
/**
* @brief Response Content
@@ -79,7 +84,7 @@ class _Response
}
/* If we have received a 1xx response code, that means it's not the final response */
if ( mb_substr( $this->httpResponseCode, 0, 1 ) == 1 )
if ( mb_substr( $this->httpResponseCode, 0, 1 ) == 1 OR ( $this->httpResponseCode == 200 AND trim( $this->httpResponseText ) == 'Connection established' ) )
{
$content = trim( mb_substr( $content, $firstLineBreak ) );
}
@@ -112,6 +117,26 @@ class _Response
if ( $line !== '' and preg_match( "/^(.+?): (.+?)$/", $line, $matches ) )
{
$this->httpHeaders[ $matches[1] ] = $matches[2];
if( mb_strtolower( $matches[1] ) == 'set-cookie' )
{
$cookie = array();
$cookieDetails = explode( ';', $matches[2] );
$cookieInfo = array_shift( $cookieDetails );
$cookieInfo = explode( '=', $cookieInfo );
$cookie[ $cookieInfo[0] ] = array( 'value' => rawurldecode( $cookieInfo[1] ) );
foreach( $cookieDetails as $cookieDetail )
{
$cookieDetail = explode( '=', $cookieDetail );
$cookie[ $cookieInfo[0] ][ trim( $cookieDetail[0] ) ] = ( isset( $cookieDetail[1] ) ) ? $cookieDetail[1] : true;
}
$this->cookies = array_merge( $this->cookies, $cookie );
}
}
}
@@ -123,7 +148,7 @@ class _Response
if( isset( $this->httpHeaders['Transfer-Encoding'] ) and mb_strtolower( trim( $this->httpHeaders['Transfer-Encoding'] ) ) == 'chunked' )
{
if ( $decoded = $this->decodeChunked( $this->content ) )
if ( ( $decoded = $this->decodeChunked( $this->content ) ) !== '' )
{
$this->content = $decoded;
}
@@ -194,11 +219,11 @@ class _Response
*/
public function decodeJson( $asArray=TRUE )
{
if ( !( $json = json_decode( $this->content, $asArray ) ) )
$json = json_decode( $this->content, $asArray );
if ( $json === FALSE )
{
throw new \RuntimeException('BAD_JSON');
}
return $json;
}
+1230 -1098
View File
File diff suppressed because it is too large. Load diff
+27
View File
@@ -0,0 +1,27 @@
<?php
/**
* @brief URL Exception
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 12 May 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Http\Url;
/* 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;
}
/**
* URL Exception
*/
class _Exception extends \DomainException
{
}
+681
View File
@@ -0,0 +1,681 @@
<?php
/**
* @brief Friendly URL
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 11 May 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Http\Url;
/* 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;
}
/**
* Friendly URL
*/
class _Friendly extends \IPS\Http\Url\Internal
{
/**
* @brief Base
*/
public $base = 'front';
/**
* @brief SEO Template
*/
public $seoTemplate;
/**
* @brief SEO Titles (unencoded)
*/
public $seoTitles = array();
/**
* @brief The friendly URL component, which may be for the path or the query string (e.g. "topic/1-test")
*/
public $friendlyUrlComponent;
/* !Factory methods */
/**
* Create a friendly URL from a query string
*
* @param int $protocol Protocol (one of the PROTOCOL_* constants)
* @param string $friendlyUrlComponent The friendly URL component, which may be for the path or the query string (e.g. "topic/1-test")
* @param array $queryString Additional query string data
* @return \IPS\Http\Url\Friendly
* @throws \IPS\Http\Url\Exception
*/
public static function friendlyUrlFromComponent( $protocol, $friendlyUrlComponent, $queryString )
{
if ( \IPS\Settings::i()->htaccess_mod_rewrite )
{
return static::createInternalFromComponents(
'front',
$protocol,
$friendlyUrlComponent ? "{$friendlyUrlComponent}/" : '',
$queryString
);
}
else
{
return static::createInternalFromComponents(
'front',
$protocol,
'index.php',
( $friendlyUrlComponent ? array( "/{$friendlyUrlComponent}/" => NULL ) : array() ) + $queryString
);
}
}
/**
* Create a friendly URL from a full URL, working out friendly URL data
*
* @param array $components An array of components as returned by componentsFromUrlString()
* @param string $potentialFurl The potential FURL slug (e.g. "topic/1-test")
* @return \IPS\Http\Url\Friendly|NULL
*/
public static function createFriendlyUrlFromComponents( $components, $potentialFurl )
{
/* Loop each of our friendly URL definitions */
$matchedFurlDefinition = NULL;
$seoTitles = array();
foreach ( static::furlDefinition() as $seoTemplate => $furlDefinition )
{
if ( $returnedMatches = static::getMatchedParamsFromFriendlyUrlComponent( $furlDefinition, $potentialFurl ) )
{
$matchedFurlDefinition = $furlDefinition;
list( $matchedParams, $seoTitles ) = $returnedMatches;
break;
}
}
/* If we didn't get one return NULL */
if ( !$matchedFurlDefinition )
{
return NULL;
}
/* If we did, create our object... */
return static::createFromComponents( $components[ static::COMPONENT_HOST ], $components[ static::COMPONENT_SCHEME ], $components[ static::COMPONENT_PATH ], $components[ static::COMPONENT_QUERY ], $components[ static::COMPONENT_PORT ], $components[ static::COMPONENT_USERNAME ], $components[ static::COMPONENT_PASSWORD ], $components[ static::COMPONENT_FRAGMENT ] )
->setFriendlyUrlData( $seoTemplate, $seoTitles, $matchedParams, $potentialFurl );
}
/**
* Create a friendly URL from a query string with known friendly URL data
*
* @param string $queryString The query string
* @param string $seoTemplate The key for making this a friendly URL
* @param string|array $seoTitles The title(s) needed for the friendly URL
* @param int $protocol Protocol (one of the PROTOCOL_* constants)
* @return \IPS\Http\Url\Friendly
* @throws \IPS\Http\Url\Exception
* @todo Currently this will silently return a non-friendly URL if $seoTemplate is not valid, for backwards compatibility. Remove this in a major update.
*/
public static function friendlyUrlFromQueryString( $queryString, $seoTemplate, $seoTitles, $protocol )
{
/* Get the friendly URL component */
try
{
$friendlyUrlComponent = static::buildFriendlyUrlComponentFromData( $queryString, $seoTemplate, $seoTitles );
}
catch ( \IPS\Http\Url\Exception $e )
{
if ( $e->getMessage() === 'INVALID_SEO_TEMPLATE' and !\IPS\IN_DEV )
{
return static::createInternalFromComponents( 'front', $protocol, 'index.php', $queryString );
}
else
{
throw $e;
}
}
/* Extract the hidden query string parameters inside it */
$matchedParams = array();
$furlDefinition = static::furlDefinition();
if ( !isset( $furlDefinition[ $seoTemplate ] ) )
{
throw new \IPS\Http\Url\Exception( 'INVALID_SEO_TEMPLATE' );
}
if ( $returnedMatches = static::getMatchedParamsFromFriendlyUrlComponent( $furlDefinition[ $seoTemplate ], $friendlyUrlComponent ) )
{
list( $matchedParams, $_seoTitles ) = $returnedMatches;
}
else
{
if ( \IPS\IN_DEV )
{
throw new \IPS\Http\Url\Exception( 'SEO_TEMPLATE_IS_NOT_VALID_FOR_URL' );
}
}
/* Return */
return static::friendlyUrlFromComponent( $protocol, $friendlyUrlComponent, $queryString )->setFriendlyUrlData( $seoTemplate, $seoTitles, $matchedParams, $friendlyUrlComponent );
}
/* !Instance */
/**
* Set friendly URL data
*
* @param string $seoTemplate The key for making this a friendly URL
* @param string|array $seoTitles The title(s) needed for the friendly URL
* @param array $matchedParams The values for hidden query string properties
* @param string $friendlyUrlComponent The friendly URL component, which may be for the path or the query string (e.g. "topic/1-test")
* @return \IPS\Http\Url\Friendly
* @throws \IPS\Http\Url\Exception
*/
protected function setFriendlyUrlData( $seoTemplate, $seoTitles, $matchedParams=array(), $friendlyUrlComponent )
{
/* Get the definition */
$furlDefinition = static::furlDefinition();
if ( !isset( $furlDefinition[ $seoTemplate ] ) )
{
throw new \IPS\Http\Url\Exception( 'INVALID_SEO_TEMPLATE' );
}
/* Set basic properties */
$this->seoTemplate = $seoTemplate;
$this->seoTitles = is_string( $seoTitles ) ? array( $seoTitles ) : $seoTitles;
$this->friendlyUrlComponent = $friendlyUrlComponent;
/* Set hidden query string */
parse_str( $furlDefinition[ $seoTemplate ]['real'], $hiddenQueryString );
$this->hiddenQueryString = $hiddenQueryString + $matchedParams;
/* Return */
return $this;
}
/**
* Validate that the SEO title is correct
*
* @return mixed The correct URL if incorrect, TRUE is correct, or NULL if unknown
*/
public function correctFriendlyUrl()
{
/* Get the definition */
$furlDefinition = static::furlDefinition();
/* If we don't have a validate callback, we can return NULL */
if ( !isset( $furlDefinition[ $this->seoTemplate ]['verify'] ) or !$furlDefinition[ $this->seoTemplate ]['verify'] )
{
return NULL;
}
/* Load it */
try
{
if ( $correctUrl = $this->correctUrlFromVerifyClass( $furlDefinition[ $this->seoTemplate ]['verify'] ) )
{
/* IP.Board 3.x used /page-x in the path rather than a query string argument - we support this so as not to break past links */
if( mb_strpos( (string) $this, '/page-' ) )
{
preg_match( "/\/page\-(\d+)/", (string) $this, $matches );
if( isset( $matches[1] ) )
{
$correctUrl = $correctUrl->setQueryString( 'page', $matches[1] );
}
}
/* IP.Board 3.x also used to do /page__x__y__a__b for /?x=y&a=b ... let's extract those parameters */
if( mb_strpos( (string) $this, '/page__' ) )
{
preg_match( "/\/page__([^\/$]+?)(?:$|\/)/", (string) $this, $matches );
if( isset( $matches[1] ) )
{
$params = explode( '__', $matches[1] );
$key = NULL;
foreach( $params as $param )
{
if( $key === NULL )
{
$key = $param;
}
else
{
$correctUrl = $correctUrl->setQueryString( $key, $param );
$key = NULL;
}
}
}
}
if ( (string) $correctUrl->normaliseForEqualityCheck() !== (string) $this->normaliseForEqualityCheck() )
{
return $correctUrl;
}
else
{
return TRUE;
}
}
return NULL;
}
/* It doesn't exist */
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
/**
* Normalise the URL for comparing if the "correct" URL is different to the accessed URL
*
* @return \IPS\Http\Url
*/
public function normaliseForEqualityCheck()
{
/* Strip the query string except the FURL component */
$return = $this->stripQueryString();
/* Make it schema-relative */
$return = $return->setScheme( NULL );
/* Return */
return $return;
}
/**
* Strip Query String
* Overrides main method so that the FURL slug isn't lost
*
* @param string|array $keys The key(s) to strip - if omitted, entire query string is wiped
* @return \IPS\Http\Url
*/
public function stripQueryString( $keys=NULL )
{
if ( $keys === NULL and !\IPS\Settings::i()->htaccess_mod_rewrite )
{
$keys = $this->queryString;
unset( $keys[ "/{$this->friendlyUrlComponent}/" ] );
$keys = array_keys( $keys );
}
return parent::stripQueryString( $keys );
}
/* !Utilities */
/**
* Get friendly URL component (e.g. "topic/1-test") from a query string and SEO template
*
* @param string $queryString The query string - is passed by reference and any parts used are removed, which can be used to detect extraneous parts
* @param string $seoTemplate The key for making this a friendly URL
* @param string|array $seoTitles The title(s) needed for the friendly URL
* @return string
* @throws \IPS\Http\Url\Exception
*/
public static function buildFriendlyUrlComponentFromData( &$queryString, $seoTemplate, $seoTitles )
{
/* Get the definition */
$furlDefinition = static::furlDefinition();
if ( !isset( $furlDefinition[ $seoTemplate ] ) )
{
throw new \IPS\Http\Url\Exception( 'INVALID_SEO_TEMPLATE' );
}
$component = $furlDefinition[ $seoTemplate ]['friendly'];
/* Parse the query string into an array */
parse_str( $queryString, $queryString );
/* For each query string component, replace it out */
foreach ( $queryString as $k => $v )
{
if ( mb_strpos( $component, "{#{$k}}" ) !== FALSE or mb_strpos( $component, "{@{$k}}" ) !== FALSE )
{
$component = str_replace( "{#{$k}}", intval( $v ), $component );
$component = str_replace( "{@{$k}}", $v, $component );
unset( $queryString[ $k ] );
}
}
/* Parse out the titles */
$seoTitles = is_null( $seoTitles ) ? array() : ( is_string( $seoTitles ) ? array( $seoTitles ) : array_values( $seoTitles ) );
$seoTitlesCount = count( $seoTitles );
for ( $i = 0; $i < $seoTitlesCount; $i++ )
{
if ( $i === 0 )
{
$component = str_replace( '{?}', $seoTitles[ $i ], $component );
}
$component = str_replace( "{?{$i}}", $seoTitles[ $i ], $component );
}
/* Remove the "real" parts in the query string */
parse_str( $furlDefinition[ $seoTemplate ]['real'], $realQueryString );
foreach ( $realQueryString as $k => $v )
{
if ( isset( $queryString[ $k ] ) and $queryString[ $k ] == $v )
{
unset( $queryString[ $k ] );
}
}
/* Return */
return $component;
}
/**
* Convert a value into an "SEO Title" for friendly URLs
*
* @param string $value Value
* @return string
* @note Many places require an SEO title, so we always need to return something, so when no valid title is available we return a dash
*/
public static function seoTitle( $value )
{
/* Ensure there are no HTML tags */
$value = strip_tags( $value );
/* Always lowercase */
$value = mb_strtolower( $value );
/* Get rid of newlines/carriage returns as they're not cool in friendly URL titles */
$value = str_replace( array( "\r\n", "\r", "\n" ), ' ', $value );
/* Just for readability */
$value = str_replace( ' ', '-', $value );
/* Disallowed characters which browsers may try to automatically percent-encode */
$value = str_replace( array( '!', '*', '\'', '(', ')', ';', ':', '@', '&', '=', '+', '$', ',', '/', '?', '#', '[', ']', '%', '\\', '"', '<', '>', '^', '{', '}', '|', '.', '`' ), '', $value );
if(defined("\IPS\IPB_USE_SEO_TRANSLIT")&&\IPS\IPB_USE_SEO_TRANSLIT) $value = self::rus2translit($value);
/* Trim */
$value = preg_replace( '/\-+/', '-', $value );
$value = trim( $value, '-' );
$value = trim( $value );
/* Return */
return $value ?: '-';
}
static function rus2translit($string) {
$converter = array(
'а' => 'a', 'б' => 'b', 'в' => 'v',
'г' => 'g', 'д' => 'd', 'е' => 'e',
'ё' => 'e', 'ж' => 'zh', 'з' => 'z',
'и' => 'i', 'й' => 'y', 'к' => 'k',
'л' => 'l', 'м' => 'm', 'н' => 'n',
'о' => 'o', 'п' => 'p', 'р' => 'r',
'с' => 's', 'т' => 't', 'у' => 'u',
'ф' => 'f', 'х' => 'h', 'ц' => 'c',
'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sch',
'ь' => '', 'ы' => 'y', 'ъ' => '',
'э' => 'e', 'ю' => 'yu', 'я' => 'ya',
'А' => 'A', 'Б' => 'B', 'В' => 'V',
'Г' => 'G', 'Д' => 'D', 'Е' => 'E',
'Ё' => 'E', 'Ж' => 'Zh', 'З' => 'Z',
'И' => 'I', 'Й' => 'Y', 'К' => 'K',
'Л' => 'L', 'М' => 'M', 'Н' => 'N',
'О' => 'O', 'П' => 'P', 'Р' => 'R',
'С' => 'S', 'Т' => 'T', 'У' => 'U',
'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C',
'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sch',
'Ь' => '', 'Ы' => 'Y', 'Ъ' => '',
'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya',
);
return strtr($string, $converter);
}
/**
* Get the matched parameters and SEO titles from a friendly URL component for a particular
*
* @param array $furlDefinition The FURL definition from the furl.json file
* @param string $friendlyUrlComponent The friendly URL component, which may be for the path or the query string (e.g. "topic/1-test")
* @return array|NULL array( $matchedParams, $seoTitles ) if matched, NULL if the $friendlyUrlComponent does not match this $furlDefinition
*/
protected static function getMatchedParamsFromFriendlyUrlComponent( $furlDefinition, $friendlyUrlComponent )
{
/* See if it matches */
$seoTitles = array();
$matchedParams = array();
foreach ( $furlDefinition['regex'] as $_regex )
{
if ( preg_match( '/^' . $_regex . '$/i', $friendlyUrlComponent, $matches ) )
{
foreach ( $furlDefinition['params'] as $k => $param )
{
if ( $param )
{
$matchedParams[ $param ] = $matches[ $k + 1 ];
}
else
{
$seoTitles[] = $matches[ $k + 1 ];
}
}
return array( $matchedParams, $seoTitles );
}
}
/* Still here? No match */
return NULL;
}
/**
* @brief FURL Definition
*/
protected static $furlDefinition = NULL;
/**
* Get FURL Definition
*
* @param bool $revert If TRUE, ignores all customisations and reloads from json
* @return array
*/
public static function furlDefinition( $revert=FALSE )
{
if ( static::$furlDefinition === NULL or $revert )
{
$furlCustomizations = ( \IPS\Settings::i()->furl_configuration AND !$revert ) ? json_decode( \IPS\Settings::i()->furl_configuration, TRUE ) : array();
$furlConfiguration = ( isset( \IPS\Data\Store::i()->furl_configuration ) AND \IPS\Data\Store::i()->furl_configuration ) ? json_decode( \IPS\Data\Store::i()->furl_configuration, TRUE ) : array();
if ( ( \IPS\IN_DEV and !\IPS\DEV_USE_FURL_CACHE ) or !count( $furlConfiguration ) or $revert )
{
$furlConfiguration = static::buildFurlConfiguation();
}
static::$furlDefinition = array_merge( $furlConfiguration, $furlCustomizations );
}
return static::$furlDefinition;
}
/**
* Rebuild and return \IPS\Data\Store::i()->furl_configuration with default, uncustomised values
*
* @return array
*/
protected static function buildFurlConfiguation()
{
/* Init */
$furlConfiguration = array();
/* Load apps, prioritising the default (otherwise if two apps both have "/category/..." the app which is higher in the list may steal from the default app) */
$applications = \IPS\Application::applications();
foreach ( $applications as $k => $app )
{
if ( $app->default )
{
unset( $applications[ $k ] );
array_unshift( $applications, $app );
break;
}
}
/* Loop each app... */
foreach ( $applications as $app )
{
/* If it has a furl.json file... */
if( file_exists( \IPS\ROOT_PATH . "/applications/{$app->directory}/data/furl.json" ) )
{
/* Open it up */
$data = json_decode( preg_replace( '/\/\*.+?\*\//s', '', \file_get_contents( \IPS\ROOT_PATH . "/applications/{$app->directory}/data/furl.json" ) ), TRUE );
$topLevel = $data['topLevel'];
/* Process them */
$definitions = array();
foreach ( $data['pages'] as $k => $page )
{
$definitions[ $k ] = static::buildFurlDefinition( $page['friendly'], $page['real'], $topLevel, $app->default, isset( $page['alias'] ) ? $page['alias'] : NULL, FALSE, isset( $page['verify'] ) ? $page['verify'] : NULL );
}
/* Add it in */
$furlConfiguration = array_merge( $furlConfiguration, $definitions );
}
}
/* Store */
\IPS\Data\Store::i()->furl_configuration = json_encode( $furlConfiguration );
/* Return */
return $furlConfiguration;
}
/**
* Build the friendly URL definition
*
* @param string $friendly Friendly URL pattern
* @param string $real Non-friendly URL pattern
* @param NULL|string $appTopLevel FURL slug if the app is not the default app
* @param bool $appIsDefault Flag to indicate if the app is default or not
* @param NULL|string $alias Friendly URL alias
* @param bool $custom Flag to indicate if this is a custom FURL definition
* @param string $verify The name of a class that contains a loadFromUrl() and an url() method for verifying the friendly URL is correct
* @return array
*/
public static function buildFurlDefinition( $friendly, $real, $appTopLevel = NULL, $appIsDefault = FALSE, $alias = NULL, $custom = FALSE, $verify = NULL )
{
/* Init */
$return = array(
'friendly' => $friendly,
'real' => $real
);
if ( $verify )
{
$return['verify'] = $verify;
}
if ( $custom )
{
$return['custom'] = TRUE;
}
/* If it has a top-level (e.g. "/forums") we need to store the definition either with that if it's the default app, or
without it if it isn't, so that if the default app changes we can redirect accordingly */
if ( $appTopLevel )
{
if ( $appIsDefault )
{
$return['with_top_level'] = $appTopLevel . ( $friendly ? '/' . $friendly : '' );
}
else
{
$return['without_top_level'] = $friendly;
$return['friendly'] = $appTopLevel . ( $friendly ? '/' . $friendly : '' );
}
}
/* Figure out the regexes */
$return['regex'] = array( preg_quote( $return['friendly'], '/' ) );
if ( $alias )
{
$return['regex'][] = str_replace( '\{\!\}', '(?!&)(?:.+?)', preg_quote( $alias, '/' ) );
}
if ( isset( $return['without_top_level'] ) and $return['without_top_level'] )
{
$return['regex'][] = preg_quote( $return['without_top_level'], '/' );
}
elseif ( isset( $return['with_top_level'] ) )
{
$return['regex'][] = preg_quote( $return['with_top_level'], '/' );
}
/* Parse out variables */
$return['params'] = array();
preg_match_all( '/{(.+?)}/', $return['friendly'], $matches );
foreach ( $matches[1] as $tag )
{
switch ( mb_substr( $tag, 0, 1 ) )
{
case '#':
$return['regex'] = array_map( function( $_regex ) use ( $tag ) {
return str_replace( '\{#' . mb_substr( $tag, 1 ) . '\}', '(\d+?)', $_regex );
}, $return['regex'] );
$return['params'][] = mb_substr( $tag, 1 );
break;
case '@':
$return['regex'] = array_map( function( $_regex ) use ( $tag ) {
return str_replace( '\{@' . mb_substr( $tag, 1 ) . '\}', '(.+?)', $_regex );
}, $return['regex'] );
$return['params'][] = mb_substr( $tag, 1 );
break;
case '?':
$return['regex'] = array_map( function( $_regex ) use ( $tag ) {
return str_replace( '\{\?\}', '(?!&)(.+?)', $_regex );
}, $return['regex'] );
$return['params'][] = '';
break;
}
}
/* Return */
return $return;
}
/* !Deprecated */
/**
* @brief Is internal?
* @deprecated Using this property is deprecated. Check if instance of \IPS\Http\Url\Internal
*/
public $isInternal = TRUE;
/**
* @brief Is friendly?
* @deprecated Using this property is deprecated. Check if instance of \IPS\Http\Url\Friendly
*/
public $isFriendly = TRUE;
}
+247
View File
@@ -0,0 +1,247 @@
<?php
/**
* @brief Internal URL
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 11 May 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Http\Url;
/* 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;
}
/**
* Internal URL
*/
class _Internal extends \IPS\Http\Url
{
/**
* Create internal URL from components - all arguments should be UNENCODED
*
* @param string $base Key for the URL base
* @param int|string|null $protocol Protocol (one of the PROTOCOL_* constants) or an actual scheme
* @param string $path Path, relative to site's base URL
* @param string|array|NULL $query Query
* @param string|NULL $fragment Fragment
* @return \IPS\Http\Url
*/
protected static function createInternalFromComponents( $base, $protocol, $path, $query = NULL, $fragment = NULL )
{
$baseUrlData = parse_url( static::baseUrl( is_numeric( $protocol ) ? $protocol : NULL ) );
$pathToUse = '';
if ( isset( $baseUrlData['path'] ) and $baseUrlData['path'] )
{
$pathToUse .= trim( $baseUrlData['path'], '/' );
}
if ( $path or $query or $fragment or $pathToUse )
{
if ( $pathToUse )
{
$pathToUse .= '/';
}
$pathToUse .= ltrim( $path, '/' );
}
$return = static::createFromComponents(
$baseUrlData['host'],
( is_string( $protocol ) and !is_numeric( $protocol ) ) ? $protocol : ( ( isset( $baseUrlData['scheme'] ) AND $protocol !== NULL ) ? $baseUrlData['scheme'] : NULL ),
$pathToUse ? "/{$pathToUse}" : '',
$query,
isset( $baseUrlData['port'] ) ? $baseUrlData['port'] : NULL,
isset( $baseUrlData['user'] ) ? $baseUrlData['user'] : NULL,
isset( $baseUrlData['pass'] ) ? $baseUrlData['pass'] : NULL,
$fragment
);
$return->base = $base;
return $return;
}
/**
* @brief Base
*/
public $base;
/**
* Get the friendly URL for this URL if there is one
*
* @return mixed The friendly URL if there is one, TRUE if there isn't, or NULL if not sure
*/
public function correctFriendlyUrl()
{
foreach ( \IPS\Http\Url\Friendly::furlDefinition() as $seoTemplate => $furlDefinition )
{
if( mb_stripos( $this->data[ static::COMPONENT_QUERY ], $furlDefinition['real'] ) !== FALSE )
{
/* Figure out if this FURL definition requires extra data.
Example: messenger_convo and messenger have the same $furlDefinition['real'] definition, but messenger_convo requires an 'id' parameter too */
$params = array();
preg_match_all( '/{(.+?)}/', $furlDefinition['friendly'], $matches );
foreach ( $matches[1] as $tag )
{
switch ( mb_substr( $tag, 0, 1 ) )
{
case '#':
$params[] = mb_substr( $tag, 1 );
break;
case '@':
$params[] = mb_substr( $tag, 1 );
break;
}
}
/* If this definition requires a parameter, see if we have it. If not, skip to next definition to check. */
if( count( array_diff( $params, array_keys( $this->queryString ) ) ) )
{
continue;
}
/* Now try to check URL */
try
{
/* If there's a "verify" method, we can use that to get the correct URL */
if( !empty( $furlDefinition['verify'] ) )
{
/* Load it */
try
{
if ( $return = $this->correctUrlFromVerifyClass( $furlDefinition['verify'] ) )
{
return $return;
}
return NULL;
}
/* It doesn't exist */
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
/* If not, we have to build it ourself */
else
{
$seoTitles = array();
if ( isset( $furlDefinition['seoTitles'] ) )
{
foreach ( $furlDefinition['seoTitles'] as $seoTitleData )
{
try
{
$class = $seoTitleData['class'];
$queryParam = $seoTitleData['queryParam'];
$property = $seoTitleData['property'];
$seoTitles[] = $class::load( $this->queryString[ $queryParam ] )->$property;
}
catch ( \OutOfRangeException $e ) {}
}
}
return \IPS\Http\Url::internal( $this->data['query'], 'front', $seoTemplate, $seoTitles );
}
}
catch ( \Exception $e ) {}
}
}
return TRUE;
}
/**
* Get the correct URL using a "verify" property from the FURL definition
*
* @return
* @throws \OutOfRangeException
*/
protected function correctUrlFromVerifyClass( $verify )
{
/* Load it */
if ( method_exists( $verify, 'preCorrectUrlFromVerifyClass' ) )
{
$verify::preCorrectUrlFromVerifyClass( $this );
}
$contentObject = $verify::loadFromUrl( $this );
/* Check the currently logged in user can view it, as we don't want to reveal the correct
URL to something the user has no permission to view */
$canView = TRUE;
if ( $contentObject instanceof \IPS\Content )
{
$canView = $contentObject->canView();
}
elseif ( $contentObject instanceof \IPS\Node\Model )
{
$canView = $contentObject->can('view');
}
if ( !$canView )
{
return NULL;
}
/* Create it */
$correctUrl = $contentObject->url();
/* Set extra stuff in our query string */
$paramsToSet = array();
foreach ( $this->queryString as $k => $v )
{
if ( !array_key_exists( $k, $correctUrl->queryString ) and !array_key_exists( $k, $correctUrl->hiddenQueryString ) )
{
$paramsToSet[ $k ] = $v;
}
}
if ( count( $paramsToSet ) )
{
$correctUrl = $correctUrl->setQueryString( $paramsToSet );
}
/* Return */
return $correctUrl;
}
/**
* Add CSRF check to query string
*
* @return \IPS\Http\Url
*/
public function csrf()
{
return $this->setQueryString( 'csrfKey', \IPS\Session::i()->csrfKey );
}
/**
* Get ACP query string without adsess
*
* @return string
*/
public function acpQueryString()
{
$queryString = $this->queryString;
unset( $queryString['adsess'] );
unset( $queryString['csrf'] );
return static::convertQueryAsArrayToString( $queryString );
}
/* !Deprecated */
/**
* @brief Is internal?
* @deprecated Using this property is deprecated. Check if instance of \IPS\Http\Url\Internal
*/
public $isInternal = TRUE;
/**
* @brief Is friendly?
* @deprecated Using this property is deprecated. Check if instance of \IPS\Http\Url\Friendly
*/
public $isFriendly = FALSE;
}
+6 -17
View File
@@ -2,9 +2,9 @@
/**
* @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.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 20 Aug 2013
* @version SVN_VERSION_NUMBER
*/
@@ -224,6 +224,7 @@ class _Useragent
* @note You may put multiple regex definitions for a single key into an array
*/
protected $standardEngineUseragents = array(
'edge' => "edge/([0-9.]{1,10})",
'amaya' => "amaya/([0-9.]{1,10})",
'aol' => "aol[ /\-]([0-9.]{1,10})",
'camino' => "camino/([0-9.+]{1,10})",
@@ -249,21 +250,9 @@ class _Useragent
/**
* @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
* @note List pulled via suggested whois command on Dec 13 2016
*/
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'
);
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', '179.60.192.0/22', '179.60.192.0/24', '179.60.193.0/24', '179.60.194.0/24', '179.60.195.0/24', '185.60.216.0/22', '45.64.40.0/22', '185.60.216.0/24', '185.60.217.0/24', '185.60.218.0/24', '185.60.219.0/24', '129.134.0.0/16', '157.240.0.0/16', '157.240.8.0/24', '157.240.0.0/24', '157.240.1.0/24', '157.240.2.0/24', '157.240.3.0/24', '157.240.4.0/24', '157.240.5.0/24', '157.240.6.0/24', '157.240.7.0/24', '157.240.9.0/24', '157.240.10.0/24', '204.15.20.0/22', '69.63.176.0/20', '69.63.176.0/21', '69.63.184.0/21', '66.220.144.0/20', ' 69.63.176.0/20', '2620:0:1c00::/40', '2a03:2880::/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', '2a03:2880:f017::/48', '2a03:2880:f018::/48', '2a03:2880:f019::/48', '2a03:2880:f01a::/48', '2a03:2880:f01b::/48', '2a03:2880:f01c::/48', '2a03:2880:f01d::/48', '2a03:2880:f01e::/48', '2a03:2880:f01f::/48', '2a03:2880:1000::/36', '2a03:2880:2000::/36', '2a03:2880:3000::/36', '2a03:2880:4000::/36', '2a03:2880:5000::/36', '2a03:2880:6000::/36', '2a03:2880:7000::/36', '2a03:2880:f020::/48', '2a03:2880:f021::/48', '2a03:2880:f022::/48', '2a03:2880:f023::/48', '2a03:2880:f024::/48', '2a03:2880:f025::/48', '2a03:2880:f026::/48', '2a03:2880:f027::/48', '2a03:2880:f028::/48', '2a03:2880:f029::/48', '2a03:2880:f02b::/48', '2a03:2880:f02c::/48', '2a03:2880:f02d::/48', '2a03:2880:f02e::/48', '2a03:2880:f02f::/48', '2a03:2880:f030::/48', '2a03:2880:f031::/48', '2a03:2880:f032::/48', '2a03:2880:f033::/48', '2a03:2880:f034::/48', '2a03:2880:f035::/48', '2a03:2880:f036::/48', '2a03:2880:f037::/48', '2a03:2880:f038::/48', '2a03:2880:f039::/48', '2a03:2880:f03a::/48', '2a03:2880:f03b::/48', '2a03:2880:f03c::/48', '2a03:2880:f03d::/48', '2a03:2880:f03e::/48', '2a03:2880:f03f::/48', '2401:db00::/32', '2a03:2880::/36', '2803:6080::/32', '2a03:2880:f100::/48', '2a03:2880:f200::/48', '2a03:2880:f101::/48', '2a03:2880:f201::/48', '2a03:2880:f102::/48', '2a03:2880:f202::/48', '2a03:2880:f103::/48', '2a03:2880:f203::/48', '2a03:2880:f104::/48', '2a03:2880:f204::/48', '2a03:2880:f107::/48', '2a03:2880:f207::/48', '2a03:2880:f108::/48', '2a03:2880:f208::/48', '2a03:2880:f109::/48', '2a03:2880:f209::/48', '2a03:2880:f10a::/48', '2a03:2880:f20a::/48', '2a03:2880:f10b::/48', '2a03:2880:f20b::/48', '2a03:2880:f10d::/48', '2a03:2880:f20d::/48', '2a03:2880:f10e::/48', '2a03:2880:f20e::/48', '2a03:2880:f10f::/48', '2a03:2880:f20f::/48', '2a03:2880:f110::/48', '2a03:2880:f210::/48', '2a03:2880:f111::/48', '2a03:2880:f211::/48', '2a03:2880:f112::/48', '2a03:2880:f212::/48', '2a03:2880:f114::/48', '2a03:2880:f214::/48', '2a03:2880:f115::/48', '2a03:2880:f215::/48', '2a03:2880:f116::/48', '2a03:2880:f216::/48', '2a03:2880:f117::/48', '2a03:2880:f217::/48', '2a03:2880:f118::/48', '2a03:2880:f218::/48', '2a03:2880:f119::/48', '2a03:2880:f219::/48', '2a03:2880:f11a::/48', '2a03:2880:f21a::/48', '2a03:2880:f11f::/48', '2a03:2880:f21f::/48', '2a03:2880:f121::/48', '2a03:2880:f221::/48', '2a03:2880:f122::/48', '2a03:2880:f222::/48', '2a03:2880:f123::/48', '2a03:2880:f223::/48', '2a03:2880:f10c::/48', '2a03:2880:f20c::/48', '2a03:2880:f126::/48', '2a03:2880:f226::/48', '2a03:2880:f105::/48', '2a03:2880:f205::/48', '2a03:2880:f125::/48', '2a03:2880:f225::/48', '2a03:2880:f106::/48', '2a03:2880:f206::/48', '2a03:2880:f11b::/48', '2a03:2880:f21b::/48', '2a03:2880:f113::/48', '2a03:2880:f213::/48', '2a03:2880:f11c::/48', '2a03:2880:f21Line truncated
/**
* Verify a supplied IP address is within the Facebook range
@@ -291,7 +280,7 @@ class _Useragent
$net = $this->_convertCompressedIpv6ToBits( inet_pton( $net ) );
if( $_checkIp == $_checkNet )
if( $ip == $net )
{
return TRUE;
}