Seditio Source
Root |
./othercms/ips_4.3.4/system/Http/Request/Sockets.php
<?php
/**
 * @brief        Sockets REST Class
 * @author        <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright    (c) Invision Power Services, Inc.
 * @license        https://www.invisioncommunity.com/legal/standards/
 * @package        Invision Community
 * @since        18 Mar 2013
 */

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   Stream context
     */
   
protected $context;

   
/**
     * @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->context = stream_context_create();
       
$this->httpVersion = $httpVersion ?: '1.1';
       
$this->timeout = $timeout;
       
$this->followRedirects = $followRedirects;
       
       
/* 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,
               
'crypto_method'        => STREAM_CRYPTO_METHOD_ANY_CLIENT
           
)
        ) );
    }

   
/**
     * Login
     *
     * @param    string    Username
     * @param    string    Password
     * @return    \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;
    }
   
   
/**
     * Toggle SSL checks
     *
     * @param    boolean        $value    True will enable SSL checks, false will disable them
     * @return    \IPS\Http\Request\Socket
     */
   
public function sslCheck( $value=TRUE )
    {
       
stream_context_set_option( $this->context, array(
           
'ssl'   => array(
               
'verify_peer_name'  => ( $value ) ? 2 : FALSE,
               
'verify_peer'       => (boolean) $value,
            )
        ) );

        return
$this;
    }
   
   
/**
     * Force TLS
     *
     * @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] ) )
        {
           
$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 and the Content-Length header if this is a POST, PUT or PATCH request */
       
stream_context_set_option( $this->context, 'http', 'method', $method );

        if(
$data )
        {
           
$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 );
        }

       
$hostname = sprintf( '%s%s:%d',
            (
$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 )
        );

       
/* Open connection */
       
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 );
        }

       
/* Get the location */
       
$location    = $this->url->data['path'] ? \IPS\Http\Url::encodeComponent( \IPS\Http\Url::COMPONENT_PATH, $this->url->data['path'] ) : '';
       
$location    .= ( count( $this->url->queryString ) ) ? '?' . \IPS\Http\Url::convertQueryAsArrayToString( $this->url->queryString, true ) : '';
       
$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'] )
        {
           
$response .= \fgets( $resource, 8192 );
           
$status = stream_get_meta_data( $resource );
        }
               
       
/* Close connection */
       
\fclose( $resource );
       
       
/* 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::debug( "\n\n------------------------------------\nSOCKETS REQUEST: {$this->url}\n------------------------------------\n\n{$request}\n\n------------------------------------\nRESPONSE\n------------------------------------\n\n" . $response, 'request' );
        }

       
/* Interpret response */
       
$response = new \IPS\Http\Response( $response );

       
/* 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
$newRequest->$method( $params );
        }
        return
$response;
    }
}

/**
 * Sockets Exception Class
 */
class SocketsException extends \IPS\Http\Request\Exception { }