WordPress.org

Make WordPress Core

Ticket #37705: 37705.diff

File 37705.diff, 50.4 KB (added by dd32, 3 years ago)
  • src/wp-includes/class-http.php

    class WP_Http { 
    453453         * @static
    454454         *
    455455         * @param string            $location URL to redirect to.
    456456         * @param array             $headers  Headers for the redirect.
    457457         * @param array             $options  Redirect request options.
    458458         * @param Requests_Response $original Response object.
    459459         */
    460460        public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) {
    461461                // Browser compat
    462462                if ( $original->status_code === 302 ) {
    463463                        $options['type'] = Requests::GET;
    464464                }
    465465        }
    466466
    467467        /**
    468          * Tests which transports are capable of supporting the request.
    469          *
    470          * @since 3.2.0
    471          * @access public
    472          *
    473          * @param array $args Request arguments
    474          * @param string $url URL to Request
    475          *
    476          * @return string|false Class name for the first transport that claims to support the request. False if no transport claims to support the request.
    477          */
    478         public function _get_first_available_transport( $args, $url = null ) {
    479                 $transports = array( 'curl', 'streams' );
    480 
    481                 /**
    482                  * Filters which HTTP transports are available and in what order.
    483                  *
    484                  * @since 3.7.0
    485                  *
    486                  * @param array  $transports Array of HTTP transports to check. Default array contains
    487                  *                           'curl', and 'streams', in that order.
    488                  * @param array  $args       HTTP request arguments.
    489                  * @param string $url        The URL to request.
    490                  */
    491                 $request_order = apply_filters( 'http_api_transports', $transports, $args, $url );
    492 
    493                 // Loop over each transport on each HTTP request looking for one which will serve this request's needs.
    494                 foreach ( $request_order as $transport ) {
    495                         if ( in_array( $transport, $transports ) ) {
    496                                 $transport = ucfirst( $transport );
    497                         }
    498                         $class = 'WP_Http_' . $transport;
    499 
    500                         // Check to see if this transport is a possibility, calls the transport statically.
    501                         if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
    502                                 continue;
    503 
    504                         return $class;
    505                 }
    506 
    507                 return false;
    508         }
    509 
    510         /**
    511          * Dispatches a HTTP request to a supporting transport.
    512          *
    513          * Tests each transport in order to find a transport which matches the request arguments.
    514          * Also caches the transport instance to be used later.
    515          *
    516          * The order for requests is cURL, and then PHP Streams.
    517          *
    518          * @since 3.2.0
    519          *
    520          * @static
    521          * @access private
    522          *
    523          * @param string $url URL to Request
    524          * @param array $args Request arguments
    525          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    526          */
    527         private function _dispatch_request( $url, $args ) {
    528                 static $transports = array();
    529 
    530                 $class = $this->_get_first_available_transport( $args, $url );
    531                 if ( !$class )
    532                         return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
    533 
    534                 // Transport claims to support request, instantiate it and give it a whirl.
    535                 if ( empty( $transports[$class] ) )
    536                         $transports[$class] = new $class;
    537 
    538                 $response = $transports[$class]->request( $url, $args );
    539 
    540                 /** This action is documented in wp-includes/class-http.php */
    541                 do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
    542 
    543                 if ( is_wp_error( $response ) )
    544                         return $response;
    545 
    546                 /**
    547                  * Filters the HTTP API response immediately before the response is returned.
    548                  *
    549                  * @since 2.9.0
    550                  *
    551                  * @param array  $response HTTP response.
    552                  * @param array  $args     HTTP request arguments.
    553                  * @param string $url      The request URL.
    554                  */
    555                 return apply_filters( 'http_response', $response, $args, $url );
    556         }
    557 
    558         /**
    559468         * Uses the POST HTTP method.
    560469         *
    561470         * Used for sending data that is expected to be in the body.
    562471         *
    563472         * @access public
    564473         * @since 2.7.0
    565474         *
    566475         * @param string       $url  The request URL.
    567476         * @param string|array $args Optional. Override the defaults.
    568477         * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    569478         */
    570479        public function post($url, $args = array()) {
    571480                $defaults = array('method' => 'POST');
    572481                $r = wp_parse_args( $args, $defaults );
    573482                return $this->request($url, $r);
    class WP_Http { 
    598507         *
    599508         * @access public
    600509         * @since 2.7.0
    601510         *
    602511         * @param string $url The request URL.
    603512         * @param string|array $args Optional. Override the defaults.
    604513         * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    605514         */
    606515        public function head($url, $args = array()) {
    607516                $defaults = array('method' => 'HEAD');
    608517                $r = wp_parse_args( $args, $defaults );
    609518                return $this->request($url, $r);
    610519        }
    611520
    612521        /**
    613          * Parses the responses and splits the parts into headers and body.
    614          *
    615          * @access public
    616          * @static
    617          * @since 2.7.0
    618          *
    619          * @param string $strResponse The full response string
    620          * @return array Array with 'headers' and 'body' keys.
    621          */
    622         public static function processResponse($strResponse) {
    623                 $res = explode("\r\n\r\n", $strResponse, 2);
    624 
    625                 return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
    626         }
    627 
    628         /**
    629522         * Transform header string into an array.
    630523         *
    631524         * If an array is given then it is assumed to be raw header data with numeric keys with the
    632525         * headers as the values. No headers must be passed that were already processed.
    633526         *
    634527         * @access public
    635528         * @static
    636529         * @since 2.7.0
    637530         *
    638531         * @param string|array $headers
    639532         * @param string $url The URL that was requested
    640533         * @return array Processed string headers. If duplicate headers are encountered,
    641534         *                                      Then a numbered array is returned as the value of that header-key.
    642535         */
    643536        public static function processHeaders( $headers, $url = '' ) {
    class WP_Http { 
    691584                                $newheaders[ $key ][] = $value;
    692585                        } else {
    693586                                $newheaders[ $key ] = $value;
    694587                        }
    695588                        if ( 'set-cookie' == $key )
    696589                                $cookies[] = new WP_Http_Cookie( $value, $url );
    697590                }
    698591
    699592                // Cast the Response Code to an int
    700593                $response['code'] = intval( $response['code'] );
    701594
    702595                return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
    703596        }
    704597
    705598        /**
    706          * Takes the arguments for a ::request() and checks for the cookie array.
    707          *
    708          * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances,
    709          * which are each parsed into strings and added to the Cookie: header (within the arguments array).
    710          * Edits the array by reference.
    711          *
    712          * @access public
    713          * @version 2.8.0
    714          * @static
    715          *
    716          * @param array $r Full array of args passed into ::request()
    717          */
    718         public static function buildCookieHeader( &$r ) {
    719                 if ( ! empty($r['cookies']) ) {
    720                         // Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances.
    721                         foreach ( $r['cookies'] as $name => $value ) {
    722                                 if ( ! is_object( $value ) )
    723                                         $r['cookies'][ $name ] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) );
    724                         }
    725 
    726                         $cookies_header = '';
    727                         foreach ( (array) $r['cookies'] as $cookie ) {
    728                                 $cookies_header .= $cookie->getHeaderValue() . '; ';
    729                         }
    730 
    731                         $cookies_header = substr( $cookies_header, 0, -2 );
    732                         $r['headers']['cookie'] = $cookies_header;
    733                 }
    734         }
    735 
    736         /**
    737          * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
    738          *
    739          * Based off the HTTP http_encoding_dechunk function.
    740          *
    741          * @link https://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
    742          *
    743          * @access public
    744          * @since 2.7.0
    745          * @static
    746          *
    747          * @param string $body Body content
    748          * @return string Chunked decoded body on success or raw body on failure.
    749          */
    750         public static function chunkTransferDecode( $body ) {
    751                 // The body is not chunked encoded or is malformed.
    752                 if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) )
    753                         return $body;
    754 
    755                 $parsed_body = '';
    756 
    757                 // We'll be altering $body, so need a backup in case of error.
    758                 $body_original = $body;
    759 
    760                 while ( true ) {
    761                         $has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match );
    762                         if ( ! $has_chunk || empty( $match[1] ) )
    763                                 return $body_original;
    764 
    765                         $length = hexdec( $match[1] );
    766                         $chunk_length = strlen( $match[0] );
    767 
    768                         // Parse out the chunk of data.
    769                         $parsed_body .= substr( $body, $chunk_length, $length );
    770 
    771                         // Remove the chunk from the raw data.
    772                         $body = substr( $body, $length + $chunk_length );
    773 
    774                         // End of the document.
    775                         if ( '0' === trim( $body ) )
    776                                 return $parsed_body;
    777                 }
    778         }
    779 
    780         /**
    781599         * Block requests through the proxy.
    782600         *
    783601         * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
    784602         * prevent plugins from working and core functionality, if you don't include api.wordpress.org.
    785603         *
    786604         * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php
    787605         * file and this will only allow localhost and your site to make requests. The constant
    788606         * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the
    789607         * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains
    790608         * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted.
    791609         *
    792610         * @since 2.8.0
    793611         * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
    794612         * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
    795613         *
    class WP_Http { 
    835653                                $wildcard_regex = array();
    836654                                foreach ( $accessible_hosts as $host )
    837655                                        $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
    838656                                $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
    839657                        }
    840658                }
    841659
    842660                if ( !empty($wildcard_regex) )
    843661                        return !preg_match($wildcard_regex, $check['host']);
    844662                else
    845663                        return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If it's in the array, then we can't access it.
    846664
    847665        }
    848666
    849667        /**
    850          * Used as a wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7.
    851          *
    852          * @access protected
    853          * @deprecated 4.4.0 Use wp_parse_url()
    854          * @see wp_parse_url()
    855          *
    856          * @param string $url The URL to parse.
    857          * @return bool|array False on failure; Array of URL components on success;
    858          *                    See parse_url()'s return values.
    859          */
    860         protected static function parse_url( $url ) {
    861                 _deprecated_function( __METHOD__, '4.4.0', 'wp_parse_url()' );
    862                 return wp_parse_url( $url );
    863         }
    864 
    865         /**
    866668         * Converts a relative URL to an absolute URL relative to a given URL.
    867669         *
    868670         * If an Absolute URL is provided, no processing of that URL is done.
    869671         *
    870672         * @since 3.4.0
    871673         *
    872674         * @static
    873675         * @access public
    874676         *
    875677         * @param string $maybe_relative_path The URL which might be relative
    876678         * @param string $url                 The URL which $maybe_relative_path is relative to
    877679         * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned.
    878680         */
    879681        public static function make_absolute_url( $maybe_relative_path, $url ) {
    880682                if ( empty( $url ) )
    class WP_Http { 
    925727                        while ( strpos( $path, '../' ) > 1 ) {
    926728                                $path = preg_replace( '![^/]+/\.\./!', '', $path );
    927729                        }
    928730
    929731                        // Strip any final leading ../ from the path.
    930732                        $path = preg_replace( '!^/(\.\./)+!', '', $path );
    931733                }
    932734
    933735                // Add the Query string.
    934736                if ( ! empty( $relative_url_parts['query'] ) )
    935737                        $path .= '?' . $relative_url_parts['query'];
    936738
    937739                return $absolute_path . '/' . ltrim( $path, '/' );
    938740        }
    939741
    940         /**
    941          * Handles HTTP Redirects and follows them if appropriate.
    942          *
    943          * @since 3.7.0
    944          *
    945          * @static
    946          *
    947          * @param string $url The URL which was requested.
    948          * @param array $args The Arguments which were used to make the request.
    949          * @param array $response The Response of the HTTP request.
    950          * @return false|object False if no redirect is present, a WP_HTTP or WP_Error result otherwise.
    951          */
    952         public static function handle_redirects( $url, $args, $response ) {
    953                 // If no redirects are present, or, redirects were not requested, perform no action.
    954                 if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] )
    955                         return false;
    956 
    957                 // Only perform redirections on redirection http codes.
    958                 if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 )
    959                         return false;
    960 
    961                 // Don't redirect if we've run out of redirects.
    962                 if ( $args['redirection']-- <= 0 )
    963                         return new WP_Error( 'http_request_failed', __('Too many redirects.') );
    964 
    965                 $redirect_location = $response['headers']['location'];
    966 
    967                 // If there were multiple Location headers, use the last header specified.
    968                 if ( is_array( $redirect_location ) )
    969                         $redirect_location = array_pop( $redirect_location );
    970 
    971                 $redirect_location = WP_Http::make_absolute_url( $redirect_location, $url );
    972 
    973                 // POST requests should not POST to a redirected location.
    974                 if ( 'POST' == $args['method'] ) {
    975                         if ( in_array( $response['response']['code'], array( 302, 303 ) ) )
    976                                 $args['method'] = 'GET';
    977                 }
    978 
    979                 // Include valid cookies in the redirect process.
    980                 if ( ! empty( $response['cookies'] ) ) {
    981                         foreach ( $response['cookies'] as $cookie ) {
    982                                 if ( $cookie->test( $redirect_location ) )
    983                                         $args['cookies'][] = $cookie;
    984                         }
    985                 }
    986 
    987                 return wp_remote_request( $redirect_location, $args );
    988         }
    989 
    990         /**
    991          * Determines if a specified string represents an IP address or not.
    992          *
    993          * This function also detects the type of the IP address, returning either
    994          * '4' or '6' to represent a IPv4 and IPv6 address respectively.
    995          * This does not verify if the IP is a valid IP, only that it appears to be
    996          * an IP address.
    997          *
    998          * @link http://home.deds.nl/~aeron/regex/ for IPv6 regex
    999          *
    1000          * @since 3.7.0
    1001          * @static
    1002          *
    1003          * @param string $maybe_ip A suspected IP address
    1004          * @return integer|bool Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure
    1005          */
    1006         public static function is_ip_address( $maybe_ip ) {
    1007                 if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) )
    1008                         return 4;
    1009 
    1010                 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, ' []' ) ) )
    1011                         return 6;
    1012 
    1013                 return false;
    1014         }
    1015 
    1016742}
  • src/wp-settings.php

    require( ABSPATH . WPINC . '/cron.php' ) 
    184184require( ABSPATH . WPINC . '/deprecated.php' );
    185185require( ABSPATH . WPINC . '/script-loader.php' );
    186186require( ABSPATH . WPINC . '/taxonomy.php' );
    187187require( ABSPATH . WPINC . '/class-wp-term.php' );
    188188require( ABSPATH . WPINC . '/class-wp-term-query.php' );
    189189require( ABSPATH . WPINC . '/class-wp-tax-query.php' );
    190190require( ABSPATH . WPINC . '/update.php' );
    191191require( ABSPATH . WPINC . '/canonical.php' );
    192192require( ABSPATH . WPINC . '/shortcodes.php' );
    193193require( ABSPATH . WPINC . '/embed.php' );
    194194require( ABSPATH . WPINC . '/class-wp-embed.php' );
    195195require( ABSPATH . WPINC . '/class-wp-oembed-controller.php' );
    196196require( ABSPATH . WPINC . '/media.php' );
    197197require( ABSPATH . WPINC . '/http.php' );
    198198require( ABSPATH . WPINC . '/class-http.php' );
    199 require( ABSPATH . WPINC . '/class-wp-http-streams.php' );
    200 require( ABSPATH . WPINC . '/class-wp-http-curl.php' );
    201199require( ABSPATH . WPINC . '/class-wp-http-proxy.php' );
    202200require( ABSPATH . WPINC . '/class-wp-http-cookie.php' );
    203 require( ABSPATH . WPINC . '/class-wp-http-encoding.php' );
    204201require( ABSPATH . WPINC . '/class-wp-http-response.php' );
    205202require( ABSPATH . WPINC . '/class-wp-http-requests-response.php' );
    206203require( ABSPATH . WPINC . '/widgets.php' );
    207204require( ABSPATH . WPINC . '/class-wp-widget.php' );
    208205require( ABSPATH . WPINC . '/class-wp-widget-factory.php' );
    209206require( ABSPATH . WPINC . '/nav-menu.php' );
    210207require( ABSPATH . WPINC . '/nav-menu-template.php' );
    211208require( ABSPATH . WPINC . '/admin-bar.php' );
    212209require( ABSPATH . WPINC . '/rest-api.php' );
    213210require( ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' );
    214211require( ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' );
    215212require( ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' );
    216213
    217214// Load multisite-specific files.
    218215if ( is_multisite() ) {
  • src/wp-includes/class-wp-http-curl.php

     
    1 <?php
    2 /**
    3  * HTTP API: WP_Http_Curl class
    4  *
    5  * @package WordPress
    6  * @subpackage HTTP
    7  * @since 4.4.0
    8  */
    9 
    10 /**
    11  * Core class used to integrate Curl as an HTTP transport.
    12  *
    13  * HTTP request method uses Curl extension to retrieve the url.
    14  *
    15  * Requires the Curl extension to be installed.
    16  *
    17  * @since 2.7.0
    18  */
    19 class WP_Http_Curl {
    20 
    21         /**
    22          * Temporary header storage for during requests.
    23          *
    24          * @since 3.2.0
    25          * @access private
    26          * @var string
    27          */
    28         private $headers = '';
    29 
    30         /**
    31          * Temporary body storage for during requests.
    32          *
    33          * @since 3.6.0
    34          * @access private
    35          * @var string
    36          */
    37         private $body = '';
    38 
    39         /**
    40          * The maximum amount of data to receive from the remote server.
    41          *
    42          * @since 3.6.0
    43          * @access private
    44          * @var int
    45          */
    46         private $max_body_length = false;
    47 
    48         /**
    49          * The file resource used for streaming to file.
    50          *
    51          * @since 3.6.0
    52          * @access private
    53          * @var resource
    54          */
    55         private $stream_handle = false;
    56 
    57         /**
    58          * The total bytes written in the current request.
    59          *
    60          * @since 4.1.0
    61          * @access private
    62          * @var int
    63          */
    64         private $bytes_written_total = 0;
    65 
    66         /**
    67          * Send a HTTP request to a URI using cURL extension.
    68          *
    69          * @access public
    70          * @since 2.7.0
    71          *
    72          * @param string $url The request URL.
    73          * @param string|array $args Optional. Override the defaults.
    74          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    75          */
    76         public function request($url, $args = array()) {
    77                 $defaults = array(
    78                         'method' => 'GET', 'timeout' => 5,
    79                         'redirection' => 5, 'httpversion' => '1.0',
    80                         'blocking' => true,
    81                         'headers' => array(), 'body' => null, 'cookies' => array()
    82                 );
    83 
    84                 $r = wp_parse_args( $args, $defaults );
    85 
    86                 if ( isset( $r['headers']['User-Agent'] ) ) {
    87                         $r['user-agent'] = $r['headers']['User-Agent'];
    88                         unset( $r['headers']['User-Agent'] );
    89                 } elseif ( isset( $r['headers']['user-agent'] ) ) {
    90                         $r['user-agent'] = $r['headers']['user-agent'];
    91                         unset( $r['headers']['user-agent'] );
    92                 }
    93 
    94                 // Construct Cookie: header if any cookies are set.
    95                 WP_Http::buildCookieHeader( $r );
    96 
    97                 $handle = curl_init();
    98 
    99                 // cURL offers really easy proxy support.
    100                 $proxy = new WP_HTTP_Proxy();
    101 
    102                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
    103 
    104                         curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
    105                         curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
    106                         curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
    107 
    108                         if ( $proxy->use_authentication() ) {
    109                                 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
    110                                 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
    111                         }
    112                 }
    113 
    114                 $is_local = isset($r['local']) && $r['local'];
    115                 $ssl_verify = isset($r['sslverify']) && $r['sslverify'];
    116                 if ( $is_local ) {
    117                         /** This filter is documented in wp-includes/class-wp-http-streams.php */
    118                         $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
    119                 } elseif ( ! $is_local ) {
    120                         /** This filter is documented in wp-includes/class-wp-http-streams.php */
    121                         $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
    122                 }
    123 
    124                 /*
    125                  * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since.
    126                  * a value of 0 will allow an unlimited timeout.
    127                  */
    128                 $timeout = (int) ceil( $r['timeout'] );
    129                 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
    130                 curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
    131 
    132                 curl_setopt( $handle, CURLOPT_URL, $url);
    133                 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
    134                 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
    135                 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
    136 
    137                 if ( $ssl_verify ) {
    138                         curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] );
    139                 }
    140 
    141                 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
    142 
    143                 /*
    144                  * The option doesn't work with safe mode or when open_basedir is set, and there's
    145                  * a bug #17490 with redirected POST requests, so handle redirections outside Curl.
    146                  */
    147                 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
    148                 if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4
    149                         curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
    150 
    151                 switch ( $r['method'] ) {
    152                         case 'HEAD':
    153                                 curl_setopt( $handle, CURLOPT_NOBODY, true );
    154                                 break;
    155                         case 'POST':
    156                                 curl_setopt( $handle, CURLOPT_POST, true );
    157                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
    158                                 break;
    159                         case 'PUT':
    160                                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
    161                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
    162                                 break;
    163                         default:
    164                                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
    165                                 if ( ! is_null( $r['body'] ) )
    166                                         curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
    167                                 break;
    168                 }
    169 
    170                 if ( true === $r['blocking'] ) {
    171                         curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
    172                         curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
    173                 }
    174 
    175                 curl_setopt( $handle, CURLOPT_HEADER, false );
    176 
    177                 if ( isset( $r['limit_response_size'] ) )
    178                         $this->max_body_length = intval( $r['limit_response_size'] );
    179                 else
    180                         $this->max_body_length = false;
    181 
    182                 // If streaming to a file open a file handle, and setup our curl streaming handler.
    183                 if ( $r['stream'] ) {
    184                         if ( ! WP_DEBUG )
    185                                 $this->stream_handle = @fopen( $r['filename'], 'w+' );
    186                         else
    187                                 $this->stream_handle = fopen( $r['filename'], 'w+' );
    188                         if ( ! $this->stream_handle )
    189                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
    190                 } else {
    191                         $this->stream_handle = false;
    192                 }
    193 
    194                 if ( !empty( $r['headers'] ) ) {
    195                         // cURL expects full header strings in each element.
    196                         $headers = array();
    197                         foreach ( $r['headers'] as $name => $value ) {
    198                                 $headers[] = "{$name}: $value";
    199                         }
    200                         curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
    201                 }
    202 
    203                 if ( $r['httpversion'] == '1.0' )
    204                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
    205                 else
    206                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
    207 
    208                 /**
    209                  * Fires before the cURL request is executed.
    210                  *
    211                  * Cookies are not currently handled by the HTTP API. This action allows
    212                  * plugins to handle cookies themselves.
    213                  *
    214                  * @since 2.8.0
    215                  *
    216                  * @param resource &$handle The cURL handle returned by curl_init().
    217                  * @param array    $r       The HTTP request arguments.
    218                  * @param string   $url     The request URL.
    219                  */
    220                 do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) );
    221 
    222                 // We don't need to return the body, so don't. Just execute request and return.
    223                 if ( ! $r['blocking'] ) {
    224                         curl_exec( $handle );
    225 
    226                         if ( $curl_error = curl_error( $handle ) ) {
    227                                 curl_close( $handle );
    228                                 return new WP_Error( 'http_request_failed', $curl_error );
    229                         }
    230                         if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
    231                                 curl_close( $handle );
    232                                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
    233                         }
    234 
    235                         curl_close( $handle );
    236                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    237                 }
    238 
    239                 curl_exec( $handle );
    240                 $theHeaders = WP_Http::processHeaders( $this->headers, $url );
    241                 $theBody = $this->body;
    242                 $bytes_written_total = $this->bytes_written_total;
    243 
    244                 $this->headers = '';
    245                 $this->body = '';
    246                 $this->bytes_written_total = 0;
    247 
    248                 $curl_error = curl_errno( $handle );
    249 
    250                 // If an error occurred, or, no response.
    251                 if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) {
    252                         if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) {
    253                                 if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) {
    254                                         if ( $r['stream'] ) {
    255                                                 curl_close( $handle );
    256                                                 fclose( $this->stream_handle );
    257                                                 return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
    258                                         } else {
    259                                                 curl_close( $handle );
    260                                                 return new WP_Error( 'http_request_failed', curl_error( $handle ) );
    261                                         }
    262                                 }
    263                         } else {
    264                                 if ( $curl_error = curl_error( $handle ) ) {
    265                                         curl_close( $handle );
    266                                         return new WP_Error( 'http_request_failed', $curl_error );
    267                                 }
    268                         }
    269                         if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
    270                                 curl_close( $handle );
    271                                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
    272                         }
    273                 }
    274 
    275                 curl_close( $handle );
    276 
    277                 if ( $r['stream'] )
    278                         fclose( $this->stream_handle );
    279 
    280                 $response = array(
    281                         'headers' => $theHeaders['headers'],
    282                         'body' => null,
    283                         'response' => $theHeaders['response'],
    284                         'cookies' => $theHeaders['cookies'],
    285                         'filename' => $r['filename']
    286                 );
    287 
    288                 // Handle redirects.
    289                 if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
    290                         return $redirect_response;
    291 
    292                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
    293                         $theBody = WP_Http_Encoding::decompress( $theBody );
    294 
    295                 $response['body'] = $theBody;
    296 
    297                 return $response;
    298         }
    299 
    300         /**
    301          * Grabs the headers of the cURL request.
    302          *
    303          * Each header is sent individually to this callback, so we append to the `$header` property
    304          * for temporary storage
    305          *
    306          * @since 3.2.0
    307          * @access private
    308          *
    309          * @param resource $handle  cURL handle.
    310          * @param string   $headers cURL request headers.
    311          * @return int Length of the request headers.
    312          */
    313         private function stream_headers( $handle, $headers ) {
    314                 $this->headers .= $headers;
    315                 return strlen( $headers );
    316         }
    317 
    318         /**
    319          * Grabs the body of the cURL request.
    320          *
    321          * The contents of the document are passed in chunks, so we append to the `$body`
    322          * property for temporary storage. Returning a length shorter than the length of
    323          * `$data` passed in will cause cURL to abort the request with `CURLE_WRITE_ERROR`.
    324          *
    325          * @since 3.6.0
    326          * @access private
    327          *
    328          * @param resource $handle  cURL handle.
    329          * @param string   $data    cURL request body.
    330          * @return int Total bytes of data written.
    331          */
    332         private function stream_body( $handle, $data ) {
    333                 $data_length = strlen( $data );
    334 
    335                 if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) {
    336                         $data_length = ( $this->max_body_length - $this->bytes_written_total );
    337                         $data = substr( $data, 0, $data_length );
    338                 }
    339 
    340                 if ( $this->stream_handle ) {
    341                         $bytes_written = fwrite( $this->stream_handle, $data );
    342                 } else {
    343                         $this->body .= $data;
    344                         $bytes_written = $data_length;
    345                 }
    346 
    347                 $this->bytes_written_total += $bytes_written;
    348 
    349                 // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR.
    350                 return $bytes_written;
    351         }
    352 
    353         /**
    354          * Determines whether this class can be used for retrieving a URL.
    355          *
    356          * @static
    357          * @since 2.7.0
    358          *
    359          * @param array $args Optional. Array of request arguments. Default empty array.
    360          * @return bool False means this class can not be used, true means it can.
    361          */
    362         public static function test( $args = array() ) {
    363                 if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
    364                         return false;
    365 
    366                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
    367 
    368                 if ( $is_ssl ) {
    369                         $curl_version = curl_version();
    370                         // Check whether this cURL version support SSL requests.
    371                         if ( ! (CURL_VERSION_SSL & $curl_version['features']) )
    372                                 return false;
    373                 }
    374 
    375                 /**
    376                  * Filters whether cURL can be used as a transport for retrieving a URL.
    377                  *
    378                  * @since 2.7.0
    379                  *
    380                  * @param bool  $use_class Whether the class can be used. Default true.
    381                  * @param array $args      An array of request arguments.
    382                  */
    383                 return apply_filters( 'use_curl_transport', true, $args );
    384         }
    385 }
  • src/wp-includes/class-wp-http-streams.php

     
    1 <?php
    2 /**
    3  * HTTP API: WP_Http_Streams class
    4  *
    5  * @package WordPress
    6  * @subpackage HTTP
    7  * @since 4.4.0
    8  */
    9 
    10 /**
    11  * Core class used to integrate PHP Streams as an HTTP transport.
    12  *
    13  * @since 2.7.0
    14  * @since 3.7.0 Combined with the fsockopen transport and switched to `stream_socket_client()`.
    15  */
    16 class WP_Http_Streams {
    17         /**
    18          * Send a HTTP request to a URI using PHP Streams.
    19          *
    20          * @see WP_Http::request For default options descriptions.
    21          *
    22          * @since 2.7.0
    23          * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
    24          *
    25          * @access public
    26          * @param string $url The request URL.
    27          * @param string|array $args Optional. Override the defaults.
    28          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    29          */
    30         public function request($url, $args = array()) {
    31                 $defaults = array(
    32                         'method' => 'GET', 'timeout' => 5,
    33                         'redirection' => 5, 'httpversion' => '1.0',
    34                         'blocking' => true,
    35                         'headers' => array(), 'body' => null, 'cookies' => array()
    36                 );
    37 
    38                 $r = wp_parse_args( $args, $defaults );
    39 
    40                 if ( isset( $r['headers']['User-Agent'] ) ) {
    41                         $r['user-agent'] = $r['headers']['User-Agent'];
    42                         unset( $r['headers']['User-Agent'] );
    43                 } elseif ( isset( $r['headers']['user-agent'] ) ) {
    44                         $r['user-agent'] = $r['headers']['user-agent'];
    45                         unset( $r['headers']['user-agent'] );
    46                 }
    47 
    48                 // Construct Cookie: header if any cookies are set.
    49                 WP_Http::buildCookieHeader( $r );
    50 
    51                 $arrURL = parse_url($url);
    52 
    53                 $connect_host = $arrURL['host'];
    54 
    55                 $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' );
    56                 if ( ! isset( $arrURL['port'] ) ) {
    57                         if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) {
    58                                 $arrURL['port'] = 443;
    59                                 $secure_transport = true;
    60                         } else {
    61                                 $arrURL['port'] = 80;
    62                         }
    63                 }
    64 
    65                 // Always pass a Path, defaulting to the root in cases such as http://example.com
    66                 if ( ! isset( $arrURL['path'] ) ) {
    67                         $arrURL['path'] = '/';
    68                 }
    69 
    70                 if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) {
    71                         if ( isset( $r['headers']['Host'] ) )
    72                                 $arrURL['host'] = $r['headers']['Host'];
    73                         else
    74                                 $arrURL['host'] = $r['headers']['host'];
    75                         unset( $r['headers']['Host'], $r['headers']['host'] );
    76                 }
    77 
    78                 /*
    79                  * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect
    80                  * to ::1, which fails when the server is not set up for it. For compatibility, always
    81                  * connect to the IPv4 address.
    82                  */
    83                 if ( 'localhost' == strtolower( $connect_host ) )
    84                         $connect_host = '127.0.0.1';
    85 
    86                 $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host;
    87 
    88                 $is_local = isset( $r['local'] ) && $r['local'];
    89                 $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify'];
    90                 if ( $is_local ) {
    91                         /**
    92                          * Filters whether SSL should be verified for local requests.
    93                          *
    94                          * @since 2.8.0
    95                          *
    96                          * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
    97                          */
    98                         $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
    99                 } elseif ( ! $is_local ) {
    100                         /**
    101                          * Filters whether SSL should be verified for non-local requests.
    102                          *
    103                          * @since 2.8.0
    104                          *
    105                          * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
    106                          */
    107                         $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
    108                 }
    109 
    110                 $proxy = new WP_HTTP_Proxy();
    111 
    112                 $context = stream_context_create( array(
    113                         'ssl' => array(
    114                                 'verify_peer' => $ssl_verify,
    115                                 //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate()
    116                                 'capture_peer_cert' => $ssl_verify,
    117                                 'SNI_enabled' => true,
    118                                 'cafile' => $r['sslcertificates'],
    119                                 'allow_self_signed' => ! $ssl_verify,
    120                         )
    121                 ) );
    122 
    123                 $timeout = (int) floor( $r['timeout'] );
    124                 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
    125                 $connect_timeout = max( $timeout, 1 );
    126 
    127                 // Store error number.
    128                 $connection_error = null;
    129 
    130                 // Store error string.
    131                 $connection_error_str = null;
    132 
    133                 if ( !WP_DEBUG ) {
    134                         // In the event that the SSL connection fails, silence the many PHP Warnings.
    135                         if ( $secure_transport )
    136                                 $error_reporting = error_reporting(0);
    137 
    138                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
    139                                 $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    140                         else
    141                                 $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    142 
    143                         if ( $secure_transport )
    144                                 error_reporting( $error_reporting );
    145 
    146                 } else {
    147                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
    148                                 $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    149                         else
    150                                 $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    151                 }
    152 
    153                 if ( false === $handle ) {
    154                         // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken.
    155                         if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str )
    156                                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
    157 
    158                         return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str );
    159                 }
    160 
    161                 // Verify that the SSL certificate is valid for this request.
    162                 if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) {
    163                         if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) )
    164                                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
    165                 }
    166 
    167                 stream_set_timeout( $handle, $timeout, $utimeout );
    168 
    169                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
    170                         $requestPath = $url;
    171                 else
    172                         $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
    173 
    174                 $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
    175 
    176                 $include_port_in_host_header = (
    177                         ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) ||
    178                         ( 'http'  == $arrURL['scheme'] && 80  != $arrURL['port'] ) ||
    179                         ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] )
    180                 );
    181 
    182                 if ( $include_port_in_host_header ) {
    183                         $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
    184                 } else {
    185                         $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
    186                 }
    187 
    188                 if ( isset($r['user-agent']) )
    189                         $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
    190 
    191                 if ( is_array($r['headers']) ) {
    192                         foreach ( (array) $r['headers'] as $header => $headerValue )
    193                                 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
    194                 } else {
    195                         $strHeaders .= $r['headers'];
    196                 }
    197 
    198                 if ( $proxy->use_authentication() )
    199                         $strHeaders .= $proxy->authentication_header() . "\r\n";
    200 
    201                 $strHeaders .= "\r\n";
    202 
    203                 if ( ! is_null($r['body']) )
    204                         $strHeaders .= $r['body'];
    205 
    206                 fwrite($handle, $strHeaders);
    207 
    208                 if ( ! $r['blocking'] ) {
    209                         stream_set_blocking( $handle, 0 );
    210                         fclose( $handle );
    211                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    212                 }
    213 
    214                 $strResponse = '';
    215                 $bodyStarted = false;
    216                 $keep_reading = true;
    217                 $block_size = 4096;
    218                 if ( isset( $r['limit_response_size'] ) )
    219                         $block_size = min( $block_size, $r['limit_response_size'] );
    220 
    221                 // If streaming to a file setup the file handle.
    222                 if ( $r['stream'] ) {
    223                         if ( ! WP_DEBUG )
    224                                 $stream_handle = @fopen( $r['filename'], 'w+' );
    225                         else
    226                                 $stream_handle = fopen( $r['filename'], 'w+' );
    227                         if ( ! $stream_handle )
    228                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
    229 
    230                         $bytes_written = 0;
    231                         while ( ! feof($handle) && $keep_reading ) {
    232                                 $block = fread( $handle, $block_size );
    233                                 if ( ! $bodyStarted ) {
    234                                         $strResponse .= $block;
    235                                         if ( strpos( $strResponse, "\r\n\r\n" ) ) {
    236                                                 $process = WP_Http::processResponse( $strResponse );
    237                                                 $bodyStarted = true;
    238                                                 $block = $process['body'];
    239                                                 unset( $strResponse );
    240                                                 $process['body'] = '';
    241                                         }
    242                                 }
    243 
    244                                 $this_block_size = strlen( $block );
    245 
    246                                 if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) {
    247                                         $this_block_size = ( $r['limit_response_size'] - $bytes_written );
    248                                         $block = substr( $block, 0, $this_block_size );
    249                                 }
    250 
    251                                 $bytes_written_to_file = fwrite( $stream_handle, $block );
    252 
    253                                 if ( $bytes_written_to_file != $this_block_size ) {
    254                                         fclose( $handle );
    255                                         fclose( $stream_handle );
    256                                         return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
    257                                 }
    258 
    259                                 $bytes_written += $bytes_written_to_file;
    260 
    261                                 $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size'];
    262                         }
    263 
    264                         fclose( $stream_handle );
    265 
    266                 } else {
    267                         $header_length = 0;
    268                         while ( ! feof( $handle ) && $keep_reading ) {
    269                                 $block = fread( $handle, $block_size );
    270                                 $strResponse .= $block;
    271                                 if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) {
    272                                         $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4;
    273                                         $bodyStarted = true;
    274                                 }
    275                                 $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) );
    276                         }
    277 
    278                         $process = WP_Http::processResponse( $strResponse );
    279                         unset( $strResponse );
    280 
    281                 }
    282 
    283                 fclose( $handle );
    284 
    285                 $arrHeaders = WP_Http::processHeaders( $process['headers'], $url );
    286 
    287                 $response = array(
    288                         'headers' => $arrHeaders['headers'],
    289                         // Not yet processed.
    290                         'body' => null,
    291                         'response' => $arrHeaders['response'],
    292                         'cookies' => $arrHeaders['cookies'],
    293                         'filename' => $r['filename']
    294                 );
    295 
    296                 // Handle redirects.
    297                 if ( false !== ( $redirect_response = WP_Http::handle_redirects( $url, $r, $response ) ) )
    298                         return $redirect_response;
    299 
    300                 // If the body was chunk encoded, then decode it.
    301                 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
    302                         $process['body'] = WP_Http::chunkTransferDecode($process['body']);
    303 
    304                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
    305                         $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
    306 
    307                 if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] )
    308                         $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] );
    309 
    310                 $response['body'] = $process['body'];
    311 
    312                 return $response;
    313         }
    314 
    315         /**
    316          * Verifies the received SSL certificate against its Common Names and subjectAltName fields.
    317          *
    318          * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if
    319          * the certificate is valid for the hostname which was requested.
    320          * This function verifies the requested hostname against certificate's subjectAltName field,
    321          * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used.
    322          *
    323          * IP Address support is included if the request is being made to an IP address.
    324          *
    325          * @since 3.7.0
    326          * @static
    327          *
    328          * @param stream $stream The PHP Stream which the SSL request is being made over
    329          * @param string $host The hostname being requested
    330          * @return bool If the cerficiate presented in $stream is valid for $host
    331          */
    332         public static function verify_ssl_certificate( $stream, $host ) {
    333                 $context_options = stream_context_get_options( $stream );
    334 
    335                 if ( empty( $context_options['ssl']['peer_certificate'] ) )
    336                         return false;
    337 
    338                 $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] );
    339                 if ( ! $cert )
    340                         return false;
    341 
    342                 /*
    343                  * If the request is being made to an IP address, we'll validate against IP fields
    344                  * in the cert (if they exist)
    345                  */
    346                 $host_type = ( WP_Http::is_ip_address( $host ) ? 'ip' : 'dns' );
    347 
    348                 $certificate_hostnames = array();
    349                 if ( ! empty( $cert['extensions']['subjectAltName'] ) ) {
    350                         $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] );
    351                         foreach ( $match_against as $match ) {
    352                                 list( $match_type, $match_host ) = explode( ':', $match );
    353                                 if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS:
    354                                         $certificate_hostnames[] = strtolower( trim( $match_host ) );
    355                         }
    356                 } elseif ( !empty( $cert['subject']['CN'] ) ) {
    357                         // Only use the CN when the certificate includes no subjectAltName extension.
    358                         $certificate_hostnames[] = strtolower( $cert['subject']['CN'] );
    359                 }
    360 
    361                 // Exact hostname/IP matches.
    362                 if ( in_array( strtolower( $host ), $certificate_hostnames ) )
    363                         return true;
    364 
    365                 // IP's can't be wildcards, Stop processing.
    366                 if ( 'ip' == $host_type )
    367                         return false;
    368 
    369                 // Test to see if the domain is at least 2 deep for wildcard support.
    370                 if ( substr_count( $host, '.' ) < 2 )
    371                         return false;
    372 
    373                 // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com.
    374                 $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host );
    375 
    376                 return in_array( strtolower( $wildcard_host ), $certificate_hostnames );
    377         }
    378 
    379         /**
    380          * Determines whether this class can be used for retrieving a URL.
    381          *
    382          * @static
    383          * @access public
    384          * @since 2.7.0
    385          * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
    386          *
    387          * @param array $args Optional. Array of request arguments. Default empty array.
    388          * @return bool False means this class can not be used, true means it can.
    389          */
    390         public static function test( $args = array() ) {
    391                 if ( ! function_exists( 'stream_socket_client' ) )
    392                         return false;
    393 
    394                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
    395 
    396                 if ( $is_ssl ) {
    397                         if ( ! extension_loaded( 'openssl' ) )
    398                                 return false;
    399                         if ( ! function_exists( 'openssl_x509_parse' ) )
    400                                 return false;
    401                 }
    402 
    403                 /**
    404                  * Filters whether streams can be used as a transport for retrieving a URL.
    405                  *
    406                  * @since 2.7.0
    407                  *
    408                  * @param bool  $use_class Whether the class can be used. Default true.
    409                  * @param array $args      Request arguments.
    410                  */
    411                 return apply_filters( 'use_streams_transport', true, $args );
    412         }
    413 }
    414 
    415 /**
    416  * Deprecated HTTP Transport method which used fsockopen.
    417  *
    418  * This class is not used, and is included for backward compatibility only.
    419  * All code should make use of WP_Http directly through its API.
    420  *
    421  * @see WP_HTTP::request
    422  *
    423  * @since 2.7.0
    424  * @deprecated 3.7.0 Please use WP_HTTP::request() directly
    425  */
    426 class WP_HTTP_Fsockopen extends WP_HTTP_Streams {
    427         // For backward compatibility for users who are using the class directly.
    428 }
  • src/wp-includes/class-wp-http-encoding.php

     
    1 <?php
    2 /**
    3  * HTTP API: WP_Http_Encoding class
    4  *
    5  * @package WordPress
    6  * @subpackage HTTP
    7  * @since 4.4.0
    8  */
    9 
    10 /**
    11  * Core class used to implement deflate and gzip transfer encoding support for HTTP requests.
    12  *
    13  * Includes RFC 1950, RFC 1951, and RFC 1952.
    14  *
    15  * @since 2.8.0
    16  */
    17 class WP_Http_Encoding {
    18 
    19         /**
    20          * Compress raw string using the deflate format.
    21          *
    22          * Supports the RFC 1951 standard.
    23          *
    24          * @since 2.8.0
    25          *
    26          * @static
    27          *
    28          * @param string $raw String to compress.
    29          * @param int $level Optional, default is 9. Compression level, 9 is highest.
    30          * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports.
    31          * @return string|false False on failure.
    32          */
    33         public static function compress( $raw, $level = 9, $supports = null ) {
    34                 return gzdeflate( $raw, $level );
    35         }
    36 
    37         /**
    38          * Decompression of deflated string.
    39          *
    40          * Will attempt to decompress using the RFC 1950 standard, and if that fails
    41          * then the RFC 1951 standard deflate will be attempted. Finally, the RFC
    42          * 1952 standard gzip decode will be attempted. If all fail, then the
    43          * original compressed string will be returned.
    44          *
    45          * @since 2.8.0
    46          *
    47          * @static
    48          *
    49          * @param string $compressed String to decompress.
    50          * @param int $length The optional length of the compressed data.
    51          * @return string|bool False on failure.
    52          */
    53         public static function decompress( $compressed, $length = null ) {
    54 
    55                 if ( empty($compressed) )
    56                         return $compressed;
    57 
    58                 if ( false !== ( $decompressed = @gzinflate( $compressed ) ) )
    59                         return $decompressed;
    60 
    61                 if ( false !== ( $decompressed = self::compatible_gzinflate( $compressed ) ) )
    62                         return $decompressed;
    63 
    64                 if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) )
    65                         return $decompressed;
    66 
    67                 if ( function_exists('gzdecode') ) {
    68                         $decompressed = @gzdecode( $compressed );
    69 
    70                         if ( false !== $decompressed )
    71                                 return $decompressed;
    72                 }
    73 
    74                 return $compressed;
    75         }
    76 
    77         /**
    78          * Decompression of deflated string while staying compatible with the majority of servers.
    79          *
    80          * Certain Servers will return deflated data with headers which PHP's gzinflate()
    81          * function cannot handle out of the box. The following function has been created from
    82          * various snippets on the gzinflate() PHP documentation.
    83          *
    84          * Warning: Magic numbers within. Due to the potential different formats that the compressed
    85          * data may be returned in, some "magic offsets" are needed to ensure proper decompression
    86          * takes place. For a simple progmatic way to determine the magic offset in use, see:
    87          * https://core.trac.wordpress.org/ticket/18273
    88          *
    89          * @since 2.8.1
    90          * @link https://core.trac.wordpress.org/ticket/18273
    91          * @link https://secure.php.net/manual/en/function.gzinflate.php#70875
    92          * @link https://secure.php.net/manual/en/function.gzinflate.php#77336
    93          *
    94          * @static
    95          *
    96          * @param string $gzData String to decompress.
    97          * @return string|bool False on failure.
    98          */
    99         public static function compatible_gzinflate($gzData) {
    100 
    101                 // Compressed data might contain a full header, if so strip it for gzinflate().
    102                 if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
    103                         $i = 10;
    104                         $flg = ord( substr($gzData, 3, 1) );
    105                         if ( $flg > 0 ) {
    106                                 if ( $flg & 4 ) {
    107                                         list($xlen) = unpack('v', substr($gzData, $i, 2) );
    108                                         $i = $i + 2 + $xlen;
    109                                 }
    110                                 if ( $flg & 8 )
    111                                         $i = strpos($gzData, "\0", $i) + 1;
    112                                 if ( $flg & 16 )
    113                                         $i = strpos($gzData, "\0", $i) + 1;
    114                                 if ( $flg & 2 )
    115                                         $i = $i + 2;
    116                         }
    117                         $decompressed = @gzinflate( substr($gzData, $i, -8) );
    118                         if ( false !== $decompressed )
    119                                 return $decompressed;
    120                 }
    121 
    122                 // Compressed data from java.util.zip.Deflater amongst others.
    123                 $decompressed = @gzinflate( substr($gzData, 2) );
    124                 if ( false !== $decompressed )
    125                         return $decompressed;
    126 
    127                 return false;
    128         }
    129 
    130         /**
    131          * What encoding types to accept and their priority values.
    132          *
    133          * @since 2.8.0
    134          *
    135          * @static
    136          *
    137          * @param string $url
    138          * @param array  $args
    139          * @return string Types of encoding to accept.
    140          */
    141         public static function accept_encoding( $url, $args ) {
    142                 $type = array();
    143                 $compression_enabled = self::is_available();
    144 
    145                 if ( ! $args['decompress'] ) // Decompression specifically disabled.
    146                         $compression_enabled = false;
    147                 elseif ( $args['stream'] ) // Disable when streaming to file.
    148                         $compression_enabled = false;
    149                 elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it.
    150                         $compression_enabled = false;
    151 
    152                 if ( $compression_enabled ) {
    153                         if ( function_exists( 'gzinflate' ) )
    154                                 $type[] = 'deflate;q=1.0';
    155 
    156                         if ( function_exists( 'gzuncompress' ) )
    157                                 $type[] = 'compress;q=0.5';
    158 
    159                         if ( function_exists( 'gzdecode' ) )
    160                                 $type[] = 'gzip;q=0.5';
    161                 }
    162 
    163                 /**
    164                  * Filters the allowed encoding types.
    165                  *
    166                  * @since 3.6.0
    167                  *
    168                  * @param array  $type Encoding types allowed. Accepts 'gzinflate',
    169                  *                     'gzuncompress', 'gzdecode'.
    170                  * @param string $url  URL of the HTTP request.
    171                  * @param array  $args HTTP request arguments.
    172                  */
    173                 $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args );
    174 
    175                 return implode(', ', $type);
    176         }
    177 
    178         /**
    179          * What encoding the content used when it was compressed to send in the headers.
    180          *
    181          * @since 2.8.0
    182          *
    183          * @static
    184          *
    185          * @return string Content-Encoding string to send in the header.
    186          */
    187         public static function content_encoding() {
    188                 return 'deflate';
    189         }
    190 
    191         /**
    192          * Whether the content be decoded based on the headers.
    193          *
    194          * @since 2.8.0
    195          *
    196          * @static
    197          *
    198          * @param array|string $headers All of the available headers.
    199          * @return bool
    200          */
    201         public static function should_decode($headers) {
    202                 if ( is_array( $headers ) ) {
    203                         if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) )
    204                                 return true;
    205                 } elseif ( is_string( $headers ) ) {
    206                         return ( stripos($headers, 'content-encoding:') !== false );
    207                 }
    208 
    209                 return false;
    210         }
    211 
    212         /**
    213          * Whether decompression and compression are supported by the PHP version.
    214          *
    215          * Each function is tested instead of checking for the zlib extension, to
    216          * ensure that the functions all exist in the PHP version and aren't
    217          * disabled.
    218          *
    219          * @since 2.8.0
    220          *
    221          * @static
    222          *
    223          * @return bool
    224          */
    225         public static function is_available() {
    226                 return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') );
    227         }
    228 }