Make WordPress Core


Ignore:
Timestamp:
08/26/2015 03:55:00 AM (9 years ago)
Author:
wonderboymusic
Message:

HTTP: move classes into their own files, http.php loads the new files, so this is 100% BC if someone is loading http.php directly. New files created using svn cp.

class-http.php requires functions from http.php, so loading it by itself wouldn't have worked.

Creates:
class-wp-http-cookie.php
class-wp-http-curl.php
class-wp-http-encoding.php
class-wp-http-proxy.php
class-wp-http-streams.php
http-functions.php

WP_Http remains in class-http.php.

http.php contains only top-level code. Class files only contain classes. Functions file only contains functions.

See #33413.

File:
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-http-cookie.php

    r33746 r33748  
    11<?php
    2 /**
    3  * Simple and uniform HTTP request API.
    4  *
    5  * Standardizes the HTTP requests for WordPress. Handles cookies, gzip encoding and decoding, chunk
    6  * decoding, if HTTP 1.1 and various other difficult HTTP protocol implementations.
    7  *
    8  * @link https://core.trac.wordpress.org/ticket/4779 HTTP API Proposal
    9  *
    10  * @package WordPress
    11  * @subpackage HTTP
    12  * @since 2.7.0
    13  */
    14 
    15 /**
    16  * WordPress HTTP Class for managing HTTP Transports and making HTTP requests.
    17  *
    18  * This class is used to consistently make outgoing HTTP requests easy for developers
    19  * while still being compatible with the many PHP configurations under which
    20  * WordPress runs.
    21  *
    22  * Debugging includes several actions, which pass different variables for debugging the HTTP API.
    23  *
    24  * @package WordPress
    25  * @subpackage HTTP
    26  * @since 2.7.0
    27  */
    28 class WP_Http {
    29 
    30     /**
    31      * Send an HTTP request to a URI.
    32      *
    33      * Please note: The only URI that are supported in the HTTP Transport implementation
    34      * are the HTTP and HTTPS protocols.
    35      *
    36      * @access public
    37      * @since 2.7.0
    38      *
    39      * @global string $wp_version
    40      *
    41      * @param string       $url  The request URL.
    42      * @param string|array $args {
    43      *     Optional. Array or string of HTTP request arguments.
    44      *
    45      *     @type string       $method              Request method. Accepts 'GET', 'POST', 'HEAD', or 'PUT'.
    46      *                                             Some transports technically allow others, but should not be
    47      *                                             assumed. Default 'GET'.
    48      *     @type int          $timeout             How long the connection should stay open in seconds. Default 5.
    49      *     @type int          $redirection         Number of allowed redirects. Not supported by all transports
    50      *                                             Default 5.
    51      *     @type string       $httpversion         Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
    52      *                                             Default '1.0'.
    53      *     @type string       $user-agent          User-agent value sent.
    54      *                                             Default WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ).
    55      *     @type bool         $reject_unsafe_urls  Whether to pass URLs through {@see wp_http_validate_url()}.
    56      *                                             Default false.
    57      *     @type bool         $blocking            Whether the calling code requires the result of the request.
    58      *                                             If set to false, the request will be sent to the remote server,
    59      *                                             and processing returned to the calling code immediately, the caller
    60      *                                             will know if the request succeeded or failed, but will not receive
    61      *                                             any response from the remote server. Default true.
    62      *     @type string|array $headers             Array or string of headers to send with the request.
    63      *                                             Default empty array.
    64      *     @type array        $cookies             List of cookies to send with the request. Default empty array.
    65      *     @type string|array $body                Body to send with the request. Default null.
    66      *     @type bool         $compress            Whether to compress the $body when sending the request.
    67      *                                             Default false.
    68      *     @type bool         $decompress          Whether to decompress a compressed response. If set to false and
    69      *                                             compressed content is returned in the response anyway, it will
    70      *                                             need to be separately decompressed. Default true.
    71      *     @type bool         $sslverify           Whether to verify SSL for the request. Default true.
    72      *     @type string       sslcertificates      Absolute path to an SSL certificate .crt file.
    73      *                                             Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
    74      *     @type bool         $stream              Whether to stream to a file. If set to true and no filename was
    75      *                                             given, it will be droped it in the WP temp dir and its name will
    76      *                                             be set using the basename of the URL. Default false.
    77      *     @type string       $filename            Filename of the file to write to when streaming. $stream must be
    78      *                                             set to true. Default null.
    79      *     @type int          $limit_response_size Size in bytes to limit the response to. Default null.
    80      *
    81      * }
    82      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
    83      *                        A WP_Error instance upon error.
    84      */
    85     public function request( $url, $args = array() ) {
    86         global $wp_version;
    87 
    88         $defaults = array(
    89             'method' => 'GET',
    90             /**
    91              * Filter the timeout value for an HTTP request.
    92              *
    93              * @since 2.7.0
    94              *
    95              * @param int $timeout_value Time in seconds until a request times out.
    96              *                           Default 5.
    97              */
    98             'timeout' => apply_filters( 'http_request_timeout', 5 ),
    99             /**
    100              * Filter the number of redirects allowed during an HTTP request.
    101              *
    102              * @since 2.7.0
    103              *
    104              * @param int $redirect_count Number of redirects allowed. Default 5.
    105              */
    106             'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
    107             /**
    108              * Filter the version of the HTTP protocol used in a request.
    109              *
    110              * @since 2.7.0
    111              *
    112              * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
    113              *                        Default '1.0'.
    114              */
    115             'httpversion' => apply_filters( 'http_request_version', '1.0' ),
    116             /**
    117              * Filter the user agent value sent with an HTTP request.
    118              *
    119              * @since 2.7.0
    120              *
    121              * @param string $user_agent WordPress user agent string.
    122              */
    123             'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ),
    124             /**
    125              * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request.
    126              *
    127              * @since 3.6.0
    128              *
    129              * @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
    130              *                       Default false.
    131              */
    132             'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
    133             'blocking' => true,
    134             'headers' => array(),
    135             'cookies' => array(),
    136             'body' => null,
    137             'compress' => false,
    138             'decompress' => true,
    139             'sslverify' => true,
    140             'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
    141             'stream' => false,
    142             'filename' => null,
    143             'limit_response_size' => null,
    144         );
    145 
    146         // Pre-parse for the HEAD checks.
    147         $args = wp_parse_args( $args );
    148 
    149         // By default, Head requests do not cause redirections.
    150         if ( isset($args['method']) && 'HEAD' == $args['method'] )
    151             $defaults['redirection'] = 0;
    152 
    153         $r = wp_parse_args( $args, $defaults );
    154         /**
    155          * Filter the arguments used in an HTTP request.
    156          *
    157          * @since 2.7.0
    158          *
    159          * @param array  $r   An array of HTTP request arguments.
    160          * @param string $url The request URL.
    161          */
    162         $r = apply_filters( 'http_request_args', $r, $url );
    163 
    164         // The transports decrement this, store a copy of the original value for loop purposes.
    165         if ( ! isset( $r['_redirection'] ) )
    166             $r['_redirection'] = $r['redirection'];
    167 
    168         /**
    169          * Filter whether to preempt an HTTP request's return.
    170          *
    171          * Returning a truthy value to the filter will short-circuit
    172          * the HTTP request and return early with that value.
    173          *
    174          * @since 2.9.0
    175          *
    176          * @param bool   $preempt Whether to preempt an HTTP request return. Default false.
    177          * @param array  $r       HTTP request arguments.
    178          * @param string $url     The request URL.
    179          */
    180         $pre = apply_filters( 'pre_http_request', false, $r, $url );
    181         if ( false !== $pre )
    182             return $pre;
    183 
    184         if ( function_exists( 'wp_kses_bad_protocol' ) ) {
    185             if ( $r['reject_unsafe_urls'] )
    186                 $url = wp_http_validate_url( $url );
    187             if ( $url ) {
    188                 $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
    189             }
    190         }
    191 
    192         $arrURL = @parse_url( $url );
    193 
    194         if ( empty( $url ) || empty( $arrURL['scheme'] ) )
    195             return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
    196 
    197         if ( $this->block_request( $url ) )
    198             return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
    199 
    200         /*
    201          * Determine if this is a https call and pass that on to the transport functions
    202          * so that we can blacklist the transports that do not support ssl verification
    203          */
    204         $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
    205 
    206         // Determine if this request is to OUR install of WordPress.
    207         $homeURL = parse_url( get_bloginfo( 'url' ) );
    208         $r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] );
    209         unset( $homeURL );
    210 
    211         /*
    212          * If we are streaming to a file but no filename was given drop it in the WP temp dir
    213          * and pick its name using the basename of the $url.
    214          */
    215         if ( $r['stream']  && empty( $r['filename'] ) ) {
    216             $r['filename'] = get_temp_dir() . wp_unique_filename( get_temp_dir(), basename( $url ) );
    217         }
    218 
    219         /*
    220          * Force some settings if we are streaming to a file and check for existence and perms
    221          * of destination directory.
    222          */
    223         if ( $r['stream'] ) {
    224             $r['blocking'] = true;
    225             if ( ! wp_is_writable( dirname( $r['filename'] ) ) )
    226                 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
    227         }
    228 
    229         if ( is_null( $r['headers'] ) )
    230             $r['headers'] = array();
    231 
    232         if ( ! is_array( $r['headers'] ) ) {
    233             $processedHeaders = self::processHeaders( $r['headers'], $url );
    234             $r['headers'] = $processedHeaders['headers'];
    235         }
    236 
    237         if ( isset( $r['headers']['User-Agent'] ) ) {
    238             $r['user-agent'] = $r['headers']['User-Agent'];
    239             unset( $r['headers']['User-Agent'] );
    240         }
    241 
    242         if ( isset( $r['headers']['user-agent'] ) ) {
    243             $r['user-agent'] = $r['headers']['user-agent'];
    244             unset( $r['headers']['user-agent'] );
    245         }
    246 
    247         if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) {
    248             $r['headers']['connection'] = 'close';
    249         }
    250 
    251         // Construct Cookie: header if any cookies are set.
    252         self::buildCookieHeader( $r );
    253 
    254         // Avoid issues where mbstring.func_overload is enabled.
    255         mbstring_binary_safe_encoding();
    256 
    257         if ( ! isset( $r['headers']['Accept-Encoding'] ) ) {
    258             if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) )
    259                 $r['headers']['Accept-Encoding'] = $encoding;
    260         }
    261 
    262         if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) {
    263             if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
    264                 $r['body'] = http_build_query( $r['body'], null, '&' );
    265 
    266                 if ( ! isset( $r['headers']['Content-Type'] ) )
    267                     $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
    268             }
    269 
    270             if ( '' === $r['body'] )
    271                 $r['body'] = null;
    272 
    273             if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
    274                 $r['headers']['Content-Length'] = strlen( $r['body'] );
    275         }
    276 
    277         $response = $this->_dispatch_request( $url, $r );
    278 
    279         reset_mbstring_encoding();
    280 
    281         if ( is_wp_error( $response ) )
    282             return $response;
    283 
    284         // Append cookies that were used in this request to the response
    285         if ( ! empty( $r['cookies'] ) ) {
    286             $cookies_set = wp_list_pluck( $response['cookies'], 'name' );
    287             foreach ( $r['cookies'] as $cookie ) {
    288                 if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) {
    289                     $response['cookies'][] = $cookie;
    290                 }
    291             }
    292         }
    293 
    294         return $response;
    295     }
    296 
    297     /**
    298      * Tests which transports are capable of supporting the request.
    299      *
    300      * @since 3.2.0
    301      * @access private
    302      *
    303      * @param array $args Request arguments
    304      * @param string $url URL to Request
    305      *
    306      * @return string|false Class name for the first transport that claims to support the request. False if no transport claims to support the request.
    307      */
    308     public function _get_first_available_transport( $args, $url = null ) {
    309         /**
    310          * Filter which HTTP transports are available and in what order.
    311          *
    312          * @since 3.7.0
    313          *
    314          * @param array  $value Array of HTTP transports to check. Default array contains
    315          *                      'curl', and 'streams', in that order.
    316          * @param array  $args  HTTP request arguments.
    317          * @param string $url   The URL to request.
    318          */
    319         $request_order = apply_filters( 'http_api_transports', array( 'curl', 'streams' ), $args, $url );
    320 
    321         // Loop over each transport on each HTTP request looking for one which will serve this request's needs.
    322         foreach ( $request_order as $transport ) {
    323             $class = 'WP_HTTP_' . $transport;
    324 
    325             // Check to see if this transport is a possibility, calls the transport statically.
    326             if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
    327                 continue;
    328 
    329             return $class;
    330         }
    331 
    332         return false;
    333     }
    334 
    335     /**
    336      * Dispatches a HTTP request to a supporting transport.
    337      *
    338      * Tests each transport in order to find a transport which matches the request arguments.
    339      * Also caches the transport instance to be used later.
    340      *
    341      * The order for requests is cURL, and then PHP Streams.
    342      *
    343      * @since 3.2.0
    344      *
    345      * @static
    346      * @access private
    347      *
    348      * @param string $url URL to Request
    349      * @param array $args Request arguments
    350      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    351      */
    352     private function _dispatch_request( $url, $args ) {
    353         static $transports = array();
    354 
    355         $class = $this->_get_first_available_transport( $args, $url );
    356         if ( !$class )
    357             return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
    358 
    359         // Transport claims to support request, instantiate it and give it a whirl.
    360         if ( empty( $transports[$class] ) )
    361             $transports[$class] = new $class;
    362 
    363         $response = $transports[$class]->request( $url, $args );
    364 
    365         /**
    366          * Fires after an HTTP API response is received and before the response is returned.
    367          *
    368          * @since 2.8.0
    369          *
    370          * @param array|WP_Error $response HTTP response or WP_Error object.
    371          * @param string         $context  Context under which the hook is fired.
    372          * @param string         $class    HTTP transport used.
    373          * @param array          $args     HTTP request arguments.
    374          * @param string         $url      The request URL.
    375          */
    376         do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
    377 
    378         if ( is_wp_error( $response ) )
    379             return $response;
    380 
    381         /**
    382          * Filter the HTTP API response immediately before the response is returned.
    383          *
    384          * @since 2.9.0
    385          *
    386          * @param array  $response HTTP response.
    387          * @param array  $args     HTTP request arguments.
    388          * @param string $url      The request URL.
    389          */
    390         return apply_filters( 'http_response', $response, $args, $url );
    391     }
    392 
    393     /**
    394      * Uses the POST HTTP method.
    395      *
    396      * Used for sending data that is expected to be in the body.
    397      *
    398      * @access public
    399      * @since 2.7.0
    400      *
    401      * @param string       $url  The request URL.
    402      * @param string|array $args Optional. Override the defaults.
    403      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    404      */
    405     public function post($url, $args = array()) {
    406         $defaults = array('method' => 'POST');
    407         $r = wp_parse_args( $args, $defaults );
    408         return $this->request($url, $r);
    409     }
    410 
    411     /**
    412      * Uses the GET HTTP method.
    413      *
    414      * Used for sending data that is expected to be in the body.
    415      *
    416      * @access public
    417      * @since 2.7.0
    418      *
    419      * @param string $url The request URL.
    420      * @param string|array $args Optional. Override the defaults.
    421      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    422      */
    423     public function get($url, $args = array()) {
    424         $defaults = array('method' => 'GET');
    425         $r = wp_parse_args( $args, $defaults );
    426         return $this->request($url, $r);
    427     }
    428 
    429     /**
    430      * Uses the HEAD HTTP method.
    431      *
    432      * Used for sending data that is expected to be in the body.
    433      *
    434      * @access public
    435      * @since 2.7.0
    436      *
    437      * @param string $url The request URL.
    438      * @param string|array $args Optional. Override the defaults.
    439      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    440      */
    441     public function head($url, $args = array()) {
    442         $defaults = array('method' => 'HEAD');
    443         $r = wp_parse_args( $args, $defaults );
    444         return $this->request($url, $r);
    445     }
    446 
    447     /**
    448      * Parses the responses and splits the parts into headers and body.
    449      *
    450      * @access public
    451      * @static
    452      * @since 2.7.0
    453      *
    454      * @param string $strResponse The full response string
    455      * @return array Array with 'headers' and 'body' keys.
    456      */
    457     public static function processResponse($strResponse) {
    458         $res = explode("\r\n\r\n", $strResponse, 2);
    459 
    460         return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
    461     }
    462 
    463     /**
    464      * Transform header string into an array.
    465      *
    466      * If an array is given then it is assumed to be raw header data with numeric keys with the
    467      * headers as the values. No headers must be passed that were already processed.
    468      *
    469      * @access public
    470      * @static
    471      * @since 2.7.0
    472      *
    473      * @param string|array $headers
    474      * @param string $url The URL that was requested
    475      * @return array Processed string headers. If duplicate headers are encountered,
    476      *                  Then a numbered array is returned as the value of that header-key.
    477      */
    478     public static function processHeaders( $headers, $url = '' ) {
    479         // Split headers, one per array element.
    480         if ( is_string($headers) ) {
    481             // Tolerate line terminator: CRLF = LF (RFC 2616 19.3).
    482             $headers = str_replace("\r\n", "\n", $headers);
    483             /*
    484              * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>,
    485              * <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2).
    486              */
    487             $headers = preg_replace('/\n[ \t]/', ' ', $headers);
    488             // Create the headers array.
    489             $headers = explode("\n", $headers);
    490         }
    491 
    492         $response = array('code' => 0, 'message' => '');
    493 
    494         /*
    495          * If a redirection has taken place, The headers for each page request may have been passed.
    496          * In this case, determine the final HTTP header and parse from there.
    497          */
    498         for ( $i = count($headers)-1; $i >= 0; $i-- ) {
    499             if ( !empty($headers[$i]) && false === strpos($headers[$i], ':') ) {
    500                 $headers = array_splice($headers, $i);
    501                 break;
    502             }
    503         }
    504 
    505         $cookies = array();
    506         $newheaders = array();
    507         foreach ( (array) $headers as $tempheader ) {
    508             if ( empty($tempheader) )
    509                 continue;
    510 
    511             if ( false === strpos($tempheader, ':') ) {
    512                 $stack = explode(' ', $tempheader, 3);
    513                 $stack[] = '';
    514                 list( , $response['code'], $response['message']) = $stack;
    515                 continue;
    516             }
    517 
    518             list($key, $value) = explode(':', $tempheader, 2);
    519 
    520             $key = strtolower( $key );
    521             $value = trim( $value );
    522 
    523             if ( isset( $newheaders[ $key ] ) ) {
    524                 if ( ! is_array( $newheaders[ $key ] ) )
    525                     $newheaders[$key] = array( $newheaders[ $key ] );
    526                 $newheaders[ $key ][] = $value;
    527             } else {
    528                 $newheaders[ $key ] = $value;
    529             }
    530             if ( 'set-cookie' == $key )
    531                 $cookies[] = new WP_Http_Cookie( $value, $url );
    532         }
    533 
    534         // Cast the Response Code to an int
    535         $response['code'] = intval( $response['code'] );
    536 
    537         return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
    538     }
    539 
    540     /**
    541      * Takes the arguments for a ::request() and checks for the cookie array.
    542      *
    543      * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances,
    544      * which are each parsed into strings and added to the Cookie: header (within the arguments array).
    545      * Edits the array by reference.
    546      *
    547      * @access public
    548      * @version 2.8.0
    549      * @static
    550      *
    551      * @param array $r Full array of args passed into ::request()
    552      */
    553     public static function buildCookieHeader( &$r ) {
    554         if ( ! empty($r['cookies']) ) {
    555             // Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances.
    556             foreach ( $r['cookies'] as $name => $value ) {
    557                 if ( ! is_object( $value ) )
    558                     $r['cookies'][ $name ] = new WP_HTTP_Cookie( array( 'name' => $name, 'value' => $value ) );
    559             }
    560 
    561             $cookies_header = '';
    562             foreach ( (array) $r['cookies'] as $cookie ) {
    563                 $cookies_header .= $cookie->getHeaderValue() . '; ';
    564             }
    565 
    566             $cookies_header = substr( $cookies_header, 0, -2 );
    567             $r['headers']['cookie'] = $cookies_header;
    568         }
    569     }
    570 
    571     /**
    572      * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
    573      *
    574      * Based off the HTTP http_encoding_dechunk function.
    575      *
    576      * @link http://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
    577      *
    578      * @access public
    579      * @since 2.7.0
    580      * @static
    581      *
    582      * @param string $body Body content
    583      * @return string Chunked decoded body on success or raw body on failure.
    584      */
    585     public static function chunkTransferDecode( $body ) {
    586         // The body is not chunked encoded or is malformed.
    587         if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) )
    588             return $body;
    589 
    590         $parsed_body = '';
    591 
    592         // We'll be altering $body, so need a backup in case of error.
    593         $body_original = $body;
    594 
    595         while ( true ) {
    596             $has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match );
    597             if ( ! $has_chunk || empty( $match[1] ) )
    598                 return $body_original;
    599 
    600             $length = hexdec( $match[1] );
    601             $chunk_length = strlen( $match[0] );
    602 
    603             // Parse out the chunk of data.
    604             $parsed_body .= substr( $body, $chunk_length, $length );
    605 
    606             // Remove the chunk from the raw data.
    607             $body = substr( $body, $length + $chunk_length );
    608 
    609             // End of the document.
    610             if ( '0' === trim( $body ) )
    611                 return $parsed_body;
    612         }
    613     }
    614 
    615     /**
    616      * Block requests through the proxy.
    617      *
    618      * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
    619      * prevent plugins from working and core functionality, if you don't include api.wordpress.org.
    620      *
    621      * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php
    622      * file and this will only allow localhost and your blog to make requests. The constant
    623      * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the
    624      * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains
    625      * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted.
    626      *
    627      * @since 2.8.0
    628      * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
    629      * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
    630      *
    631      * @staticvar array|null $accessible_hosts
    632      * @staticvar array      $wildcard_regex
    633      *
    634      * @param string $uri URI of url.
    635      * @return bool True to block, false to allow.
    636      */
    637     public function block_request($uri) {
    638         // We don't need to block requests, because nothing is blocked.
    639         if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL )
    640             return false;
    641 
    642         $check = parse_url($uri);
    643         if ( ! $check )
    644             return true;
    645 
    646         $home = parse_url( get_option('siteurl') );
    647 
    648         // Don't block requests back to ourselves by default.
    649         if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) {
    650             /**
    651              * Filter whether to block local requests through the proxy.
    652              *
    653              * @since 2.8.0
    654              *
    655              * @param bool $block Whether to block local requests through proxy.
    656              *                    Default false.
    657              */
    658             return apply_filters( 'block_local_requests', false );
    659         }
    660 
    661         if ( !defined('WP_ACCESSIBLE_HOSTS') )
    662             return true;
    663 
    664         static $accessible_hosts = null;
    665         static $wildcard_regex = array();
    666         if ( null === $accessible_hosts ) {
    667             $accessible_hosts = preg_split('|,\s*|', WP_ACCESSIBLE_HOSTS);
    668 
    669             if ( false !== strpos(WP_ACCESSIBLE_HOSTS, '*') ) {
    670                 $wildcard_regex = array();
    671                 foreach ( $accessible_hosts as $host )
    672                     $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
    673                 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
    674             }
    675         }
    676 
    677         if ( !empty($wildcard_regex) )
    678             return !preg_match($wildcard_regex, $check['host']);
    679         else
    680             return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If it's in the array, then we can't access it.
    681 
    682     }
    683 
    684     /**
    685      * A wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7
    686      *
    687      * PHP 5.4.7 expanded parse_url()'s ability to handle non-absolute url's, including
    688      * schemeless and relative url's with :// in the path, this works around those
    689      * limitations providing a standard output on PHP 5.2~5.4+.
    690      *
    691      * Error suppression is used as prior to PHP 5.3.3, an E_WARNING would be generated
    692      * when URL parsing failed.
    693      *
    694      * @since 4.1.0
    695      *
    696      * @static
    697      * @access protected
    698      *
    699      * @param string $url The URL to parse.
    700      * @return bool|array False on failure; Array of URL components on success;
    701      *                    See parse_url()'s return values.
    702      */
    703     protected static function parse_url( $url ) {
    704         $parts = @parse_url( $url );
    705         if ( ! $parts ) {
    706             // < PHP 5.4.7 compat, trouble with relative paths including a scheme break in the path
    707             if ( '/' == $url[0] && false !== strpos( $url, '://' ) ) {
    708                 // Since we know it's a relative path, prefix with a scheme/host placeholder and try again
    709                 if ( ! $parts = @parse_url( 'placeholder://placeholder' . $url ) ) {
    710                     return $parts;
    711                 }
    712                 // Remove the placeholder values
    713                 unset( $parts['scheme'], $parts['host'] );
    714             } else {
    715                 return $parts;
    716             }
    717         }
    718 
    719         // < PHP 5.4.7 compat, doesn't detect schemeless URL's host field
    720         if ( '//' == substr( $url, 0, 2 ) && ! isset( $parts['host'] ) ) {
    721             list( $parts['host'], $slashless_path ) = explode( '/', substr( $parts['path'], 2 ), 2 );
    722             $parts['path'] = "/{$slashless_path}";
    723         }
    724 
    725         return $parts;
    726     }
    727 
    728     /**
    729      * Converts a relative URL to an absolute URL relative to a given URL.
    730      *
    731      * If an Absolute URL is provided, no processing of that URL is done.
    732      *
    733      * @since 3.4.0
    734      *
    735      * @static
    736      * @access public
    737      *
    738      * @param string $maybe_relative_path The URL which might be relative
    739      * @param string $url                 The URL which $maybe_relative_path is relative to
    740      * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned.
    741      */
    742     public static function make_absolute_url( $maybe_relative_path, $url ) {
    743         if ( empty( $url ) )
    744             return $maybe_relative_path;
    745 
    746         if ( ! $url_parts = WP_HTTP::parse_url( $url ) ) {
    747             return $maybe_relative_path;
    748         }
    749 
    750         if ( ! $relative_url_parts = WP_HTTP::parse_url( $maybe_relative_path ) ) {
    751             return $maybe_relative_path;
    752         }
    753 
    754         // Check for a scheme on the 'relative' url
    755         if ( ! empty( $relative_url_parts['scheme'] ) ) {
    756             return $maybe_relative_path;
    757         }
    758 
    759         $absolute_path = $url_parts['scheme'] . '://';
    760 
    761         // Schemeless URL's will make it this far, so we check for a host in the relative url and convert it to a protocol-url
    762         if ( isset( $relative_url_parts['host'] ) ) {
    763             $absolute_path .= $relative_url_parts['host'];
    764             if ( isset( $relative_url_parts['port'] ) )
    765                 $absolute_path .= ':' . $relative_url_parts['port'];
    766         } else {
    767             $absolute_path .= $url_parts['host'];
    768             if ( isset( $url_parts['port'] ) )
    769                 $absolute_path .= ':' . $url_parts['port'];
    770         }
    771 
    772         // Start off with the Absolute URL path.
    773         $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/';
    774 
    775         // If it's a root-relative path, then great.
    776         if ( ! empty( $relative_url_parts['path'] ) && '/' == $relative_url_parts['path'][0] ) {
    777             $path = $relative_url_parts['path'];
    778 
    779         // Else it's a relative path.
    780         } elseif ( ! empty( $relative_url_parts['path'] ) ) {
    781             // Strip off any file components from the absolute path.
    782             $path = substr( $path, 0, strrpos( $path, '/' ) + 1 );
    783 
    784             // Build the new path.
    785             $path .= $relative_url_parts['path'];
    786 
    787             // Strip all /path/../ out of the path.
    788             while ( strpos( $path, '../' ) > 1 ) {
    789                 $path = preg_replace( '![^/]+/\.\./!', '', $path );
    790             }
    791 
    792             // Strip any final leading ../ from the path.
    793             $path = preg_replace( '!^/(\.\./)+!', '', $path );
    794         }
    795 
    796         // Add the Query string.
    797         if ( ! empty( $relative_url_parts['query'] ) )
    798             $path .= '?' . $relative_url_parts['query'];
    799 
    800         return $absolute_path . '/' . ltrim( $path, '/' );
    801     }
    802 
    803     /**
    804      * Handles HTTP Redirects and follows them if appropriate.
    805      *
    806      * @since 3.7.0
    807      *
    808      * @static
    809      *
    810      * @param string $url The URL which was requested.
    811      * @param array $args The Arguments which were used to make the request.
    812      * @param array $response The Response of the HTTP request.
    813      * @return false|object False if no redirect is present, a WP_HTTP or WP_Error result otherwise.
    814      */
    815     public static function handle_redirects( $url, $args, $response ) {
    816         // If no redirects are present, or, redirects were not requested, perform no action.
    817         if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] )
    818             return false;
    819 
    820         // Only perform redirections on redirection http codes.
    821         if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 )
    822             return false;
    823 
    824         // Don't redirect if we've run out of redirects.
    825         if ( $args['redirection']-- <= 0 )
    826             return new WP_Error( 'http_request_failed', __('Too many redirects.') );
    827 
    828         $redirect_location = $response['headers']['location'];
    829 
    830         // If there were multiple Location headers, use the last header specified.
    831         if ( is_array( $redirect_location ) )
    832             $redirect_location = array_pop( $redirect_location );
    833 
    834         $redirect_location = WP_HTTP::make_absolute_url( $redirect_location, $url );
    835 
    836         // POST requests should not POST to a redirected location.
    837         if ( 'POST' == $args['method'] ) {
    838             if ( in_array( $response['response']['code'], array( 302, 303 ) ) )
    839                 $args['method'] = 'GET';
    840         }
    841 
    842         // Include valid cookies in the redirect process.
    843         if ( ! empty( $response['cookies'] ) ) {
    844             foreach ( $response['cookies'] as $cookie ) {
    845                 if ( $cookie->test( $redirect_location ) )
    846                     $args['cookies'][] = $cookie;
    847             }
    848         }
    849 
    850         return wp_remote_request( $redirect_location, $args );
    851     }
    852 
    853     /**
    854      * Determines if a specified string represents an IP address or not.
    855      *
    856      * This function also detects the type of the IP address, returning either
    857      * '4' or '6' to represent a IPv4 and IPv6 address respectively.
    858      * This does not verify if the IP is a valid IP, only that it appears to be
    859      * an IP address.
    860      *
    861      * @see http://home.deds.nl/~aeron/regex/ for IPv6 regex
    862      *
    863      * @since 3.7.0
    864      * @static
    865      *
    866      * @param string $maybe_ip A suspected IP address
    867      * @return integer|bool Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure
    868      */
    869     public static function is_ip_address( $maybe_ip ) {
    870         if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) )
    871             return 4;
    872 
    873         if ( false !== strpos( $maybe_ip, ':' ) && preg_match( '/^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i', trim( $maybe_ip, ' []' ) ) )
    874             return 6;
    875 
    876         return false;
    877     }
    878 
    879 }
    880 
    881 /**
    882  * HTTP request method uses PHP Streams to retrieve the url.
    883  *
    884  * @since 2.7.0
    885  * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
    886  */
    887 class WP_Http_Streams {
    888     /**
    889      * Send a HTTP request to a URI using PHP Streams.
    890      *
    891      * @see WP_Http::request For default options descriptions.
    892      *
    893      * @since 2.7.0
    894      * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
    895      *
    896      * @access public
    897      * @param string $url The request URL.
    898      * @param string|array $args Optional. Override the defaults.
    899      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    900      */
    901     public function request($url, $args = array()) {
    902         $defaults = array(
    903             'method' => 'GET', 'timeout' => 5,
    904             'redirection' => 5, 'httpversion' => '1.0',
    905             'blocking' => true,
    906             'headers' => array(), 'body' => null, 'cookies' => array()
    907         );
    908 
    909         $r = wp_parse_args( $args, $defaults );
    910 
    911         if ( isset( $r['headers']['User-Agent'] ) ) {
    912             $r['user-agent'] = $r['headers']['User-Agent'];
    913             unset( $r['headers']['User-Agent'] );
    914         } elseif ( isset( $r['headers']['user-agent'] ) ) {
    915             $r['user-agent'] = $r['headers']['user-agent'];
    916             unset( $r['headers']['user-agent'] );
    917         }
    918 
    919         // Construct Cookie: header if any cookies are set.
    920         WP_Http::buildCookieHeader( $r );
    921 
    922         $arrURL = parse_url($url);
    923 
    924         $connect_host = $arrURL['host'];
    925 
    926         $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' );
    927         if ( ! isset( $arrURL['port'] ) ) {
    928             if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) {
    929                 $arrURL['port'] = 443;
    930                 $secure_transport = true;
    931             } else {
    932                 $arrURL['port'] = 80;
    933             }
    934         }
    935 
    936         // Always pass a Path, defaulting to the root in cases such as http://example.com
    937         if ( ! isset( $arrURL['path'] ) ) {
    938             $arrURL['path'] = '/';
    939         }
    940 
    941         if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) {
    942             if ( isset( $r['headers']['Host'] ) )
    943                 $arrURL['host'] = $r['headers']['Host'];
    944             else
    945                 $arrURL['host'] = $r['headers']['host'];
    946             unset( $r['headers']['Host'], $r['headers']['host'] );
    947         }
    948 
    949         /*
    950          * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect
    951          * to ::1, which fails when the server is not set up for it. For compatibility, always
    952          * connect to the IPv4 address.
    953          */
    954         if ( 'localhost' == strtolower( $connect_host ) )
    955             $connect_host = '127.0.0.1';
    956 
    957         $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host;
    958 
    959         $is_local = isset( $r['local'] ) && $r['local'];
    960         $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify'];
    961         if ( $is_local ) {
    962             /**
    963              * Filter whether SSL should be verified for local requests.
    964              *
    965              * @since 2.8.0
    966              *
    967              * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
    968              */
    969             $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
    970         } elseif ( ! $is_local ) {
    971             /**
    972              * Filter whether SSL should be verified for non-local requests.
    973              *
    974              * @since 2.8.0
    975              *
    976              * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
    977              */
    978             $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
    979         }
    980 
    981         $proxy = new WP_HTTP_Proxy();
    982 
    983         $context = stream_context_create( array(
    984             'ssl' => array(
    985                 'verify_peer' => $ssl_verify,
    986                 //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate()
    987                 'capture_peer_cert' => $ssl_verify,
    988                 'SNI_enabled' => true,
    989                 'cafile' => $r['sslcertificates'],
    990                 'allow_self_signed' => ! $ssl_verify,
    991             )
    992         ) );
    993 
    994         $timeout = (int) floor( $r['timeout'] );
    995         $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
    996         $connect_timeout = max( $timeout, 1 );
    997 
    998         // Store error number.
    999         $connection_error = null;
    1000 
    1001         // Store error string.
    1002         $connection_error_str = null;
    1003 
    1004         if ( !WP_DEBUG ) {
    1005             // In the event that the SSL connection fails, silence the many PHP Warnings.
    1006             if ( $secure_transport )
    1007                 $error_reporting = error_reporting(0);
    1008 
    1009             if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
    1010                 $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    1011             else
    1012                 $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    1013 
    1014             if ( $secure_transport )
    1015                 error_reporting( $error_reporting );
    1016 
    1017         } else {
    1018             if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
    1019                 $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    1020             else
    1021                 $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    1022         }
    1023 
    1024         if ( false === $handle ) {
    1025             // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken.
    1026             if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str )
    1027                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
    1028 
    1029             return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str );
    1030         }
    1031 
    1032         // Verify that the SSL certificate is valid for this request.
    1033         if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) {
    1034             if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) )
    1035                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
    1036         }
    1037 
    1038         stream_set_timeout( $handle, $timeout, $utimeout );
    1039 
    1040         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
    1041             $requestPath = $url;
    1042         else
    1043             $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
    1044 
    1045         $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
    1046 
    1047         $include_port_in_host_header = (
    1048             ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) ||
    1049             ( 'http'  == $arrURL['scheme'] && 80  != $arrURL['port'] ) ||
    1050             ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] )
    1051         );
    1052 
    1053         if ( $include_port_in_host_header ) {
    1054             $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
    1055         } else {
    1056             $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
    1057         }
    1058 
    1059         if ( isset($r['user-agent']) )
    1060             $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
    1061 
    1062         if ( is_array($r['headers']) ) {
    1063             foreach ( (array) $r['headers'] as $header => $headerValue )
    1064                 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
    1065         } else {
    1066             $strHeaders .= $r['headers'];
    1067         }
    1068 
    1069         if ( $proxy->use_authentication() )
    1070             $strHeaders .= $proxy->authentication_header() . "\r\n";
    1071 
    1072         $strHeaders .= "\r\n";
    1073 
    1074         if ( ! is_null($r['body']) )
    1075             $strHeaders .= $r['body'];
    1076 
    1077         fwrite($handle, $strHeaders);
    1078 
    1079         if ( ! $r['blocking'] ) {
    1080             stream_set_blocking( $handle, 0 );
    1081             fclose( $handle );
    1082             return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    1083         }
    1084 
    1085         $strResponse = '';
    1086         $bodyStarted = false;
    1087         $keep_reading = true;
    1088         $block_size = 4096;
    1089         if ( isset( $r['limit_response_size'] ) )
    1090             $block_size = min( $block_size, $r['limit_response_size'] );
    1091 
    1092         // If streaming to a file setup the file handle.
    1093         if ( $r['stream'] ) {
    1094             if ( ! WP_DEBUG )
    1095                 $stream_handle = @fopen( $r['filename'], 'w+' );
    1096             else
    1097                 $stream_handle = fopen( $r['filename'], 'w+' );
    1098             if ( ! $stream_handle )
    1099                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
    1100 
    1101             $bytes_written = 0;
    1102             while ( ! feof($handle) && $keep_reading ) {
    1103                 $block = fread( $handle, $block_size );
    1104                 if ( ! $bodyStarted ) {
    1105                     $strResponse .= $block;
    1106                     if ( strpos( $strResponse, "\r\n\r\n" ) ) {
    1107                         $process = WP_Http::processResponse( $strResponse );
    1108                         $bodyStarted = true;
    1109                         $block = $process['body'];
    1110                         unset( $strResponse );
    1111                         $process['body'] = '';
    1112                     }
    1113                 }
    1114 
    1115                 $this_block_size = strlen( $block );
    1116 
    1117                 if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) {
    1118                     $this_block_size = ( $r['limit_response_size'] - $bytes_written );
    1119                     $block = substr( $block, 0, $this_block_size );
    1120                 }
    1121 
    1122                 $bytes_written_to_file = fwrite( $stream_handle, $block );
    1123 
    1124                 if ( $bytes_written_to_file != $this_block_size ) {
    1125                     fclose( $handle );
    1126                     fclose( $stream_handle );
    1127                     return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
    1128                 }
    1129 
    1130                 $bytes_written += $bytes_written_to_file;
    1131 
    1132                 $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size'];
    1133             }
    1134 
    1135             fclose( $stream_handle );
    1136 
    1137         } else {
    1138             $header_length = 0;
    1139             while ( ! feof( $handle ) && $keep_reading ) {
    1140                 $block = fread( $handle, $block_size );
    1141                 $strResponse .= $block;
    1142                 if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) {
    1143                     $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4;
    1144                     $bodyStarted = true;
    1145                 }
    1146                 $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) );
    1147             }
    1148 
    1149             $process = WP_Http::processResponse( $strResponse );
    1150             unset( $strResponse );
    1151 
    1152         }
    1153 
    1154         fclose( $handle );
    1155 
    1156         $arrHeaders = WP_Http::processHeaders( $process['headers'], $url );
    1157 
    1158         $response = array(
    1159             'headers' => $arrHeaders['headers'],
    1160             // Not yet processed.
    1161             'body' => null,
    1162             'response' => $arrHeaders['response'],
    1163             'cookies' => $arrHeaders['cookies'],
    1164             'filename' => $r['filename']
    1165         );
    1166 
    1167         // Handle redirects.
    1168         if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
    1169             return $redirect_response;
    1170 
    1171         // If the body was chunk encoded, then decode it.
    1172         if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
    1173             $process['body'] = WP_Http::chunkTransferDecode($process['body']);
    1174 
    1175         if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
    1176             $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
    1177 
    1178         if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] )
    1179             $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] );
    1180 
    1181         $response['body'] = $process['body'];
    1182 
    1183         return $response;
    1184     }
    1185 
    1186     /**
    1187      * Verifies the received SSL certificate against it's Common Names and subjectAltName fields
    1188      *
    1189      * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if
    1190      * the certificate is valid for the hostname which was requested.
    1191      * This function verifies the requested hostname against certificate's subjectAltName field,
    1192      * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used.
    1193      *
    1194      * IP Address support is included if the request is being made to an IP address.
    1195      *
    1196      * @since 3.7.0
    1197      * @static
    1198      *
    1199      * @param stream $stream The PHP Stream which the SSL request is being made over
    1200      * @param string $host The hostname being requested
    1201      * @return bool If the cerficiate presented in $stream is valid for $host
    1202      */
    1203     public static function verify_ssl_certificate( $stream, $host ) {
    1204         $context_options = stream_context_get_options( $stream );
    1205 
    1206         if ( empty( $context_options['ssl']['peer_certificate'] ) )
    1207             return false;
    1208 
    1209         $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] );
    1210         if ( ! $cert )
    1211             return false;
    1212 
    1213         /*
    1214          * If the request is being made to an IP address, we'll validate against IP fields
    1215          * in the cert (if they exist)
    1216          */
    1217         $host_type = ( WP_HTTP::is_ip_address( $host ) ? 'ip' : 'dns' );
    1218 
    1219         $certificate_hostnames = array();
    1220         if ( ! empty( $cert['extensions']['subjectAltName'] ) ) {
    1221             $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] );
    1222             foreach ( $match_against as $match ) {
    1223                 list( $match_type, $match_host ) = explode( ':', $match );
    1224                 if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS:
    1225                     $certificate_hostnames[] = strtolower( trim( $match_host ) );
    1226             }
    1227         } elseif ( !empty( $cert['subject']['CN'] ) ) {
    1228             // Only use the CN when the certificate includes no subjectAltName extension.
    1229             $certificate_hostnames[] = strtolower( $cert['subject']['CN'] );
    1230         }
    1231 
    1232         // Exact hostname/IP matches.
    1233         if ( in_array( strtolower( $host ), $certificate_hostnames ) )
    1234             return true;
    1235 
    1236         // IP's can't be wildcards, Stop processing.
    1237         if ( 'ip' == $host_type )
    1238             return false;
    1239 
    1240         // Test to see if the domain is at least 2 deep for wildcard support.
    1241         if ( substr_count( $host, '.' ) < 2 )
    1242             return false;
    1243 
    1244         // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com.
    1245         $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host );
    1246 
    1247         return in_array( strtolower( $wildcard_host ), $certificate_hostnames );
    1248     }
    1249 
    1250     /**
    1251      * Whether this class can be used for retrieving a URL.
    1252      *
    1253      * @static
    1254      * @access public
    1255      * @since 2.7.0
    1256      * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
    1257      *
    1258      * @return bool False means this class can not be used, true means it can.
    1259      */
    1260     public static function test( $args = array() ) {
    1261         if ( ! function_exists( 'stream_socket_client' ) )
    1262             return false;
    1263 
    1264         $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
    1265 
    1266         if ( $is_ssl ) {
    1267             if ( ! extension_loaded( 'openssl' ) )
    1268                 return false;
    1269             if ( ! function_exists( 'openssl_x509_parse' ) )
    1270                 return false;
    1271         }
    1272 
    1273         /**
    1274          * Filter whether streams can be used as a transport for retrieving a URL.
    1275          *
    1276          * @since 2.7.0
    1277          *
    1278          * @param bool  $use_class Whether the class can be used. Default true.
    1279          * @param array $args      Request arguments.
    1280          */
    1281         return apply_filters( 'use_streams_transport', true, $args );
    1282     }
    1283 }
    1284 
    1285 /**
    1286  * Deprecated HTTP Transport method which used fsockopen.
    1287  *
    1288  * This class is not used, and is included for backwards compatibility only.
    1289  * All code should make use of WP_HTTP directly through it's API.
    1290  *
    1291  * @see WP_HTTP::request
    1292  *
    1293  * @since 2.7.0
    1294  * @deprecated 3.7.0 Please use WP_HTTP::request() directly
    1295  */
    1296 class WP_HTTP_Fsockopen extends WP_HTTP_Streams {
    1297     // For backwards compatibility for users who are using the class directly.
    1298 }
    1299 
    1300 /**
    1301  * HTTP request method uses Curl extension to retrieve the url.
    1302  *
    1303  * Requires the Curl extension to be installed.
    1304  *
    1305  * @package WordPress
    1306  * @subpackage HTTP
    1307  * @since 2.7.0
    1308  */
    1309 class WP_Http_Curl {
    1310 
    1311     /**
    1312      * Temporary header storage for during requests.
    1313      *
    1314      * @since 3.2.0
    1315      * @access private
    1316      * @var string
    1317      */
    1318     private $headers = '';
    1319 
    1320     /**
    1321      * Temporary body storage for during requests.
    1322      *
    1323      * @since 3.6.0
    1324      * @access private
    1325      * @var string
    1326      */
    1327     private $body = '';
    1328 
    1329     /**
    1330      * The maximum amount of data to receive from the remote server.
    1331      *
    1332      * @since 3.6.0
    1333      * @access private
    1334      * @var int
    1335      */
    1336     private $max_body_length = false;
    1337 
    1338     /**
    1339      * The file resource used for streaming to file.
    1340      *
    1341      * @since 3.6.0
    1342      * @access private
    1343      * @var resource
    1344      */
    1345     private $stream_handle = false;
    1346 
    1347     /**
    1348      * The total bytes written in the current request.
    1349      *
    1350      * @since 4.1.0
    1351      * @access private
    1352      * @var int
    1353      */
    1354     private $bytes_written_total = 0;
    1355 
    1356     /**
    1357      * Send a HTTP request to a URI using cURL extension.
    1358      *
    1359      * @access public
    1360      * @since 2.7.0
    1361      *
    1362      * @param string $url The request URL.
    1363      * @param string|array $args Optional. Override the defaults.
    1364      * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    1365      */
    1366     public function request($url, $args = array()) {
    1367         $defaults = array(
    1368             'method' => 'GET', 'timeout' => 5,
    1369             'redirection' => 5, 'httpversion' => '1.0',
    1370             'blocking' => true,
    1371             'headers' => array(), 'body' => null, 'cookies' => array()
    1372         );
    1373 
    1374         $r = wp_parse_args( $args, $defaults );
    1375 
    1376         if ( isset( $r['headers']['User-Agent'] ) ) {
    1377             $r['user-agent'] = $r['headers']['User-Agent'];
    1378             unset( $r['headers']['User-Agent'] );
    1379         } elseif ( isset( $r['headers']['user-agent'] ) ) {
    1380             $r['user-agent'] = $r['headers']['user-agent'];
    1381             unset( $r['headers']['user-agent'] );
    1382         }
    1383 
    1384         // Construct Cookie: header if any cookies are set.
    1385         WP_Http::buildCookieHeader( $r );
    1386 
    1387         $handle = curl_init();
    1388 
    1389         // cURL offers really easy proxy support.
    1390         $proxy = new WP_HTTP_Proxy();
    1391 
    1392         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
    1393 
    1394             curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
    1395             curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
    1396             curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
    1397 
    1398             if ( $proxy->use_authentication() ) {
    1399                 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
    1400                 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
    1401             }
    1402         }
    1403 
    1404         $is_local = isset($r['local']) && $r['local'];
    1405         $ssl_verify = isset($r['sslverify']) && $r['sslverify'];
    1406         if ( $is_local ) {
    1407             /** This filter is documented in wp-includes/class-http.php */
    1408             $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
    1409         } elseif ( ! $is_local ) {
    1410             /** This filter is documented in wp-includes/class-http.php */
    1411             $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
    1412         }
    1413 
    1414         /*
    1415          * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since.
    1416          * a value of 0 will allow an unlimited timeout.
    1417          */
    1418         $timeout = (int) ceil( $r['timeout'] );
    1419         curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
    1420         curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
    1421 
    1422         curl_setopt( $handle, CURLOPT_URL, $url);
    1423         curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
    1424         curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
    1425         curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
    1426         curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] );
    1427         curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
    1428 
    1429         /*
    1430          * The option doesn't work with safe mode or when open_basedir is set, and there's
    1431          * a bug #17490 with redirected POST requests, so handle redirections outside Curl.
    1432          */
    1433         curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
    1434         if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4
    1435             curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
    1436 
    1437         switch ( $r['method'] ) {
    1438             case 'HEAD':
    1439                 curl_setopt( $handle, CURLOPT_NOBODY, true );
    1440                 break;
    1441             case 'POST':
    1442                 curl_setopt( $handle, CURLOPT_POST, true );
    1443                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
    1444                 break;
    1445             case 'PUT':
    1446                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
    1447                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
    1448                 break;
    1449             default:
    1450                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
    1451                 if ( ! is_null( $r['body'] ) )
    1452                     curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
    1453                 break;
    1454         }
    1455 
    1456         if ( true === $r['blocking'] ) {
    1457             curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
    1458             curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
    1459         }
    1460 
    1461         curl_setopt( $handle, CURLOPT_HEADER, false );
    1462 
    1463         if ( isset( $r['limit_response_size'] ) )
    1464             $this->max_body_length = intval( $r['limit_response_size'] );
    1465         else
    1466             $this->max_body_length = false;
    1467 
    1468         // If streaming to a file open a file handle, and setup our curl streaming handler.
    1469         if ( $r['stream'] ) {
    1470             if ( ! WP_DEBUG )
    1471                 $this->stream_handle = @fopen( $r['filename'], 'w+' );
    1472             else
    1473                 $this->stream_handle = fopen( $r['filename'], 'w+' );
    1474             if ( ! $this->stream_handle )
    1475                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
    1476         } else {
    1477             $this->stream_handle = false;
    1478         }
    1479 
    1480         if ( !empty( $r['headers'] ) ) {
    1481             // cURL expects full header strings in each element.
    1482             $headers = array();
    1483             foreach ( $r['headers'] as $name => $value ) {
    1484                 $headers[] = "{$name}: $value";
    1485             }
    1486             curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
    1487         }
    1488 
    1489         if ( $r['httpversion'] == '1.0' )
    1490             curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
    1491         else
    1492             curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
    1493 
    1494         /**
    1495          * Fires before the cURL request is executed.
    1496          *
    1497          * Cookies are not currently handled by the HTTP API. This action allows
    1498          * plugins to handle cookies themselves.
    1499          *
    1500          * @since 2.8.0
    1501          *
    1502          * @param resource &$handle The cURL handle returned by curl_init().
    1503          * @param array    $r       The HTTP request arguments.
    1504          * @param string   $url     The request URL.
    1505          */
    1506         do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) );
    1507 
    1508         // We don't need to return the body, so don't. Just execute request and return.
    1509         if ( ! $r['blocking'] ) {
    1510             curl_exec( $handle );
    1511 
    1512             if ( $curl_error = curl_error( $handle ) ) {
    1513                 curl_close( $handle );
    1514                 return new WP_Error( 'http_request_failed', $curl_error );
    1515             }
    1516             if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
    1517                 curl_close( $handle );
    1518                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
    1519             }
    1520 
    1521             curl_close( $handle );
    1522             return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    1523         }
    1524 
    1525         curl_exec( $handle );
    1526         $theHeaders = WP_Http::processHeaders( $this->headers, $url );
    1527         $theBody = $this->body;
    1528         $bytes_written_total = $this->bytes_written_total;
    1529 
    1530         $this->headers = '';
    1531         $this->body = '';
    1532         $this->bytes_written_total = 0;
    1533 
    1534         $curl_error = curl_errno( $handle );
    1535 
    1536         // If an error occurred, or, no response.
    1537         if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) {
    1538             if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) {
    1539                 if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) {
    1540                     if ( $r['stream'] ) {
    1541                         curl_close( $handle );
    1542                         fclose( $this->stream_handle );
    1543                         return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
    1544                     } else {
    1545                         curl_close( $handle );
    1546                         return new WP_Error( 'http_request_failed', curl_error( $handle ) );
    1547                     }
    1548                 }
    1549             } else {
    1550                 if ( $curl_error = curl_error( $handle ) ) {
    1551                     curl_close( $handle );
    1552                     return new WP_Error( 'http_request_failed', $curl_error );
    1553                 }
    1554             }
    1555             if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
    1556                 curl_close( $handle );
    1557                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
    1558             }
    1559         }
    1560 
    1561         curl_close( $handle );
    1562 
    1563         if ( $r['stream'] )
    1564             fclose( $this->stream_handle );
    1565 
    1566         $response = array(
    1567             'headers' => $theHeaders['headers'],
    1568             'body' => null,
    1569             'response' => $theHeaders['response'],
    1570             'cookies' => $theHeaders['cookies'],
    1571             'filename' => $r['filename']
    1572         );
    1573 
    1574         // Handle redirects.
    1575         if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
    1576             return $redirect_response;
    1577 
    1578         if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
    1579             $theBody = WP_Http_Encoding::decompress( $theBody );
    1580 
    1581         $response['body'] = $theBody;
    1582 
    1583         return $response;
    1584     }
    1585 
    1586     /**
    1587      * Grab the headers of the cURL request
    1588      *
    1589      * Each header is sent individually to this callback, so we append to the $header property for temporary storage
    1590      *
    1591      * @since 3.2.0
    1592      * @access private
    1593      * @return int
    1594      */
    1595     private function stream_headers( $handle, $headers ) {
    1596         $this->headers .= $headers;
    1597         return strlen( $headers );
    1598     }
    1599 
    1600     /**
    1601      * Grab the body of the cURL request
    1602      *
    1603      * The contents of the document are passed in chunks, so we append to the $body property for temporary storage.
    1604      * Returning a length shorter than the length of $data passed in will cause cURL to abort the request with CURLE_WRITE_ERROR
    1605      *
    1606      * @since 3.6.0
    1607      * @access private
    1608      * @return int
    1609      */
    1610     private function stream_body( $handle, $data ) {
    1611         $data_length = strlen( $data );
    1612 
    1613         if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) {
    1614             $data_length = ( $this->max_body_length - $this->bytes_written_total );
    1615             $data = substr( $data, 0, $data_length );
    1616         }
    1617 
    1618         if ( $this->stream_handle ) {
    1619             $bytes_written = fwrite( $this->stream_handle, $data );
    1620         } else {
    1621             $this->body .= $data;
    1622             $bytes_written = $data_length;
    1623         }
    1624 
    1625         $this->bytes_written_total += $bytes_written;
    1626 
    1627         // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR.
    1628         return $bytes_written;
    1629     }
    1630 
    1631     /**
    1632      * Whether this class can be used for retrieving an URL.
    1633      *
    1634      * @static
    1635      * @since 2.7.0
    1636      *
    1637      * @return bool False means this class can not be used, true means it can.
    1638      */
    1639     public static function test( $args = array() ) {
    1640         if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
    1641             return false;
    1642 
    1643         $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
    1644 
    1645         if ( $is_ssl ) {
    1646             $curl_version = curl_version();
    1647             // Check whether this cURL version support SSL requests.
    1648             if ( ! (CURL_VERSION_SSL & $curl_version['features']) )
    1649                 return false;
    1650         }
    1651 
    1652         /**
    1653          * Filter whether cURL can be used as a transport for retrieving a URL.
    1654          *
    1655          * @since 2.7.0
    1656          *
    1657          * @param bool  $use_class Whether the class can be used. Default true.
    1658          * @param array $args      An array of request arguments.
    1659          */
    1660         return apply_filters( 'use_curl_transport', true, $args );
    1661     }
    1662 }
    1663 
    1664 /**
    1665  * Adds Proxy support to the WordPress HTTP API.
    1666  *
    1667  * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to
    1668  * enable proxy support. There are also a few filters that plugins can hook into for some of the
    1669  * constants.
    1670  *
    1671  * Please note that only BASIC authentication is supported by most transports.
    1672  * cURL MAY support more methods (such as NTLM authentication) depending on your environment.
    1673  *
    1674  * The constants are as follows:
    1675  * <ol>
    1676  * <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li>
    1677  * <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li>
    1678  * <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li>
    1679  * <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li>
    1680  * <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy.
    1681  * You do not need to have localhost and the blog host in this list, because they will not be passed
    1682  * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org</li>
    1683  * </ol>
    1684  *
    1685  * An example can be as seen below.
    1686  *
    1687  *     define('WP_PROXY_HOST', '192.168.84.101');
    1688  *     define('WP_PROXY_PORT', '8080');
    1689  *     define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');
    1690  *
    1691  * @link https://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress.
    1692  * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS
    1693  * @since 2.8.0
    1694  */
    1695 class WP_HTTP_Proxy {
    1696 
    1697     /**
    1698      * Whether proxy connection should be used.
    1699      *
    1700      * @since 2.8.0
    1701      *
    1702      * @use WP_PROXY_HOST
    1703      * @use WP_PROXY_PORT
    1704      *
    1705      * @return bool
    1706      */
    1707     public function is_enabled() {
    1708         return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT');
    1709     }
    1710 
    1711     /**
    1712      * Whether authentication should be used.
    1713      *
    1714      * @since 2.8.0
    1715      *
    1716      * @use WP_PROXY_USERNAME
    1717      * @use WP_PROXY_PASSWORD
    1718      *
    1719      * @return bool
    1720      */
    1721     public function use_authentication() {
    1722         return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD');
    1723     }
    1724 
    1725     /**
    1726      * Retrieve the host for the proxy server.
    1727      *
    1728      * @since 2.8.0
    1729      *
    1730      * @return string
    1731      */
    1732     public function host() {
    1733         if ( defined('WP_PROXY_HOST') )
    1734             return WP_PROXY_HOST;
    1735 
    1736         return '';
    1737     }
    1738 
    1739     /**
    1740      * Retrieve the port for the proxy server.
    1741      *
    1742      * @since 2.8.0
    1743      *
    1744      * @return string
    1745      */
    1746     public function port() {
    1747         if ( defined('WP_PROXY_PORT') )
    1748             return WP_PROXY_PORT;
    1749 
    1750         return '';
    1751     }
    1752 
    1753     /**
    1754      * Retrieve the username for proxy authentication.
    1755      *
    1756      * @since 2.8.0
    1757      *
    1758      * @return string
    1759      */
    1760     public function username() {
    1761         if ( defined('WP_PROXY_USERNAME') )
    1762             return WP_PROXY_USERNAME;
    1763 
    1764         return '';
    1765     }
    1766 
    1767     /**
    1768      * Retrieve the password for proxy authentication.
    1769      *
    1770      * @since 2.8.0
    1771      *
    1772      * @return string
    1773      */
    1774     public function password() {
    1775         if ( defined('WP_PROXY_PASSWORD') )
    1776             return WP_PROXY_PASSWORD;
    1777 
    1778         return '';
    1779     }
    1780 
    1781     /**
    1782      * Retrieve authentication string for proxy authentication.
    1783      *
    1784      * @since 2.8.0
    1785      *
    1786      * @return string
    1787      */
    1788     public function authentication() {
    1789         return $this->username() . ':' . $this->password();
    1790     }
    1791 
    1792     /**
    1793      * Retrieve header string for proxy authentication.
    1794      *
    1795      * @since 2.8.0
    1796      *
    1797      * @return string
    1798      */
    1799     public function authentication_header() {
    1800         return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() );
    1801     }
    1802 
    1803     /**
    1804      * Whether URL should be sent through the proxy server.
    1805      *
    1806      * We want to keep localhost and the blog URL from being sent through the proxy server, because
    1807      * some proxies can not handle this. We also have the constant available for defining other
    1808      * hosts that won't be sent through the proxy.
    1809      *
    1810      * @since 2.8.0
    1811      *
    1812      * @staticvar array|null $bypass_hosts
    1813      * @staticvar array      $wildcard_regex
    1814      *
    1815      * @param string $uri URI to check.
    1816      * @return bool True, to send through the proxy and false if, the proxy should not be used.
    1817      */
    1818     public function send_through_proxy( $uri ) {
    1819         /*
    1820          * parse_url() only handles http, https type URLs, and will emit E_WARNING on failure.
    1821          * This will be displayed on blogs, which is not reasonable.
    1822          */
    1823         $check = @parse_url($uri);
    1824 
    1825         // Malformed URL, can not process, but this could mean ssl, so let through anyway.
    1826         if ( $check === false )
    1827             return true;
    1828 
    1829         $home = parse_url( get_option('siteurl') );
    1830 
    1831         /**
    1832          * Filter whether to preempt sending the request through the proxy server.
    1833          *
    1834          * Returning false will bypass the proxy; returning true will send
    1835          * the request through the proxy. Returning null bypasses the filter.
    1836          *
    1837          * @since 3.5.0
    1838          *
    1839          * @param null   $override Whether to override the request result. Default null.
    1840          * @param string $uri      URL to check.
    1841          * @param array  $check    Associative array result of parsing the URI.
    1842          * @param array  $home     Associative array result of parsing the site URL.
    1843          */
    1844         $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home );
    1845         if ( ! is_null( $result ) )
    1846             return $result;
    1847 
    1848         if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) )
    1849             return false;
    1850 
    1851         if ( !defined('WP_PROXY_BYPASS_HOSTS') )
    1852             return true;
    1853 
    1854         static $bypass_hosts = null;
    1855         static $wildcard_regex = array();
    1856         if ( null === $bypass_hosts ) {
    1857             $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS);
    1858 
    1859             if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) {
    1860                 $wildcard_regex = array();
    1861                 foreach ( $bypass_hosts as $host )
    1862                     $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
    1863                 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
    1864             }
    1865         }
    1866 
    1867         if ( !empty($wildcard_regex) )
    1868             return !preg_match($wildcard_regex, $check['host']);
    1869         else
    1870             return !in_array( $check['host'], $bypass_hosts );
    1871     }
    1872 }
    18732/**
    18743 * Internal representation of a single cookie.
     
    2083212    }
    2084213}
    2085 
    2086 /**
    2087  * Implementation for deflate and gzip transfer encodings.
    2088  *
    2089  * Includes RFC 1950, RFC 1951, and RFC 1952.
    2090  *
    2091  * @since 2.8.0
    2092  * @package WordPress
    2093  * @subpackage HTTP
    2094  */
    2095 class WP_Http_Encoding {
    2096 
    2097     /**
    2098      * Compress raw string using the deflate format.
    2099      *
    2100      * Supports the RFC 1951 standard.
    2101      *
    2102      * @since 2.8.0
    2103      *
    2104      * @static
    2105      *
    2106      * @param string $raw String to compress.
    2107      * @param int $level Optional, default is 9. Compression level, 9 is highest.
    2108      * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports.
    2109      * @return string|false False on failure.
    2110      */
    2111     public static function compress( $raw, $level = 9, $supports = null ) {
    2112         return gzdeflate( $raw, $level );
    2113     }
    2114 
    2115     /**
    2116      * Decompression of deflated string.
    2117      *
    2118      * Will attempt to decompress using the RFC 1950 standard, and if that fails
    2119      * then the RFC 1951 standard deflate will be attempted. Finally, the RFC
    2120      * 1952 standard gzip decode will be attempted. If all fail, then the
    2121      * original compressed string will be returned.
    2122      *
    2123      * @since 2.8.0
    2124      *
    2125      * @static
    2126      *
    2127      * @param string $compressed String to decompress.
    2128      * @param int $length The optional length of the compressed data.
    2129      * @return string|bool False on failure.
    2130      */
    2131     public static function decompress( $compressed, $length = null ) {
    2132 
    2133         if ( empty($compressed) )
    2134             return $compressed;
    2135 
    2136         if ( false !== ( $decompressed = @gzinflate( $compressed ) ) )
    2137             return $decompressed;
    2138 
    2139         if ( false !== ( $decompressed = self::compatible_gzinflate( $compressed ) ) )
    2140             return $decompressed;
    2141 
    2142         if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) )
    2143             return $decompressed;
    2144 
    2145         if ( function_exists('gzdecode') ) {
    2146             $decompressed = @gzdecode( $compressed );
    2147 
    2148             if ( false !== $decompressed )
    2149                 return $decompressed;
    2150         }
    2151 
    2152         return $compressed;
    2153     }
    2154 
    2155     /**
    2156      * Decompression of deflated string while staying compatible with the majority of servers.
    2157      *
    2158      * Certain Servers will return deflated data with headers which PHP's gzinflate()
    2159      * function cannot handle out of the box. The following function has been created from
    2160      * various snippets on the gzinflate() PHP documentation.
    2161      *
    2162      * Warning: Magic numbers within. Due to the potential different formats that the compressed
    2163      * data may be returned in, some "magic offsets" are needed to ensure proper decompression
    2164      * takes place. For a simple progmatic way to determine the magic offset in use, see:
    2165      * https://core.trac.wordpress.org/ticket/18273
    2166      *
    2167      * @since 2.8.1
    2168      * @link https://core.trac.wordpress.org/ticket/18273
    2169      * @link http://au2.php.net/manual/en/function.gzinflate.php#70875
    2170      * @link http://au2.php.net/manual/en/function.gzinflate.php#77336
    2171      *
    2172      * @static
    2173      *
    2174      * @param string $gzData String to decompress.
    2175      * @return string|bool False on failure.
    2176      */
    2177     public static function compatible_gzinflate($gzData) {
    2178 
    2179         // Compressed data might contain a full header, if so strip it for gzinflate().
    2180         if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
    2181             $i = 10;
    2182             $flg = ord( substr($gzData, 3, 1) );
    2183             if ( $flg > 0 ) {
    2184                 if ( $flg & 4 ) {
    2185                     list($xlen) = unpack('v', substr($gzData, $i, 2) );
    2186                     $i = $i + 2 + $xlen;
    2187                 }
    2188                 if ( $flg & 8 )
    2189                     $i = strpos($gzData, "\0", $i) + 1;
    2190                 if ( $flg & 16 )
    2191                     $i = strpos($gzData, "\0", $i) + 1;
    2192                 if ( $flg & 2 )
    2193                     $i = $i + 2;
    2194             }
    2195             $decompressed = @gzinflate( substr($gzData, $i, -8) );
    2196             if ( false !== $decompressed )
    2197                 return $decompressed;
    2198         }
    2199 
    2200         // Compressed data from java.util.zip.Deflater amongst others.
    2201         $decompressed = @gzinflate( substr($gzData, 2) );
    2202         if ( false !== $decompressed )
    2203             return $decompressed;
    2204 
    2205         return false;
    2206     }
    2207 
    2208     /**
    2209      * What encoding types to accept and their priority values.
    2210      *
    2211      * @since 2.8.0
    2212      *
    2213      * @static
    2214      *
    2215      * @param string $url
    2216      * @param array  $args
    2217      * @return string Types of encoding to accept.
    2218      */
    2219     public static function accept_encoding( $url, $args ) {
    2220         $type = array();
    2221         $compression_enabled = self::is_available();
    2222 
    2223         if ( ! $args['decompress'] ) // Decompression specifically disabled.
    2224             $compression_enabled = false;
    2225         elseif ( $args['stream'] ) // Disable when streaming to file.
    2226             $compression_enabled = false;
    2227         elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it.
    2228             $compression_enabled = false;
    2229 
    2230         if ( $compression_enabled ) {
    2231             if ( function_exists( 'gzinflate' ) )
    2232                 $type[] = 'deflate;q=1.0';
    2233 
    2234             if ( function_exists( 'gzuncompress' ) )
    2235                 $type[] = 'compress;q=0.5';
    2236 
    2237             if ( function_exists( 'gzdecode' ) )
    2238                 $type[] = 'gzip;q=0.5';
    2239         }
    2240 
    2241         /**
    2242          * Filter the allowed encoding types.
    2243          *
    2244          * @since 3.6.0
    2245          *
    2246          * @param array  $type Encoding types allowed. Accepts 'gzinflate',
    2247          *                     'gzuncompress', 'gzdecode'.
    2248          * @param string $url  URL of the HTTP request.
    2249          * @param array  $args HTTP request arguments.
    2250          */
    2251         $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args );
    2252 
    2253         return implode(', ', $type);
    2254     }
    2255 
    2256     /**
    2257      * What encoding the content used when it was compressed to send in the headers.
    2258      *
    2259      * @since 2.8.0
    2260      *
    2261      * @static
    2262      *
    2263      * @return string Content-Encoding string to send in the header.
    2264      */
    2265     public static function content_encoding() {
    2266         return 'deflate';
    2267     }
    2268 
    2269     /**
    2270      * Whether the content be decoded based on the headers.
    2271      *
    2272      * @since 2.8.0
    2273      *
    2274      * @static
    2275      *
    2276      * @param array|string $headers All of the available headers.
    2277      * @return bool
    2278      */
    2279     public static function should_decode($headers) {
    2280         if ( is_array( $headers ) ) {
    2281             if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) )
    2282                 return true;
    2283         } elseif ( is_string( $headers ) ) {
    2284             return ( stripos($headers, 'content-encoding:') !== false );
    2285         }
    2286 
    2287         return false;
    2288     }
    2289 
    2290     /**
    2291      * Whether decompression and compression are supported by the PHP version.
    2292      *
    2293      * Each function is tested instead of checking for the zlib extension, to
    2294      * ensure that the functions all exist in the PHP version and aren't
    2295      * disabled.
    2296      *
    2297      * @since 2.8.0
    2298      *
    2299      * @static
    2300      *
    2301      * @return bool
    2302      */
    2303     public static function is_available() {
    2304         return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') );
    2305     }
    2306 }
Note: See TracChangeset for help on using the changeset viewer.