Make WordPress Core

Ticket #36280: 36280.patch

File 36280.patch, 18.8 KB (added by bobbywalters, 8 years ago)

First patch.

  • wp-includes/class-wp-http-streams.php

    diff --git wp-includes/class-wp-http-streams.php wp-includes/class-wp-http-streams.php
    index 80f30a4..d2d6b52 100644
     
    1515 */
    1616class WP_Http_Streams {
    1717        /**
     18         * Read the HTTP response body (if any) and updates $response.
     19         *
     20         * @uses WP_Http_Encoding::should_decode() Decides when it's
     21         * appropriate to decode (ie decompress, inflate, etc) the response
     22         * body.
     23         *
     24         * @since 4.5.0
     25         *
     26         * @param string $url The request URL.
     27         * @param array $args The arguments supplied to modify the request and
     28         * response handling.
     29         * @param resource $handle The stream socket client resource.
     30         * @param array &$response The response results as the return value. The
     31         * response results were started by read_headers and this method adds
     32         * the body information.
     33         * @throws WP_Error if the response was supposed to be streamed to a
     34         * temporary file and was unable to
     35         */
     36        protected function read_body( $url, $args, $handle, &$response ) {
     37                $filters = null;
     38                $has_body = false;
     39                $headers = $response['headers'];
     40
     41                if ( isset( $headers['content-length'] ) ) {
     42                        $has_body = 0 < intval( $headers['content-length'] );
     43                } elseif ( isset( $headers['transfer-encoding'] ) && 'chunked' === $headers['transfer-encoding'] ) {
     44                        $filters[] = stream_filter_append( $handle, 'dechunk' );
     45                        $has_body = true;
     46                } elseif ( isset( $headers['connection'] ) && 'close' === $headers['connection'] ) {
     47                        $has_body = true;
     48                }
     49
     50                $response['body'] = '';
     51
     52                if ( $has_body ) {
     53                        if ( true === $args['decompress'] && WP_Http_Encoding::should_decode( $headers ) ) {
     54                                // The window size is: 15 (max zlib) + 16 for bare minimal gzip header.
     55                                $filters[] = stream_filter_append( $handle, 'zlib.inflate', STREAM_FILTER_READ, [ 'window' => 31 ] );
     56                        }
     57
     58                        // Block size used for all reads.
     59                        $block_size = 4096;
     60                        $limit = isset( $args['limit_response_size'] ) ? $args['limit_response_size'] : null;
     61
     62                        if ( $args['stream'] ) {
     63                                $file = WP_DEBUG ? fopen( $args['filename'], 'w+' ) : @fopen( $args['filename'], 'w+' );
     64                                if ( ! $file ) {
     65                                        fclose( $handle );
     66
     67                                        return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $args['filename'] ) );
     68                                }
     69
     70                                if ( null !== $limit ) {
     71                                        while ( 0 < $limit && ! feof( $handle ) ) {
     72                                                $block = stream_get_contents( $handle, min( $limit, $block_size ) );
     73                                                $limit -= strlen( $block );
     74                                                fwrite( $file, $block );
     75                                        }
     76                                } else {
     77                                        while ( ! feof( $handle ) ) {
     78                                                fwrite( $file, stream_get_contents( $handle, $block_size ) );
     79                                        }
     80                                }
     81
     82                                if ( ! fclose( $file ) ) {
     83                                        fclose( $handle );
     84
     85                                        return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
     86                                }
     87
     88                                $response['filename'] = $args['filename'];
     89                        } elseif ( null !== $limit ) {
     90                                while ( 0 < $limit && ! feof( $handle ) ) {
     91                                        $block = stream_get_contents( $handle, min( $limit, $block_size ) );
     92                                        $limit -= strlen( $block );
     93                                        $response['body'] .= $block;
     94                                }
     95                        } else {
     96                                while ( ! feof( $handle ) ) {
     97                                        $response['body'] .= stream_get_contents( $handle, $block_size );
     98                                }
     99                        }
     100
     101                        if ( null !== $filters ) {
     102                                foreach ( $filters as $f ) {
     103                                        stream_filter_remove( $f );
     104                                }
     105                        }
     106                }
     107        }
     108
     109        /**
     110         * Read in the HTTP header information from the supplied stream socket.
     111         *
     112         * @uses WP_Http::processHeaders() Processes the read in header data.
     113         *
     114         * @since 4.5.0
     115         *
     116         * @param string $url The request URL.
     117         * @param array $args The arguments supplied to modify the request and
     118         * response handling.
     119         * @param resource $handle The stream socket client resource.
     120         * @return array The start of the response results with all header
     121         * information.
     122         */
     123        protected function read_headers( $url, $args, $handle ) {
     124                return WP_Http::processHeaders( stream_get_line( $handle, 0, "\r\n\r\n" ) );
     125        }
     126
     127        /**
     128         * Read the HTTP response data from the supplied stream socket.
     129         *
     130         * Used by the public `self::request` method to read in an HTTP
     131         * response levearaging the PHP stream API's.
     132         *
     133         * This method will only close the supplied stream socket handle
     134         * when there is an error. This allows the same socket to be
     135         * used multiple times to send and receive data.
     136         *
     137         * @uses self::read_body() Reads the response body.
     138         * @uses self::read_headers() Creates the response array and reads
     139         * the response headers.
     140         *
     141         * @see self::request()
     142         *
     143         * @since 4.5.0
     144         *
     145         * @param string $url The request URL.
     146         * @param array $args The arguments supplied to modify the request and
     147         * response handling.
     148         * @param resource $handle The stream socket client resource.
     149         * @return array The response results.
     150         */
     151        protected function read_response( $url, $args, $handle ) {
     152                $response = self::read_headers( $url, $args, $handle );
     153                self::read_body( $url, $args, $handle, $response );
     154
     155                return $response;
     156        }
     157
     158        /**
    18159         * Send a HTTP request to a URI using PHP Streams.
    19160         *
    20161         * @see WP_Http::request For default options descriptions.
    class WP_Http_Streams { 
    50191
    51192                $arrURL = parse_url($url);
    52193
    53                 $connect_host = $arrURL['host'];
    54 
    55194                $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' );
    56195                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                         }
     196                        $arrURL['port'] = $secure_transport ? 443 : 80;
    63197                }
    64198
    65199                // Always pass a Path, defaulting to the root in cases such as http://example.com
    class WP_Http_Streams { 
    67201                        $arrURL['path'] = '/';
    68202                }
    69203
     204                $proxy = new WP_HTTP_Proxy();
     205                $send_through_proxy = $proxy->is_enabled() && $proxy->send_through_proxy( $url );
     206
     207                // Hold connection host before updating with supplied host header (if any).
     208                $connect_host = $send_through_proxy ? $proxy->host() : $arrURL['host'];
     209
    70210                if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) {
    71211                        if ( isset( $r['headers']['Host'] ) )
    72212                                $arrURL['host'] = $r['headers']['Host'];
    class WP_Http_Streams { 
    75215                        unset( $r['headers']['Host'], $r['headers']['host'] );
    76216                }
    77217
     218                if ( $send_through_proxy ) {
     219                        /**
     220                         * Filter whether to tunnel this request connection through the proxy.
     221                         *
     222                         * Tunneled connections through a proxy allow the client to speak "directly" to the
     223                         * requested server. Tunneled connections behave as if the proxy was not there
     224                         * even though all traffic sill routes through the proxy.
     225                         *
     226                         * To protect users with end-to-end encryption, a proxy may allow SSL
     227                         * connections to be tunneled through them. This avoids having the proxy negotiate
     228                         * security handshakes and decrypting any transmitted data.
     229                         *
     230                         * When this filter returns true, an HTTP CONNECT will be issued to the proxy asking
     231                         * to connect "directly" to the requestd server. It is possible to tunnel any type
     232                         * of traffic through a proxy using this HTTP CONNECT mechanism, however, this is
     233                         * utlimately dependent on the proxy configuration and what is permitted traffic.
     234                         *
     235                         * @since 4.5.0
     236                         *
     237                         * @param bool $tunnel_through_proxy Whether to tunnel the connection through the
     238                         * proxy using an HTTP CONNECT. Defaults to true when a secure transport (ie HTTPS)
     239                         * is requested.
     240                         * @param array $arrURL The requested server and URI information to help determine if
     241                         * this is a request that should be tunneled through the proxy.
     242                         */
     243                        $tunnel_through_proxy = apply_filters( 'proxy_tunnel', $secure_transport, $arrURL );
     244                } else {
     245                        $tunnel_through_proxy = false;
     246                }
     247
    78248                /*
    79249                 * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect
    80250                 * to ::1, which fails when the server is not set up for it. For compatibility, always
    81251                 * connect to the IPv4 address.
    82252                 */
    83                 if ( 'localhost' == strtolower( $connect_host ) )
     253                if ( 'localhost' == strtolower( $connect_host ) ) {
    84254                        $connect_host = '127.0.0.1';
     255                }
    85256
    86                 $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host;
     257                if ( $tunnel_through_proxy ) {
     258                        $connect_host = 'tcp://' . $connect_host . ':' . $proxy->port();
     259                } else {
     260                        $connect_host = ( $secure_transport ? 'ssl' : 'tcp' ) . '://'
     261                                . $connect_host
     262                                . ':' . ( $send_through_proxy ? $proxy->port() : $arrURL['port'] );
     263                }
    87264
    88                 $is_local = isset( $r['local'] ) && $r['local'];
    89265                $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify'];
    90                 if ( $is_local ) {
     266                if ( isset( $r['local'] ) && $r['local'] ) {
    91267                        /**
    92268                         * Filter whether SSL should be verified for local requests.
    93269                         *
    class WP_Http_Streams { 
    96272                         * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
    97273                         */
    98274                        $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
    99                 } elseif ( ! $is_local ) {
     275                } else {
    100276                        /**
    101277                         * Filter whether SSL should be verified for non-local requests.
    102278                         *
    class WP_Http_Streams { 
    107283                        $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
    108284                }
    109285
    110                 $proxy = new WP_HTTP_Proxy();
    111 
    112286                $context = stream_context_create( array(
    113287                        'ssl' => array(
     288                                /**
     289                                 * Filter the crypto method to use when establishing a secure connection.
     290                                 *
     291                                 * @since 4.5.0
     292                                 *
     293                                 * @param string $filter The filter name.
     294                                 * @param int $crypto_method The crypto method bitmask to use when
     295                                 * calling stream_socket_enable_crypto.
     296                                 */
     297                                'crypto_method' => apply_filters( 'stream_crypto_method', STREAM_CRYPTO_METHOD_TLS_CLIENT ),
     298                                'peer_name' => $arrURL['host'],
    114299                                'verify_peer' => $ssl_verify,
     300                                'verify_peer_name' => $ssl_verify,
    115301                                //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate()
    116302                                'capture_peer_cert' => $ssl_verify,
    117303                                'SNI_enabled' => true,
    class WP_Http_Streams { 
    135321                        if ( $secure_transport )
    136322                                $error_reporting = error_reporting(0);
    137323
    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 );
     324                        $handle = @stream_socket_client( $connect_host, $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    142325
    143326                        if ( $secure_transport )
    144327                                error_reporting( $error_reporting );
    145328
    146329                } 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 );
     330                                $handle = stream_socket_client( $connect_host, $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
    151331                }
    152332
    153333                if ( false === $handle ) {
    class WP_Http_Streams { 
    158338                        return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str );
    159339                }
    160340
     341                if ( $tunnel_through_proxy ) {
     342                        $strHeaders = 'CONNECT ' . $arrURL['host'] . ':' . $arrURL['port'] . ' HTTP/' . $r['httpversion'] . "\r\n"
     343                                . 'Host: ' . $arrURL['host'] . "\r\n";
     344
     345                        if ( isset( $r['user-agent'] ) ) {
     346                                $strHeaders .= 'User-Agent: ' . $r['user-agent'] . "\r\n";
     347                        }
     348
     349                        $encoding = isset( $r['headers']['Accept-Encoding'] ) ? $r['headers']['Accept-Encoding'] : WP_Http_Encoding::accept_encoding( $url, $r );
     350                        if ( $encoding ) {
     351                                $strHeaders .= 'Accept-Encoding: ' . $encoding . "\r\n";
     352                        }
     353
     354                        if ( $proxy->use_authentication() ) {
     355                                $strHeaders .= $proxy->authentication_header() . "\r\n";
     356                        }
     357
     358                        fwrite( $handle, $strHeaders . "\r\n" );
     359
     360                        $response = self::read_response( null, $r, $handle );
     361                        // If the CONNECT didn't return an OK (200) stop.
     362                        if ( 200 !== $response['response']['code'] ) {
     363                                fclose( $handle );
     364
     365                                return $response;
     366                        }
     367
     368                        // Safe to discard the connect response now that everything is read.
     369                        unset( $response );
     370
     371                        // Proxy is now operating transparently; use direct connection
     372                        // behavior from this point on.
     373                        $send_through_proxy = false;
     374                }
     375
    161376                // 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'] ) )
     377                if ( $secure_transport ) {
     378                        /*
     379                         * Secured tunneled requests may perform a crypto handshake with the
     380                         * remote server after the proxy has established a valid connection.
     381                         * Enabling crypto now allows this client to perform the handshake
     382                         * with the remote server establishing an end-to-end secure channel.
     383                         *
     384                         * The `crypto_method` option set in the `ssl` stream context
     385                         * parameter is used to perform the crypto negotiation.
     386                         */
     387                        if ( $tunnel_through_proxy ) {
     388                                stream_socket_enable_crypto( $handle, true );
     389                        }
     390
     391                        if ( $ssl_verify && ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) )
    164392                                return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
    165393                }
    166394
    167395                stream_set_timeout( $handle, $timeout, $utimeout );
    168396
    169                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
     397                if ( $send_through_proxy ) { // Some proxies require full URL in this field.
    170398                        $requestPath = $url;
    171                 else
    172                         $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
     399                } else {
     400                        $requestPath = $arrURL['path'];
     401                        if ( isset( $arrURL['query'] ) ) {
     402                                $requestPath .= '?' . $arrURL['query'];
     403                        }
     404                }
    173405
    174406                $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
    175407
    176408                $include_port_in_host_header = (
    177                         ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) ||
     409                        $send_through_proxy ||
    178410                        ( 'http'  == $arrURL['scheme'] && 80  != $arrURL['port'] ) ||
    179411                        ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] )
    180412                );
    class WP_Http_Streams { 
    185417                        $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
    186418                }
    187419
    188                 if ( isset($r['user-agent']) )
     420                if ( isset( $r['user-agent'] ) )
    189421                        $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
    190422
    191                 if ( is_array($r['headers']) ) {
     423                if ( is_array( $r['headers'] ) ) {
    192424                        foreach ( (array) $r['headers'] as $header => $headerValue )
    193425                                $strHeaders .= $header . ': ' . $headerValue . "\r\n";
    194426                } else {
    195427                        $strHeaders .= $r['headers'];
    196428                }
    197429
    198                 if ( $proxy->use_authentication() )
     430                if ( $send_through_proxy && $proxy->use_authentication() ) {
    199431                        $strHeaders .= $proxy->authentication_header() . "\r\n";
     432                }
    200433
    201434                $strHeaders .= "\r\n";
    202435
    class WP_Http_Streams { 
    211444                        return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    212445                }
    213446
    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                 }
     447                $response = self::read_response( $url, $r, $handle );
    282448
    283449                fclose( $handle );
    284450
    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 
    296451                // Handle redirects.
    297452                if ( false !== ( $redirect_response = WP_Http::handle_redirects( $url, $r, $response ) ) )
    298453                        return $redirect_response;
    299454
    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 
    312455                return $response;
    313456        }
    314457