Seditio Source
Root |
./othercms/ips_4.3.4/system/Http/Response.php
<?php
/**
 * @brief        HTTP Response 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;
 
/* 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    Cookies returned in the response
     */
   
public $cookies = array();
   
   
/**
     * @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 OR ( $this->httpResponseCode == 200 AND trim( $this->httpResponseText ) == 'Connection established' ) )
            {
               
$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];

                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 );
                }
            }
        }
       
       
/* 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 )
    {
       
$json = json_decode( $this->content, $asArray );
        if (
$json === FALSE )
        {
            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;
    }
}