From 7c8c216bec0764e5e04e111bd189d4aa82d77753 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Wed, 26 Aug 2015 03:55:21 +0000 Subject: [PATCH] HTTP: move classes into their own files, `http.php` loads the new files, so this is 100% BC if someone is loading `http.php` directly. New files created using `svn cp`. `class-http.php` requires functions from `http.php`, so loading it by itself wouldn't have worked. Creates: `class-wp-http-cookie.php` `class-wp-http-curl.php` `class-wp-http-encoding.php` `class-wp-http-proxy.php` `class-wp-http-streams.php` `http-functions.php` `WP_Http` remains in `class-http.php`. `http.php` contains only top-level code. Class files only contain classes. Functions file only contains functions. See #33413. Built from https://develop.svn.wordpress.org/trunk@33748 git-svn-id: http://core.svn.wordpress.org/trunk@33716 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-http.php | 1440 ------------------------ wp-includes/class-wp-http-cookie.php | 213 ++++ wp-includes/class-wp-http-curl.php | 364 ++++++ wp-includes/class-wp-http-encoding.php | 222 ++++ wp-includes/class-wp-http-proxy.php | 210 ++++ wp-includes/class-wp-http-streams.php | 419 +++++++ wp-includes/http-functions.php | 558 +++++++++ wp-includes/http.php | 555 +-------- wp-includes/version.php | 2 +- wp-settings.php | 1 - 10 files changed, 1996 insertions(+), 1988 deletions(-) create mode 100644 wp-includes/class-wp-http-cookie.php create mode 100644 wp-includes/class-wp-http-curl.php create mode 100644 wp-includes/class-wp-http-encoding.php create mode 100644 wp-includes/class-wp-http-proxy.php create mode 100644 wp-includes/class-wp-http-streams.php create mode 100644 wp-includes/http-functions.php diff --git a/wp-includes/class-http.php b/wp-includes/class-http.php index 5c0829700b..4758436e48 100644 --- a/wp-includes/class-http.php +++ b/wp-includes/class-http.php @@ -1,17 +1,4 @@ 'GET', 'timeout' => 5, - 'redirection' => 5, 'httpversion' => '1.0', - 'blocking' => true, - 'headers' => array(), 'body' => null, 'cookies' => array() - ); - - $r = wp_parse_args( $args, $defaults ); - - if ( isset( $r['headers']['User-Agent'] ) ) { - $r['user-agent'] = $r['headers']['User-Agent']; - unset( $r['headers']['User-Agent'] ); - } elseif ( isset( $r['headers']['user-agent'] ) ) { - $r['user-agent'] = $r['headers']['user-agent']; - unset( $r['headers']['user-agent'] ); - } - - // Construct Cookie: header if any cookies are set. - WP_Http::buildCookieHeader( $r ); - - $arrURL = parse_url($url); - - $connect_host = $arrURL['host']; - - $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ); - if ( ! isset( $arrURL['port'] ) ) { - if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) { - $arrURL['port'] = 443; - $secure_transport = true; - } else { - $arrURL['port'] = 80; - } - } - - // Always pass a Path, defaulting to the root in cases such as http://example.com - if ( ! isset( $arrURL['path'] ) ) { - $arrURL['path'] = '/'; - } - - if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) { - if ( isset( $r['headers']['Host'] ) ) - $arrURL['host'] = $r['headers']['Host']; - else - $arrURL['host'] = $r['headers']['host']; - unset( $r['headers']['Host'], $r['headers']['host'] ); - } - - /* - * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect - * to ::1, which fails when the server is not set up for it. For compatibility, always - * connect to the IPv4 address. - */ - if ( 'localhost' == strtolower( $connect_host ) ) - $connect_host = '127.0.0.1'; - - $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host; - - $is_local = isset( $r['local'] ) && $r['local']; - $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify']; - if ( $is_local ) { - /** - * Filter whether SSL should be verified for local requests. - * - * @since 2.8.0 - * - * @param bool $ssl_verify Whether to verify the SSL connection. Default true. - */ - $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); - } elseif ( ! $is_local ) { - /** - * Filter whether SSL should be verified for non-local requests. - * - * @since 2.8.0 - * - * @param bool $ssl_verify Whether to verify the SSL connection. Default true. - */ - $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); - } - - $proxy = new WP_HTTP_Proxy(); - - $context = stream_context_create( array( - 'ssl' => array( - 'verify_peer' => $ssl_verify, - //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate() - 'capture_peer_cert' => $ssl_verify, - 'SNI_enabled' => true, - 'cafile' => $r['sslcertificates'], - 'allow_self_signed' => ! $ssl_verify, - ) - ) ); - - $timeout = (int) floor( $r['timeout'] ); - $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000; - $connect_timeout = max( $timeout, 1 ); - - // Store error number. - $connection_error = null; - - // Store error string. - $connection_error_str = null; - - if ( !WP_DEBUG ) { - // In the event that the SSL connection fails, silence the many PHP Warnings. - if ( $secure_transport ) - $error_reporting = error_reporting(0); - - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) - $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); - else - $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); - - if ( $secure_transport ) - error_reporting( $error_reporting ); - - } else { - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) - $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); - else - $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); - } - - if ( false === $handle ) { - // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken. - if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str ) - return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); - - return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str ); - } - - // Verify that the SSL certificate is valid for this request. - if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) { - if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) ) - return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); - } - - stream_set_timeout( $handle, $timeout, $utimeout ); - - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field. - $requestPath = $url; - else - $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' ); - - $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n"; - - $include_port_in_host_header = ( - ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) || - ( 'http' == $arrURL['scheme'] && 80 != $arrURL['port'] ) || - ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] ) - ); - - if ( $include_port_in_host_header ) { - $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n"; - } else { - $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n"; - } - - if ( isset($r['user-agent']) ) - $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n"; - - if ( is_array($r['headers']) ) { - foreach ( (array) $r['headers'] as $header => $headerValue ) - $strHeaders .= $header . ': ' . $headerValue . "\r\n"; - } else { - $strHeaders .= $r['headers']; - } - - if ( $proxy->use_authentication() ) - $strHeaders .= $proxy->authentication_header() . "\r\n"; - - $strHeaders .= "\r\n"; - - if ( ! is_null($r['body']) ) - $strHeaders .= $r['body']; - - fwrite($handle, $strHeaders); - - if ( ! $r['blocking'] ) { - stream_set_blocking( $handle, 0 ); - fclose( $handle ); - return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); - } - - $strResponse = ''; - $bodyStarted = false; - $keep_reading = true; - $block_size = 4096; - if ( isset( $r['limit_response_size'] ) ) - $block_size = min( $block_size, $r['limit_response_size'] ); - - // If streaming to a file setup the file handle. - if ( $r['stream'] ) { - if ( ! WP_DEBUG ) - $stream_handle = @fopen( $r['filename'], 'w+' ); - else - $stream_handle = fopen( $r['filename'], 'w+' ); - if ( ! $stream_handle ) - return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); - - $bytes_written = 0; - while ( ! feof($handle) && $keep_reading ) { - $block = fread( $handle, $block_size ); - if ( ! $bodyStarted ) { - $strResponse .= $block; - if ( strpos( $strResponse, "\r\n\r\n" ) ) { - $process = WP_Http::processResponse( $strResponse ); - $bodyStarted = true; - $block = $process['body']; - unset( $strResponse ); - $process['body'] = ''; - } - } - - $this_block_size = strlen( $block ); - - if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) { - $this_block_size = ( $r['limit_response_size'] - $bytes_written ); - $block = substr( $block, 0, $this_block_size ); - } - - $bytes_written_to_file = fwrite( $stream_handle, $block ); - - if ( $bytes_written_to_file != $this_block_size ) { - fclose( $handle ); - fclose( $stream_handle ); - return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); - } - - $bytes_written += $bytes_written_to_file; - - $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size']; - } - - fclose( $stream_handle ); - - } else { - $header_length = 0; - while ( ! feof( $handle ) && $keep_reading ) { - $block = fread( $handle, $block_size ); - $strResponse .= $block; - if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) { - $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4; - $bodyStarted = true; - } - $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) ); - } - - $process = WP_Http::processResponse( $strResponse ); - unset( $strResponse ); - - } - - fclose( $handle ); - - $arrHeaders = WP_Http::processHeaders( $process['headers'], $url ); - - $response = array( - 'headers' => $arrHeaders['headers'], - // Not yet processed. - 'body' => null, - 'response' => $arrHeaders['response'], - 'cookies' => $arrHeaders['cookies'], - 'filename' => $r['filename'] - ); - - // Handle redirects. - if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) - return $redirect_response; - - // If the body was chunk encoded, then decode it. - if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] ) - $process['body'] = WP_Http::chunkTransferDecode($process['body']); - - if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) ) - $process['body'] = WP_Http_Encoding::decompress( $process['body'] ); - - if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] ) - $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] ); - - $response['body'] = $process['body']; - - return $response; - } - - /** - * Verifies the received SSL certificate against it's Common Names and subjectAltName fields - * - * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if - * the certificate is valid for the hostname which was requested. - * This function verifies the requested hostname against certificate's subjectAltName field, - * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used. - * - * IP Address support is included if the request is being made to an IP address. - * - * @since 3.7.0 - * @static - * - * @param stream $stream The PHP Stream which the SSL request is being made over - * @param string $host The hostname being requested - * @return bool If the cerficiate presented in $stream is valid for $host - */ - public static function verify_ssl_certificate( $stream, $host ) { - $context_options = stream_context_get_options( $stream ); - - if ( empty( $context_options['ssl']['peer_certificate'] ) ) - return false; - - $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] ); - if ( ! $cert ) - return false; - - /* - * If the request is being made to an IP address, we'll validate against IP fields - * in the cert (if they exist) - */ - $host_type = ( WP_HTTP::is_ip_address( $host ) ? 'ip' : 'dns' ); - - $certificate_hostnames = array(); - if ( ! empty( $cert['extensions']['subjectAltName'] ) ) { - $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] ); - foreach ( $match_against as $match ) { - list( $match_type, $match_host ) = explode( ':', $match ); - if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS: - $certificate_hostnames[] = strtolower( trim( $match_host ) ); - } - } elseif ( !empty( $cert['subject']['CN'] ) ) { - // Only use the CN when the certificate includes no subjectAltName extension. - $certificate_hostnames[] = strtolower( $cert['subject']['CN'] ); - } - - // Exact hostname/IP matches. - if ( in_array( strtolower( $host ), $certificate_hostnames ) ) - return true; - - // IP's can't be wildcards, Stop processing. - if ( 'ip' == $host_type ) - return false; - - // Test to see if the domain is at least 2 deep for wildcard support. - if ( substr_count( $host, '.' ) < 2 ) - return false; - - // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com. - $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host ); - - return in_array( strtolower( $wildcard_host ), $certificate_hostnames ); - } - - /** - * Whether this class can be used for retrieving a URL. - * - * @static - * @access public - * @since 2.7.0 - * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). - * - * @return bool False means this class can not be used, true means it can. - */ - public static function test( $args = array() ) { - if ( ! function_exists( 'stream_socket_client' ) ) - return false; - - $is_ssl = isset( $args['ssl'] ) && $args['ssl']; - - if ( $is_ssl ) { - if ( ! extension_loaded( 'openssl' ) ) - return false; - if ( ! function_exists( 'openssl_x509_parse' ) ) - return false; - } - - /** - * Filter whether streams can be used as a transport for retrieving a URL. - * - * @since 2.7.0 - * - * @param bool $use_class Whether the class can be used. Default true. - * @param array $args Request arguments. - */ - return apply_filters( 'use_streams_transport', true, $args ); - } -} - -/** - * Deprecated HTTP Transport method which used fsockopen. - * - * This class is not used, and is included for backwards compatibility only. - * All code should make use of WP_HTTP directly through it's API. - * - * @see WP_HTTP::request - * - * @since 2.7.0 - * @deprecated 3.7.0 Please use WP_HTTP::request() directly - */ -class WP_HTTP_Fsockopen extends WP_HTTP_Streams { - // For backwards compatibility for users who are using the class directly. -} - -/** - * HTTP request method uses Curl extension to retrieve the url. - * - * Requires the Curl extension to be installed. - * - * @package WordPress - * @subpackage HTTP - * @since 2.7.0 - */ -class WP_Http_Curl { - - /** - * Temporary header storage for during requests. - * - * @since 3.2.0 - * @access private - * @var string - */ - private $headers = ''; - - /** - * Temporary body storage for during requests. - * - * @since 3.6.0 - * @access private - * @var string - */ - private $body = ''; - - /** - * The maximum amount of data to receive from the remote server. - * - * @since 3.6.0 - * @access private - * @var int - */ - private $max_body_length = false; - - /** - * The file resource used for streaming to file. - * - * @since 3.6.0 - * @access private - * @var resource - */ - private $stream_handle = false; - - /** - * The total bytes written in the current request. - * - * @since 4.1.0 - * @access private - * @var int - */ - private $bytes_written_total = 0; - - /** - * Send a HTTP request to a URI using cURL extension. - * - * @access public - * @since 2.7.0 - * - * @param string $url The request URL. - * @param string|array $args Optional. Override the defaults. - * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error - */ - public function request($url, $args = array()) { - $defaults = array( - 'method' => 'GET', 'timeout' => 5, - 'redirection' => 5, 'httpversion' => '1.0', - 'blocking' => true, - 'headers' => array(), 'body' => null, 'cookies' => array() - ); - - $r = wp_parse_args( $args, $defaults ); - - if ( isset( $r['headers']['User-Agent'] ) ) { - $r['user-agent'] = $r['headers']['User-Agent']; - unset( $r['headers']['User-Agent'] ); - } elseif ( isset( $r['headers']['user-agent'] ) ) { - $r['user-agent'] = $r['headers']['user-agent']; - unset( $r['headers']['user-agent'] ); - } - - // Construct Cookie: header if any cookies are set. - WP_Http::buildCookieHeader( $r ); - - $handle = curl_init(); - - // cURL offers really easy proxy support. - $proxy = new WP_HTTP_Proxy(); - - if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { - - curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP ); - curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() ); - curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() ); - - if ( $proxy->use_authentication() ) { - curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY ); - curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() ); - } - } - - $is_local = isset($r['local']) && $r['local']; - $ssl_verify = isset($r['sslverify']) && $r['sslverify']; - if ( $is_local ) { - /** This filter is documented in wp-includes/class-http.php */ - $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); - } elseif ( ! $is_local ) { - /** This filter is documented in wp-includes/class-http.php */ - $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); - } - - /* - * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since. - * a value of 0 will allow an unlimited timeout. - */ - $timeout = (int) ceil( $r['timeout'] ); - curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout ); - curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); - - curl_setopt( $handle, CURLOPT_URL, $url); - curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true ); - curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false ); - curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify ); - curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] ); - curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] ); - - /* - * The option doesn't work with safe mode or when open_basedir is set, and there's - * a bug #17490 with redirected POST requests, so handle redirections outside Curl. - */ - curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false ); - if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4 - curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); - - switch ( $r['method'] ) { - case 'HEAD': - curl_setopt( $handle, CURLOPT_NOBODY, true ); - break; - case 'POST': - curl_setopt( $handle, CURLOPT_POST, true ); - curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); - break; - case 'PUT': - curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); - curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); - break; - default: - curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] ); - if ( ! is_null( $r['body'] ) ) - curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); - break; - } - - if ( true === $r['blocking'] ) { - curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); - curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); - } - - curl_setopt( $handle, CURLOPT_HEADER, false ); - - if ( isset( $r['limit_response_size'] ) ) - $this->max_body_length = intval( $r['limit_response_size'] ); - else - $this->max_body_length = false; - - // If streaming to a file open a file handle, and setup our curl streaming handler. - if ( $r['stream'] ) { - if ( ! WP_DEBUG ) - $this->stream_handle = @fopen( $r['filename'], 'w+' ); - else - $this->stream_handle = fopen( $r['filename'], 'w+' ); - if ( ! $this->stream_handle ) - return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); - } else { - $this->stream_handle = false; - } - - if ( !empty( $r['headers'] ) ) { - // cURL expects full header strings in each element. - $headers = array(); - foreach ( $r['headers'] as $name => $value ) { - $headers[] = "{$name}: $value"; - } - curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); - } - - if ( $r['httpversion'] == '1.0' ) - curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); - else - curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); - - /** - * Fires before the cURL request is executed. - * - * Cookies are not currently handled by the HTTP API. This action allows - * plugins to handle cookies themselves. - * - * @since 2.8.0 - * - * @param resource &$handle The cURL handle returned by curl_init(). - * @param array $r The HTTP request arguments. - * @param string $url The request URL. - */ - do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) ); - - // We don't need to return the body, so don't. Just execute request and return. - if ( ! $r['blocking'] ) { - curl_exec( $handle ); - - if ( $curl_error = curl_error( $handle ) ) { - curl_close( $handle ); - return new WP_Error( 'http_request_failed', $curl_error ); - } - if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { - curl_close( $handle ); - return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); - } - - curl_close( $handle ); - return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); - } - - curl_exec( $handle ); - $theHeaders = WP_Http::processHeaders( $this->headers, $url ); - $theBody = $this->body; - $bytes_written_total = $this->bytes_written_total; - - $this->headers = ''; - $this->body = ''; - $this->bytes_written_total = 0; - - $curl_error = curl_errno( $handle ); - - // If an error occurred, or, no response. - if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) { - if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) { - if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) { - if ( $r['stream'] ) { - curl_close( $handle ); - fclose( $this->stream_handle ); - return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); - } else { - curl_close( $handle ); - return new WP_Error( 'http_request_failed', curl_error( $handle ) ); - } - } - } else { - if ( $curl_error = curl_error( $handle ) ) { - curl_close( $handle ); - return new WP_Error( 'http_request_failed', $curl_error ); - } - } - if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { - curl_close( $handle ); - return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); - } - } - - curl_close( $handle ); - - if ( $r['stream'] ) - fclose( $this->stream_handle ); - - $response = array( - 'headers' => $theHeaders['headers'], - 'body' => null, - 'response' => $theHeaders['response'], - 'cookies' => $theHeaders['cookies'], - 'filename' => $r['filename'] - ); - - // Handle redirects. - if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) - return $redirect_response; - - if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) ) - $theBody = WP_Http_Encoding::decompress( $theBody ); - - $response['body'] = $theBody; - - return $response; - } - - /** - * Grab the headers of the cURL request - * - * Each header is sent individually to this callback, so we append to the $header property for temporary storage - * - * @since 3.2.0 - * @access private - * @return int - */ - private function stream_headers( $handle, $headers ) { - $this->headers .= $headers; - return strlen( $headers ); - } - - /** - * Grab the body of the cURL request - * - * The contents of the document are passed in chunks, so we append to the $body property for temporary storage. - * Returning a length shorter than the length of $data passed in will cause cURL to abort the request with CURLE_WRITE_ERROR - * - * @since 3.6.0 - * @access private - * @return int - */ - private function stream_body( $handle, $data ) { - $data_length = strlen( $data ); - - if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) { - $data_length = ( $this->max_body_length - $this->bytes_written_total ); - $data = substr( $data, 0, $data_length ); - } - - if ( $this->stream_handle ) { - $bytes_written = fwrite( $this->stream_handle, $data ); - } else { - $this->body .= $data; - $bytes_written = $data_length; - } - - $this->bytes_written_total += $bytes_written; - - // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR. - return $bytes_written; - } - - /** - * Whether this class can be used for retrieving an URL. - * - * @static - * @since 2.7.0 - * - * @return bool False means this class can not be used, true means it can. - */ - public static function test( $args = array() ) { - if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) - return false; - - $is_ssl = isset( $args['ssl'] ) && $args['ssl']; - - if ( $is_ssl ) { - $curl_version = curl_version(); - // Check whether this cURL version support SSL requests. - if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) - return false; - } - - /** - * Filter whether cURL can be used as a transport for retrieving a URL. - * - * @since 2.7.0 - * - * @param bool $use_class Whether the class can be used. Default true. - * @param array $args An array of request arguments. - */ - return apply_filters( 'use_curl_transport', true, $args ); - } -} - -/** - * Adds Proxy support to the WordPress HTTP API. - * - * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to - * enable proxy support. There are also a few filters that plugins can hook into for some of the - * constants. - * - * Please note that only BASIC authentication is supported by most transports. - * cURL MAY support more methods (such as NTLM authentication) depending on your environment. - * - * The constants are as follows: - *
    - *
  1. WP_PROXY_HOST - Enable proxy support and host for connecting.
  2. - *
  3. WP_PROXY_PORT - Proxy port for connection. No default, must be defined.
  4. - *
  5. WP_PROXY_USERNAME - Proxy username, if it requires authentication.
  6. - *
  7. WP_PROXY_PASSWORD - Proxy password, if it requires authentication.
  8. - *
  9. WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy. - * You do not need to have localhost and the blog host in this list, because they will not be passed - * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org
  10. - *
- * - * An example can be as seen below. - * - * define('WP_PROXY_HOST', '192.168.84.101'); - * define('WP_PROXY_PORT', '8080'); - * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org'); - * - * @link https://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress. - * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS - * @since 2.8.0 - */ -class WP_HTTP_Proxy { - - /** - * Whether proxy connection should be used. - * - * @since 2.8.0 - * - * @use WP_PROXY_HOST - * @use WP_PROXY_PORT - * - * @return bool - */ - public function is_enabled() { - return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT'); - } - - /** - * Whether authentication should be used. - * - * @since 2.8.0 - * - * @use WP_PROXY_USERNAME - * @use WP_PROXY_PASSWORD - * - * @return bool - */ - public function use_authentication() { - return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD'); - } - - /** - * Retrieve the host for the proxy server. - * - * @since 2.8.0 - * - * @return string - */ - public function host() { - if ( defined('WP_PROXY_HOST') ) - return WP_PROXY_HOST; - - return ''; - } - - /** - * Retrieve the port for the proxy server. - * - * @since 2.8.0 - * - * @return string - */ - public function port() { - if ( defined('WP_PROXY_PORT') ) - return WP_PROXY_PORT; - - return ''; - } - - /** - * Retrieve the username for proxy authentication. - * - * @since 2.8.0 - * - * @return string - */ - public function username() { - if ( defined('WP_PROXY_USERNAME') ) - return WP_PROXY_USERNAME; - - return ''; - } - - /** - * Retrieve the password for proxy authentication. - * - * @since 2.8.0 - * - * @return string - */ - public function password() { - if ( defined('WP_PROXY_PASSWORD') ) - return WP_PROXY_PASSWORD; - - return ''; - } - - /** - * Retrieve authentication string for proxy authentication. - * - * @since 2.8.0 - * - * @return string - */ - public function authentication() { - return $this->username() . ':' . $this->password(); - } - - /** - * Retrieve header string for proxy authentication. - * - * @since 2.8.0 - * - * @return string - */ - public function authentication_header() { - return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() ); - } - - /** - * Whether URL should be sent through the proxy server. - * - * We want to keep localhost and the blog URL from being sent through the proxy server, because - * some proxies can not handle this. We also have the constant available for defining other - * hosts that won't be sent through the proxy. - * - * @since 2.8.0 - * - * @staticvar array|null $bypass_hosts - * @staticvar array $wildcard_regex - * - * @param string $uri URI to check. - * @return bool True, to send through the proxy and false if, the proxy should not be used. - */ - public function send_through_proxy( $uri ) { - /* - * parse_url() only handles http, https type URLs, and will emit E_WARNING on failure. - * This will be displayed on blogs, which is not reasonable. - */ - $check = @parse_url($uri); - - // Malformed URL, can not process, but this could mean ssl, so let through anyway. - if ( $check === false ) - return true; - - $home = parse_url( get_option('siteurl') ); - - /** - * Filter whether to preempt sending the request through the proxy server. - * - * Returning false will bypass the proxy; returning true will send - * the request through the proxy. Returning null bypasses the filter. - * - * @since 3.5.0 - * - * @param null $override Whether to override the request result. Default null. - * @param string $uri URL to check. - * @param array $check Associative array result of parsing the URI. - * @param array $home Associative array result of parsing the site URL. - */ - $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home ); - if ( ! is_null( $result ) ) - return $result; - - if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) - return false; - - if ( !defined('WP_PROXY_BYPASS_HOSTS') ) - return true; - - static $bypass_hosts = null; - static $wildcard_regex = array(); - if ( null === $bypass_hosts ) { - $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS); - - if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) { - $wildcard_regex = array(); - foreach ( $bypass_hosts as $host ) - $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); - $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i'; - } - } - - if ( !empty($wildcard_regex) ) - return !preg_match($wildcard_regex, $check['host']); - else - return !in_array( $check['host'], $bypass_hosts ); - } -} -/** - * Internal representation of a single cookie. - * - * Returned cookies are represented using this class, and when cookies are set, if they are not - * already a WP_Http_Cookie() object, then they are turned into one. - * - * @todo The WordPress convention is to use underscores instead of camelCase for function and method - * names. Need to switch to use underscores instead for the methods. - * - * @package WordPress - * @subpackage HTTP - * @since 2.8.0 - */ -class WP_Http_Cookie { - - /** - * Cookie name. - * - * @since 2.8.0 - * @var string - */ - public $name; - - /** - * Cookie value. - * - * @since 2.8.0 - * @var string - */ - public $value; - - /** - * When the cookie expires. - * - * @since 2.8.0 - * @var string - */ - public $expires; - - /** - * Cookie URL path. - * - * @since 2.8.0 - * @var string - */ - public $path; - - /** - * Cookie Domain. - * - * @since 2.8.0 - * @var string - */ - public $domain; - - /** - * Sets up this cookie object. - * - * The parameter $data should be either an associative array containing the indices names below - * or a header string detailing it. - * - * @since 2.8.0 - * @access public - * - * @param string|array $data { - * Raw cookie data as header string or data array. - * - * @type string $name Cookie name. - * @type mixed $value Value. Should NOT already be urlencoded. - * @type string|int $expires Optional. Unix timestamp or formatted date. Default null. - * @type string $path Optional. Path. Default '/'. - * @type string $domain Optional. Domain. Default host of parsed $requested_url. - * @type int $port Optional. Port. Default null. - * } - * @param string $requested_url The URL which the cookie was set on, used for default $domain - * and $port values. - */ - public function __construct( $data, $requested_url = '' ) { - if ( $requested_url ) - $arrURL = @parse_url( $requested_url ); - if ( isset( $arrURL['host'] ) ) - $this->domain = $arrURL['host']; - $this->path = isset( $arrURL['path'] ) ? $arrURL['path'] : '/'; - if ( '/' != substr( $this->path, -1 ) ) - $this->path = dirname( $this->path ) . '/'; - - if ( is_string( $data ) ) { - // Assume it's a header string direct from a previous request. - $pairs = explode( ';', $data ); - - // Special handling for first pair; name=value. Also be careful of "=" in value. - $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) ); - $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 ); - $this->name = $name; - $this->value = urldecode( $value ); - - // Removes name=value from items. - array_shift( $pairs ); - - // Set everything else as a property. - foreach ( $pairs as $pair ) { - $pair = rtrim($pair); - - // Handle the cookie ending in ; which results in a empty final pair. - if ( empty($pair) ) - continue; - - list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' ); - $key = strtolower( trim( $key ) ); - if ( 'expires' == $key ) - $val = strtotime( $val ); - $this->$key = $val; - } - } else { - if ( !isset( $data['name'] ) ) - return; - - // Set properties based directly on parameters. - foreach ( array( 'name', 'value', 'path', 'domain', 'port' ) as $field ) { - if ( isset( $data[ $field ] ) ) - $this->$field = $data[ $field ]; - } - - if ( isset( $data['expires'] ) ) - $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] ); - else - $this->expires = null; - } - } - - /** - * Confirms that it's OK to send this cookie to the URL checked against. - * - * Decision is based on RFC 2109/2965, so look there for details on validity. - * - * @access public - * @since 2.8.0 - * - * @param string $url URL you intend to send this cookie to - * @return bool true if allowed, false otherwise. - */ - public function test( $url ) { - if ( is_null( $this->name ) ) - return false; - - // Expires - if expired then nothing else matters. - if ( isset( $this->expires ) && time() > $this->expires ) - return false; - - // Get details on the URL we're thinking about sending to. - $url = parse_url( $url ); - $url['port'] = isset( $url['port'] ) ? $url['port'] : ( 'https' == $url['scheme'] ? 443 : 80 ); - $url['path'] = isset( $url['path'] ) ? $url['path'] : '/'; - - // Values to use for comparison against the URL. - $path = isset( $this->path ) ? $this->path : '/'; - $port = isset( $this->port ) ? $this->port : null; - $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] ); - if ( false === stripos( $domain, '.' ) ) - $domain .= '.local'; - - // Host - very basic check that the request URL ends with the domain restriction (minus leading dot). - $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain; - if ( substr( $url['host'], -strlen( $domain ) ) != $domain ) - return false; - - // Port - supports "port-lists" in the format: "80,8000,8080". - if ( !empty( $port ) && !in_array( $url['port'], explode( ',', $port) ) ) - return false; - - // Path - request path must start with path restriction. - if ( substr( $url['path'], 0, strlen( $path ) ) != $path ) - return false; - - return true; - } - - /** - * Convert cookie name and value back to header string. - * - * @access public - * @since 2.8.0 - * - * @return string Header encoded cookie name and value. - */ - public function getHeaderValue() { - if ( ! isset( $this->name ) || ! isset( $this->value ) ) - return ''; - - /** - * Filter the header-encoded cookie value. - * - * @since 3.4.0 - * - * @param string $value The cookie value. - * @param string $name The cookie name. - */ - return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name ); - } - - /** - * Retrieve cookie header for usage in the rest of the WordPress HTTP API. - * - * @access public - * @since 2.8.0 - * - * @return string - */ - public function getFullHeader() { - return 'Cookie: ' . $this->getHeaderValue(); - } -} - -/** - * Implementation for deflate and gzip transfer encodings. - * - * Includes RFC 1950, RFC 1951, and RFC 1952. - * - * @since 2.8.0 - * @package WordPress - * @subpackage HTTP - */ -class WP_Http_Encoding { - - /** - * Compress raw string using the deflate format. - * - * Supports the RFC 1951 standard. - * - * @since 2.8.0 - * - * @static - * - * @param string $raw String to compress. - * @param int $level Optional, default is 9. Compression level, 9 is highest. - * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports. - * @return string|false False on failure. - */ - public static function compress( $raw, $level = 9, $supports = null ) { - return gzdeflate( $raw, $level ); - } - - /** - * Decompression of deflated string. - * - * Will attempt to decompress using the RFC 1950 standard, and if that fails - * then the RFC 1951 standard deflate will be attempted. Finally, the RFC - * 1952 standard gzip decode will be attempted. If all fail, then the - * original compressed string will be returned. - * - * @since 2.8.0 - * - * @static - * - * @param string $compressed String to decompress. - * @param int $length The optional length of the compressed data. - * @return string|bool False on failure. - */ - public static function decompress( $compressed, $length = null ) { - - if ( empty($compressed) ) - return $compressed; - - if ( false !== ( $decompressed = @gzinflate( $compressed ) ) ) - return $decompressed; - - if ( false !== ( $decompressed = self::compatible_gzinflate( $compressed ) ) ) - return $decompressed; - - if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) ) - return $decompressed; - - if ( function_exists('gzdecode') ) { - $decompressed = @gzdecode( $compressed ); - - if ( false !== $decompressed ) - return $decompressed; - } - - return $compressed; - } - - /** - * Decompression of deflated string while staying compatible with the majority of servers. - * - * Certain Servers will return deflated data with headers which PHP's gzinflate() - * function cannot handle out of the box. The following function has been created from - * various snippets on the gzinflate() PHP documentation. - * - * Warning: Magic numbers within. Due to the potential different formats that the compressed - * data may be returned in, some "magic offsets" are needed to ensure proper decompression - * takes place. For a simple progmatic way to determine the magic offset in use, see: - * https://core.trac.wordpress.org/ticket/18273 - * - * @since 2.8.1 - * @link https://core.trac.wordpress.org/ticket/18273 - * @link http://au2.php.net/manual/en/function.gzinflate.php#70875 - * @link http://au2.php.net/manual/en/function.gzinflate.php#77336 - * - * @static - * - * @param string $gzData String to decompress. - * @return string|bool False on failure. - */ - public static function compatible_gzinflate($gzData) { - - // Compressed data might contain a full header, if so strip it for gzinflate(). - if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) { - $i = 10; - $flg = ord( substr($gzData, 3, 1) ); - if ( $flg > 0 ) { - if ( $flg & 4 ) { - list($xlen) = unpack('v', substr($gzData, $i, 2) ); - $i = $i + 2 + $xlen; - } - if ( $flg & 8 ) - $i = strpos($gzData, "\0", $i) + 1; - if ( $flg & 16 ) - $i = strpos($gzData, "\0", $i) + 1; - if ( $flg & 2 ) - $i = $i + 2; - } - $decompressed = @gzinflate( substr($gzData, $i, -8) ); - if ( false !== $decompressed ) - return $decompressed; - } - - // Compressed data from java.util.zip.Deflater amongst others. - $decompressed = @gzinflate( substr($gzData, 2) ); - if ( false !== $decompressed ) - return $decompressed; - - return false; - } - - /** - * What encoding types to accept and their priority values. - * - * @since 2.8.0 - * - * @static - * - * @param string $url - * @param array $args - * @return string Types of encoding to accept. - */ - public static function accept_encoding( $url, $args ) { - $type = array(); - $compression_enabled = self::is_available(); - - if ( ! $args['decompress'] ) // Decompression specifically disabled. - $compression_enabled = false; - elseif ( $args['stream'] ) // Disable when streaming to file. - $compression_enabled = false; - elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it. - $compression_enabled = false; - - if ( $compression_enabled ) { - if ( function_exists( 'gzinflate' ) ) - $type[] = 'deflate;q=1.0'; - - if ( function_exists( 'gzuncompress' ) ) - $type[] = 'compress;q=0.5'; - - if ( function_exists( 'gzdecode' ) ) - $type[] = 'gzip;q=0.5'; - } - - /** - * Filter the allowed encoding types. - * - * @since 3.6.0 - * - * @param array $type Encoding types allowed. Accepts 'gzinflate', - * 'gzuncompress', 'gzdecode'. - * @param string $url URL of the HTTP request. - * @param array $args HTTP request arguments. - */ - $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args ); - - return implode(', ', $type); - } - - /** - * What encoding the content used when it was compressed to send in the headers. - * - * @since 2.8.0 - * - * @static - * - * @return string Content-Encoding string to send in the header. - */ - public static function content_encoding() { - return 'deflate'; - } - - /** - * Whether the content be decoded based on the headers. - * - * @since 2.8.0 - * - * @static - * - * @param array|string $headers All of the available headers. - * @return bool - */ - public static function should_decode($headers) { - if ( is_array( $headers ) ) { - if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) ) - return true; - } elseif ( is_string( $headers ) ) { - return ( stripos($headers, 'content-encoding:') !== false ); - } - - return false; - } - - /** - * Whether decompression and compression are supported by the PHP version. - * - * Each function is tested instead of checking for the zlib extension, to - * ensure that the functions all exist in the PHP version and aren't - * disabled. - * - * @since 2.8.0 - * - * @static - * - * @return bool - */ - public static function is_available() { - return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') ); - } -} diff --git a/wp-includes/class-wp-http-cookie.php b/wp-includes/class-wp-http-cookie.php new file mode 100644 index 0000000000..ac79c9d827 --- /dev/null +++ b/wp-includes/class-wp-http-cookie.php @@ -0,0 +1,213 @@ +domain = $arrURL['host']; + $this->path = isset( $arrURL['path'] ) ? $arrURL['path'] : '/'; + if ( '/' != substr( $this->path, -1 ) ) + $this->path = dirname( $this->path ) . '/'; + + if ( is_string( $data ) ) { + // Assume it's a header string direct from a previous request. + $pairs = explode( ';', $data ); + + // Special handling for first pair; name=value. Also be careful of "=" in value. + $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) ); + $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 ); + $this->name = $name; + $this->value = urldecode( $value ); + + // Removes name=value from items. + array_shift( $pairs ); + + // Set everything else as a property. + foreach ( $pairs as $pair ) { + $pair = rtrim($pair); + + // Handle the cookie ending in ; which results in a empty final pair. + if ( empty($pair) ) + continue; + + list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' ); + $key = strtolower( trim( $key ) ); + if ( 'expires' == $key ) + $val = strtotime( $val ); + $this->$key = $val; + } + } else { + if ( !isset( $data['name'] ) ) + return; + + // Set properties based directly on parameters. + foreach ( array( 'name', 'value', 'path', 'domain', 'port' ) as $field ) { + if ( isset( $data[ $field ] ) ) + $this->$field = $data[ $field ]; + } + + if ( isset( $data['expires'] ) ) + $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] ); + else + $this->expires = null; + } + } + + /** + * Confirms that it's OK to send this cookie to the URL checked against. + * + * Decision is based on RFC 2109/2965, so look there for details on validity. + * + * @access public + * @since 2.8.0 + * + * @param string $url URL you intend to send this cookie to + * @return bool true if allowed, false otherwise. + */ + public function test( $url ) { + if ( is_null( $this->name ) ) + return false; + + // Expires - if expired then nothing else matters. + if ( isset( $this->expires ) && time() > $this->expires ) + return false; + + // Get details on the URL we're thinking about sending to. + $url = parse_url( $url ); + $url['port'] = isset( $url['port'] ) ? $url['port'] : ( 'https' == $url['scheme'] ? 443 : 80 ); + $url['path'] = isset( $url['path'] ) ? $url['path'] : '/'; + + // Values to use for comparison against the URL. + $path = isset( $this->path ) ? $this->path : '/'; + $port = isset( $this->port ) ? $this->port : null; + $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] ); + if ( false === stripos( $domain, '.' ) ) + $domain .= '.local'; + + // Host - very basic check that the request URL ends with the domain restriction (minus leading dot). + $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain; + if ( substr( $url['host'], -strlen( $domain ) ) != $domain ) + return false; + + // Port - supports "port-lists" in the format: "80,8000,8080". + if ( !empty( $port ) && !in_array( $url['port'], explode( ',', $port) ) ) + return false; + + // Path - request path must start with path restriction. + if ( substr( $url['path'], 0, strlen( $path ) ) != $path ) + return false; + + return true; + } + + /** + * Convert cookie name and value back to header string. + * + * @access public + * @since 2.8.0 + * + * @return string Header encoded cookie name and value. + */ + public function getHeaderValue() { + if ( ! isset( $this->name ) || ! isset( $this->value ) ) + return ''; + + /** + * Filter the header-encoded cookie value. + * + * @since 3.4.0 + * + * @param string $value The cookie value. + * @param string $name The cookie name. + */ + return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name ); + } + + /** + * Retrieve cookie header for usage in the rest of the WordPress HTTP API. + * + * @access public + * @since 2.8.0 + * + * @return string + */ + public function getFullHeader() { + return 'Cookie: ' . $this->getHeaderValue(); + } +} diff --git a/wp-includes/class-wp-http-curl.php b/wp-includes/class-wp-http-curl.php new file mode 100644 index 0000000000..e31369833b --- /dev/null +++ b/wp-includes/class-wp-http-curl.php @@ -0,0 +1,364 @@ + 'GET', 'timeout' => 5, + 'redirection' => 5, 'httpversion' => '1.0', + 'blocking' => true, + 'headers' => array(), 'body' => null, 'cookies' => array() + ); + + $r = wp_parse_args( $args, $defaults ); + + if ( isset( $r['headers']['User-Agent'] ) ) { + $r['user-agent'] = $r['headers']['User-Agent']; + unset( $r['headers']['User-Agent'] ); + } elseif ( isset( $r['headers']['user-agent'] ) ) { + $r['user-agent'] = $r['headers']['user-agent']; + unset( $r['headers']['user-agent'] ); + } + + // Construct Cookie: header if any cookies are set. + WP_Http::buildCookieHeader( $r ); + + $handle = curl_init(); + + // cURL offers really easy proxy support. + $proxy = new WP_HTTP_Proxy(); + + if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { + + curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP ); + curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() ); + curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() ); + + if ( $proxy->use_authentication() ) { + curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY ); + curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() ); + } + } + + $is_local = isset($r['local']) && $r['local']; + $ssl_verify = isset($r['sslverify']) && $r['sslverify']; + if ( $is_local ) { + /** This filter is documented in wp-includes/class-http.php */ + $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); + } elseif ( ! $is_local ) { + /** This filter is documented in wp-includes/class-http.php */ + $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); + } + + /* + * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since. + * a value of 0 will allow an unlimited timeout. + */ + $timeout = (int) ceil( $r['timeout'] ); + curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout ); + curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); + + curl_setopt( $handle, CURLOPT_URL, $url); + curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true ); + curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false ); + curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify ); + curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] ); + curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] ); + + /* + * The option doesn't work with safe mode or when open_basedir is set, and there's + * a bug #17490 with redirected POST requests, so handle redirections outside Curl. + */ + curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false ); + if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4 + curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); + + switch ( $r['method'] ) { + case 'HEAD': + curl_setopt( $handle, CURLOPT_NOBODY, true ); + break; + case 'POST': + curl_setopt( $handle, CURLOPT_POST, true ); + curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); + break; + case 'PUT': + curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); + curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); + break; + default: + curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] ); + if ( ! is_null( $r['body'] ) ) + curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); + break; + } + + if ( true === $r['blocking'] ) { + curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); + curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); + } + + curl_setopt( $handle, CURLOPT_HEADER, false ); + + if ( isset( $r['limit_response_size'] ) ) + $this->max_body_length = intval( $r['limit_response_size'] ); + else + $this->max_body_length = false; + + // If streaming to a file open a file handle, and setup our curl streaming handler. + if ( $r['stream'] ) { + if ( ! WP_DEBUG ) + $this->stream_handle = @fopen( $r['filename'], 'w+' ); + else + $this->stream_handle = fopen( $r['filename'], 'w+' ); + if ( ! $this->stream_handle ) + return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); + } else { + $this->stream_handle = false; + } + + if ( !empty( $r['headers'] ) ) { + // cURL expects full header strings in each element. + $headers = array(); + foreach ( $r['headers'] as $name => $value ) { + $headers[] = "{$name}: $value"; + } + curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); + } + + if ( $r['httpversion'] == '1.0' ) + curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); + else + curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); + + /** + * Fires before the cURL request is executed. + * + * Cookies are not currently handled by the HTTP API. This action allows + * plugins to handle cookies themselves. + * + * @since 2.8.0 + * + * @param resource &$handle The cURL handle returned by curl_init(). + * @param array $r The HTTP request arguments. + * @param string $url The request URL. + */ + do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) ); + + // We don't need to return the body, so don't. Just execute request and return. + if ( ! $r['blocking'] ) { + curl_exec( $handle ); + + if ( $curl_error = curl_error( $handle ) ) { + curl_close( $handle ); + return new WP_Error( 'http_request_failed', $curl_error ); + } + if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { + curl_close( $handle ); + return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); + } + + curl_close( $handle ); + return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); + } + + curl_exec( $handle ); + $theHeaders = WP_Http::processHeaders( $this->headers, $url ); + $theBody = $this->body; + $bytes_written_total = $this->bytes_written_total; + + $this->headers = ''; + $this->body = ''; + $this->bytes_written_total = 0; + + $curl_error = curl_errno( $handle ); + + // If an error occurred, or, no response. + if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) { + if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) { + if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) { + if ( $r['stream'] ) { + curl_close( $handle ); + fclose( $this->stream_handle ); + return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); + } else { + curl_close( $handle ); + return new WP_Error( 'http_request_failed', curl_error( $handle ) ); + } + } + } else { + if ( $curl_error = curl_error( $handle ) ) { + curl_close( $handle ); + return new WP_Error( 'http_request_failed', $curl_error ); + } + } + if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { + curl_close( $handle ); + return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); + } + } + + curl_close( $handle ); + + if ( $r['stream'] ) + fclose( $this->stream_handle ); + + $response = array( + 'headers' => $theHeaders['headers'], + 'body' => null, + 'response' => $theHeaders['response'], + 'cookies' => $theHeaders['cookies'], + 'filename' => $r['filename'] + ); + + // Handle redirects. + if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) + return $redirect_response; + + if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) ) + $theBody = WP_Http_Encoding::decompress( $theBody ); + + $response['body'] = $theBody; + + return $response; + } + + /** + * Grab the headers of the cURL request + * + * Each header is sent individually to this callback, so we append to the $header property for temporary storage + * + * @since 3.2.0 + * @access private + * @return int + */ + private function stream_headers( $handle, $headers ) { + $this->headers .= $headers; + return strlen( $headers ); + } + + /** + * Grab the body of the cURL request + * + * The contents of the document are passed in chunks, so we append to the $body property for temporary storage. + * Returning a length shorter than the length of $data passed in will cause cURL to abort the request with CURLE_WRITE_ERROR + * + * @since 3.6.0 + * @access private + * @return int + */ + private function stream_body( $handle, $data ) { + $data_length = strlen( $data ); + + if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) { + $data_length = ( $this->max_body_length - $this->bytes_written_total ); + $data = substr( $data, 0, $data_length ); + } + + if ( $this->stream_handle ) { + $bytes_written = fwrite( $this->stream_handle, $data ); + } else { + $this->body .= $data; + $bytes_written = $data_length; + } + + $this->bytes_written_total += $bytes_written; + + // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR. + return $bytes_written; + } + + /** + * Whether this class can be used for retrieving an URL. + * + * @static + * @since 2.7.0 + * + * @return bool False means this class can not be used, true means it can. + */ + public static function test( $args = array() ) { + if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) + return false; + + $is_ssl = isset( $args['ssl'] ) && $args['ssl']; + + if ( $is_ssl ) { + $curl_version = curl_version(); + // Check whether this cURL version support SSL requests. + if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) + return false; + } + + /** + * Filter whether cURL can be used as a transport for retrieving a URL. + * + * @since 2.7.0 + * + * @param bool $use_class Whether the class can be used. Default true. + * @param array $args An array of request arguments. + */ + return apply_filters( 'use_curl_transport', true, $args ); + } +} diff --git a/wp-includes/class-wp-http-encoding.php b/wp-includes/class-wp-http-encoding.php new file mode 100644 index 0000000000..95fc757665 --- /dev/null +++ b/wp-includes/class-wp-http-encoding.php @@ -0,0 +1,222 @@ + 0 ) { + if ( $flg & 4 ) { + list($xlen) = unpack('v', substr($gzData, $i, 2) ); + $i = $i + 2 + $xlen; + } + if ( $flg & 8 ) + $i = strpos($gzData, "\0", $i) + 1; + if ( $flg & 16 ) + $i = strpos($gzData, "\0", $i) + 1; + if ( $flg & 2 ) + $i = $i + 2; + } + $decompressed = @gzinflate( substr($gzData, $i, -8) ); + if ( false !== $decompressed ) + return $decompressed; + } + + // Compressed data from java.util.zip.Deflater amongst others. + $decompressed = @gzinflate( substr($gzData, 2) ); + if ( false !== $decompressed ) + return $decompressed; + + return false; + } + + /** + * What encoding types to accept and their priority values. + * + * @since 2.8.0 + * + * @static + * + * @param string $url + * @param array $args + * @return string Types of encoding to accept. + */ + public static function accept_encoding( $url, $args ) { + $type = array(); + $compression_enabled = self::is_available(); + + if ( ! $args['decompress'] ) // Decompression specifically disabled. + $compression_enabled = false; + elseif ( $args['stream'] ) // Disable when streaming to file. + $compression_enabled = false; + elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it. + $compression_enabled = false; + + if ( $compression_enabled ) { + if ( function_exists( 'gzinflate' ) ) + $type[] = 'deflate;q=1.0'; + + if ( function_exists( 'gzuncompress' ) ) + $type[] = 'compress;q=0.5'; + + if ( function_exists( 'gzdecode' ) ) + $type[] = 'gzip;q=0.5'; + } + + /** + * Filter the allowed encoding types. + * + * @since 3.6.0 + * + * @param array $type Encoding types allowed. Accepts 'gzinflate', + * 'gzuncompress', 'gzdecode'. + * @param string $url URL of the HTTP request. + * @param array $args HTTP request arguments. + */ + $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args ); + + return implode(', ', $type); + } + + /** + * What encoding the content used when it was compressed to send in the headers. + * + * @since 2.8.0 + * + * @static + * + * @return string Content-Encoding string to send in the header. + */ + public static function content_encoding() { + return 'deflate'; + } + + /** + * Whether the content be decoded based on the headers. + * + * @since 2.8.0 + * + * @static + * + * @param array|string $headers All of the available headers. + * @return bool + */ + public static function should_decode($headers) { + if ( is_array( $headers ) ) { + if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) ) + return true; + } elseif ( is_string( $headers ) ) { + return ( stripos($headers, 'content-encoding:') !== false ); + } + + return false; + } + + /** + * Whether decompression and compression are supported by the PHP version. + * + * Each function is tested instead of checking for the zlib extension, to + * ensure that the functions all exist in the PHP version and aren't + * disabled. + * + * @since 2.8.0 + * + * @static + * + * @return bool + */ + public static function is_available() { + return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') ); + } +} diff --git a/wp-includes/class-wp-http-proxy.php b/wp-includes/class-wp-http-proxy.php new file mode 100644 index 0000000000..155612195e --- /dev/null +++ b/wp-includes/class-wp-http-proxy.php @@ -0,0 +1,210 @@ + + *
  • WP_PROXY_HOST - Enable proxy support and host for connecting.
  • + *
  • WP_PROXY_PORT - Proxy port for connection. No default, must be defined.
  • + *
  • WP_PROXY_USERNAME - Proxy username, if it requires authentication.
  • + *
  • WP_PROXY_PASSWORD - Proxy password, if it requires authentication.
  • + *
  • WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy. + * You do not need to have localhost and the blog host in this list, because they will not be passed + * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org
  • + * + * + * An example can be as seen below. + * + * define('WP_PROXY_HOST', '192.168.84.101'); + * define('WP_PROXY_PORT', '8080'); + * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org'); + * + * @link https://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress. + * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS + * @since 2.8.0 + */ +class WP_HTTP_Proxy { + + /** + * Whether proxy connection should be used. + * + * @since 2.8.0 + * + * @use WP_PROXY_HOST + * @use WP_PROXY_PORT + * + * @return bool + */ + public function is_enabled() { + return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT'); + } + + /** + * Whether authentication should be used. + * + * @since 2.8.0 + * + * @use WP_PROXY_USERNAME + * @use WP_PROXY_PASSWORD + * + * @return bool + */ + public function use_authentication() { + return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD'); + } + + /** + * Retrieve the host for the proxy server. + * + * @since 2.8.0 + * + * @return string + */ + public function host() { + if ( defined('WP_PROXY_HOST') ) + return WP_PROXY_HOST; + + return ''; + } + + /** + * Retrieve the port for the proxy server. + * + * @since 2.8.0 + * + * @return string + */ + public function port() { + if ( defined('WP_PROXY_PORT') ) + return WP_PROXY_PORT; + + return ''; + } + + /** + * Retrieve the username for proxy authentication. + * + * @since 2.8.0 + * + * @return string + */ + public function username() { + if ( defined('WP_PROXY_USERNAME') ) + return WP_PROXY_USERNAME; + + return ''; + } + + /** + * Retrieve the password for proxy authentication. + * + * @since 2.8.0 + * + * @return string + */ + public function password() { + if ( defined('WP_PROXY_PASSWORD') ) + return WP_PROXY_PASSWORD; + + return ''; + } + + /** + * Retrieve authentication string for proxy authentication. + * + * @since 2.8.0 + * + * @return string + */ + public function authentication() { + return $this->username() . ':' . $this->password(); + } + + /** + * Retrieve header string for proxy authentication. + * + * @since 2.8.0 + * + * @return string + */ + public function authentication_header() { + return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() ); + } + + /** + * Whether URL should be sent through the proxy server. + * + * We want to keep localhost and the blog URL from being sent through the proxy server, because + * some proxies can not handle this. We also have the constant available for defining other + * hosts that won't be sent through the proxy. + * + * @since 2.8.0 + * + * @staticvar array|null $bypass_hosts + * @staticvar array $wildcard_regex + * + * @param string $uri URI to check. + * @return bool True, to send through the proxy and false if, the proxy should not be used. + */ + public function send_through_proxy( $uri ) { + /* + * parse_url() only handles http, https type URLs, and will emit E_WARNING on failure. + * This will be displayed on blogs, which is not reasonable. + */ + $check = @parse_url($uri); + + // Malformed URL, can not process, but this could mean ssl, so let through anyway. + if ( $check === false ) + return true; + + $home = parse_url( get_option('siteurl') ); + + /** + * Filter whether to preempt sending the request through the proxy server. + * + * Returning false will bypass the proxy; returning true will send + * the request through the proxy. Returning null bypasses the filter. + * + * @since 3.5.0 + * + * @param null $override Whether to override the request result. Default null. + * @param string $uri URL to check. + * @param array $check Associative array result of parsing the URI. + * @param array $home Associative array result of parsing the site URL. + */ + $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home ); + if ( ! is_null( $result ) ) + return $result; + + if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) + return false; + + if ( !defined('WP_PROXY_BYPASS_HOSTS') ) + return true; + + static $bypass_hosts = null; + static $wildcard_regex = array(); + if ( null === $bypass_hosts ) { + $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS); + + if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) { + $wildcard_regex = array(); + foreach ( $bypass_hosts as $host ) + $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); + $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i'; + } + } + + if ( !empty($wildcard_regex) ) + return !preg_match($wildcard_regex, $check['host']); + else + return !in_array( $check['host'], $bypass_hosts ); + } +} diff --git a/wp-includes/class-wp-http-streams.php b/wp-includes/class-wp-http-streams.php new file mode 100644 index 0000000000..96a2e74454 --- /dev/null +++ b/wp-includes/class-wp-http-streams.php @@ -0,0 +1,419 @@ + 'GET', 'timeout' => 5, + 'redirection' => 5, 'httpversion' => '1.0', + 'blocking' => true, + 'headers' => array(), 'body' => null, 'cookies' => array() + ); + + $r = wp_parse_args( $args, $defaults ); + + if ( isset( $r['headers']['User-Agent'] ) ) { + $r['user-agent'] = $r['headers']['User-Agent']; + unset( $r['headers']['User-Agent'] ); + } elseif ( isset( $r['headers']['user-agent'] ) ) { + $r['user-agent'] = $r['headers']['user-agent']; + unset( $r['headers']['user-agent'] ); + } + + // Construct Cookie: header if any cookies are set. + WP_Http::buildCookieHeader( $r ); + + $arrURL = parse_url($url); + + $connect_host = $arrURL['host']; + + $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ); + if ( ! isset( $arrURL['port'] ) ) { + if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) { + $arrURL['port'] = 443; + $secure_transport = true; + } else { + $arrURL['port'] = 80; + } + } + + // Always pass a Path, defaulting to the root in cases such as http://example.com + if ( ! isset( $arrURL['path'] ) ) { + $arrURL['path'] = '/'; + } + + if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) { + if ( isset( $r['headers']['Host'] ) ) + $arrURL['host'] = $r['headers']['Host']; + else + $arrURL['host'] = $r['headers']['host']; + unset( $r['headers']['Host'], $r['headers']['host'] ); + } + + /* + * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect + * to ::1, which fails when the server is not set up for it. For compatibility, always + * connect to the IPv4 address. + */ + if ( 'localhost' == strtolower( $connect_host ) ) + $connect_host = '127.0.0.1'; + + $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host; + + $is_local = isset( $r['local'] ) && $r['local']; + $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify']; + if ( $is_local ) { + /** + * Filter whether SSL should be verified for local requests. + * + * @since 2.8.0 + * + * @param bool $ssl_verify Whether to verify the SSL connection. Default true. + */ + $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); + } elseif ( ! $is_local ) { + /** + * Filter whether SSL should be verified for non-local requests. + * + * @since 2.8.0 + * + * @param bool $ssl_verify Whether to verify the SSL connection. Default true. + */ + $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); + } + + $proxy = new WP_HTTP_Proxy(); + + $context = stream_context_create( array( + 'ssl' => array( + 'verify_peer' => $ssl_verify, + //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate() + 'capture_peer_cert' => $ssl_verify, + 'SNI_enabled' => true, + 'cafile' => $r['sslcertificates'], + 'allow_self_signed' => ! $ssl_verify, + ) + ) ); + + $timeout = (int) floor( $r['timeout'] ); + $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000; + $connect_timeout = max( $timeout, 1 ); + + // Store error number. + $connection_error = null; + + // Store error string. + $connection_error_str = null; + + if ( !WP_DEBUG ) { + // In the event that the SSL connection fails, silence the many PHP Warnings. + if ( $secure_transport ) + $error_reporting = error_reporting(0); + + if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) + $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); + else + $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); + + if ( $secure_transport ) + error_reporting( $error_reporting ); + + } else { + if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) + $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); + else + $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); + } + + if ( false === $handle ) { + // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken. + if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str ) + return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); + + return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str ); + } + + // Verify that the SSL certificate is valid for this request. + if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) { + if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) ) + return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); + } + + stream_set_timeout( $handle, $timeout, $utimeout ); + + if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field. + $requestPath = $url; + else + $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' ); + + $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n"; + + $include_port_in_host_header = ( + ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) || + ( 'http' == $arrURL['scheme'] && 80 != $arrURL['port'] ) || + ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] ) + ); + + if ( $include_port_in_host_header ) { + $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n"; + } else { + $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n"; + } + + if ( isset($r['user-agent']) ) + $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n"; + + if ( is_array($r['headers']) ) { + foreach ( (array) $r['headers'] as $header => $headerValue ) + $strHeaders .= $header . ': ' . $headerValue . "\r\n"; + } else { + $strHeaders .= $r['headers']; + } + + if ( $proxy->use_authentication() ) + $strHeaders .= $proxy->authentication_header() . "\r\n"; + + $strHeaders .= "\r\n"; + + if ( ! is_null($r['body']) ) + $strHeaders .= $r['body']; + + fwrite($handle, $strHeaders); + + if ( ! $r['blocking'] ) { + stream_set_blocking( $handle, 0 ); + fclose( $handle ); + return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); + } + + $strResponse = ''; + $bodyStarted = false; + $keep_reading = true; + $block_size = 4096; + if ( isset( $r['limit_response_size'] ) ) + $block_size = min( $block_size, $r['limit_response_size'] ); + + // If streaming to a file setup the file handle. + if ( $r['stream'] ) { + if ( ! WP_DEBUG ) + $stream_handle = @fopen( $r['filename'], 'w+' ); + else + $stream_handle = fopen( $r['filename'], 'w+' ); + if ( ! $stream_handle ) + return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); + + $bytes_written = 0; + while ( ! feof($handle) && $keep_reading ) { + $block = fread( $handle, $block_size ); + if ( ! $bodyStarted ) { + $strResponse .= $block; + if ( strpos( $strResponse, "\r\n\r\n" ) ) { + $process = WP_Http::processResponse( $strResponse ); + $bodyStarted = true; + $block = $process['body']; + unset( $strResponse ); + $process['body'] = ''; + } + } + + $this_block_size = strlen( $block ); + + if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) { + $this_block_size = ( $r['limit_response_size'] - $bytes_written ); + $block = substr( $block, 0, $this_block_size ); + } + + $bytes_written_to_file = fwrite( $stream_handle, $block ); + + if ( $bytes_written_to_file != $this_block_size ) { + fclose( $handle ); + fclose( $stream_handle ); + return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); + } + + $bytes_written += $bytes_written_to_file; + + $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size']; + } + + fclose( $stream_handle ); + + } else { + $header_length = 0; + while ( ! feof( $handle ) && $keep_reading ) { + $block = fread( $handle, $block_size ); + $strResponse .= $block; + if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) { + $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4; + $bodyStarted = true; + } + $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) ); + } + + $process = WP_Http::processResponse( $strResponse ); + unset( $strResponse ); + + } + + fclose( $handle ); + + $arrHeaders = WP_Http::processHeaders( $process['headers'], $url ); + + $response = array( + 'headers' => $arrHeaders['headers'], + // Not yet processed. + 'body' => null, + 'response' => $arrHeaders['response'], + 'cookies' => $arrHeaders['cookies'], + 'filename' => $r['filename'] + ); + + // Handle redirects. + if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) + return $redirect_response; + + // If the body was chunk encoded, then decode it. + if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] ) + $process['body'] = WP_Http::chunkTransferDecode($process['body']); + + if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) ) + $process['body'] = WP_Http_Encoding::decompress( $process['body'] ); + + if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] ) + $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] ); + + $response['body'] = $process['body']; + + return $response; + } + + /** + * Verifies the received SSL certificate against it's Common Names and subjectAltName fields + * + * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if + * the certificate is valid for the hostname which was requested. + * This function verifies the requested hostname against certificate's subjectAltName field, + * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used. + * + * IP Address support is included if the request is being made to an IP address. + * + * @since 3.7.0 + * @static + * + * @param stream $stream The PHP Stream which the SSL request is being made over + * @param string $host The hostname being requested + * @return bool If the cerficiate presented in $stream is valid for $host + */ + public static function verify_ssl_certificate( $stream, $host ) { + $context_options = stream_context_get_options( $stream ); + + if ( empty( $context_options['ssl']['peer_certificate'] ) ) + return false; + + $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] ); + if ( ! $cert ) + return false; + + /* + * If the request is being made to an IP address, we'll validate against IP fields + * in the cert (if they exist) + */ + $host_type = ( WP_HTTP::is_ip_address( $host ) ? 'ip' : 'dns' ); + + $certificate_hostnames = array(); + if ( ! empty( $cert['extensions']['subjectAltName'] ) ) { + $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] ); + foreach ( $match_against as $match ) { + list( $match_type, $match_host ) = explode( ':', $match ); + if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS: + $certificate_hostnames[] = strtolower( trim( $match_host ) ); + } + } elseif ( !empty( $cert['subject']['CN'] ) ) { + // Only use the CN when the certificate includes no subjectAltName extension. + $certificate_hostnames[] = strtolower( $cert['subject']['CN'] ); + } + + // Exact hostname/IP matches. + if ( in_array( strtolower( $host ), $certificate_hostnames ) ) + return true; + + // IP's can't be wildcards, Stop processing. + if ( 'ip' == $host_type ) + return false; + + // Test to see if the domain is at least 2 deep for wildcard support. + if ( substr_count( $host, '.' ) < 2 ) + return false; + + // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com. + $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host ); + + return in_array( strtolower( $wildcard_host ), $certificate_hostnames ); + } + + /** + * Whether this class can be used for retrieving a URL. + * + * @static + * @access public + * @since 2.7.0 + * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). + * + * @return bool False means this class can not be used, true means it can. + */ + public static function test( $args = array() ) { + if ( ! function_exists( 'stream_socket_client' ) ) + return false; + + $is_ssl = isset( $args['ssl'] ) && $args['ssl']; + + if ( $is_ssl ) { + if ( ! extension_loaded( 'openssl' ) ) + return false; + if ( ! function_exists( 'openssl_x509_parse' ) ) + return false; + } + + /** + * Filter whether streams can be used as a transport for retrieving a URL. + * + * @since 2.7.0 + * + * @param bool $use_class Whether the class can be used. Default true. + * @param array $args Request arguments. + */ + return apply_filters( 'use_streams_transport', true, $args ); + } +} + +/** + * Deprecated HTTP Transport method which used fsockopen. + * + * This class is not used, and is included for backwards compatibility only. + * All code should make use of WP_HTTP directly through it's API. + * + * @see WP_HTTP::request + * + * @since 2.7.0 + * @deprecated 3.7.0 Please use WP_HTTP::request() directly + */ +class WP_HTTP_Fsockopen extends WP_HTTP_Streams { + // For backwards compatibility for users who are using the class directly. +} diff --git a/wp-includes/http-functions.php b/wp-includes/http-functions.php new file mode 100644 index 0000000000..4768c7fdd7 --- /dev/null +++ b/wp-includes/http-functions.php @@ -0,0 +1,558 @@ +request( $url, $args ); +} + +/** + * Retrieve the raw response from a safe HTTP request using the GET method. + * + * This function is ideal when the HTTP request is being made to an arbitrary + * URL. The URL is validated to avoid redirection and request forgery attacks. + * + * @since 3.6.0 + * + * @see wp_remote_request() For more information on the response array format. + * @see WP_Http::request() For default arguments information. + * + * @param string $url Site URL to retrieve. + * @param array $args Optional. Request arguments. Default empty array. + * @return WP_Error|array The response or WP_Error on failure. + */ +function wp_safe_remote_get( $url, $args = array() ) { + $args['reject_unsafe_urls'] = true; + $http = _wp_http_get_object(); + return $http->get( $url, $args ); +} + +/** + * Retrieve the raw response from a safe HTTP request using the POST method. + * + * This function is ideal when the HTTP request is being made to an arbitrary + * URL. The URL is validated to avoid redirection and request forgery attacks. + * + * @since 3.6.0 + * + * @see wp_remote_request() For more information on the response array format. + * @see WP_Http::request() For default arguments information. + * + * @param string $url Site URL to retrieve. + * @param array $args Optional. Request arguments. Default empty array. + * @return WP_Error|array The response or WP_Error on failure. + */ +function wp_safe_remote_post( $url, $args = array() ) { + $args['reject_unsafe_urls'] = true; + $http = _wp_http_get_object(); + return $http->post( $url, $args ); +} + +/** + * Retrieve the raw response from a safe HTTP request using the HEAD method. + * + * This function is ideal when the HTTP request is being made to an arbitrary + * URL. The URL is validated to avoid redirection and request forgery attacks. + * + * @since 3.6.0 + * + * @see wp_remote_request() For more information on the response array format. + * @see WP_Http::request() For default arguments information. + * + * @param string $url Site URL to retrieve. + * @param array $args Optional. Request arguments. Default empty array. + * @return WP_Error|array The response or WP_Error on failure. + */ +function wp_safe_remote_head( $url, $args = array() ) { + $args['reject_unsafe_urls'] = true; + $http = _wp_http_get_object(); + return $http->head( $url, $args ); +} + +/** + * Retrieve the raw response from the HTTP request. + * + * The array structure is a little complex: + * + * $res = array( + * 'headers' => array(), + * 'response' => array( + * 'code' => int, + * 'message' => string + * ) + * ); + * + * All of the headers in $res['headers'] are with the name as the key and the + * value as the value. So to get the User-Agent, you would do the following. + * + * $user_agent = $res['headers']['user-agent']; + * + * The body is the raw response content and can be retrieved from $res['body']. + * + * This function is called first to make the request and there are other API + * functions to abstract out the above convoluted setup. + * + * Request method defaults for helper functions: + * - Default 'GET' for wp_remote_get() + * - Default 'POST' for wp_remote_post() + * - Default 'HEAD' for wp_remote_head() + * + * @since 2.7.0 + * + * @see WP_Http::request() For additional information on default arguments. + * + * @param string $url Site URL to retrieve. + * @param array $args Optional. Request arguments. Default empty array. + * @return WP_Error|array The response or WP_Error on failure. + */ +function wp_remote_request($url, $args = array()) { + $http = _wp_http_get_object(); + return $http->request( $url, $args ); +} + +/** + * Retrieve the raw response from the HTTP request using the GET method. + * + * @since 2.7.0 + * + * @see wp_remote_request() For more information on the response array format. + * @see WP_Http::request() For default arguments information. + * + * @param string $url Site URL to retrieve. + * @param array $args Optional. Request arguments. Default empty array. + * @return WP_Error|array The response or WP_Error on failure. + */ +function wp_remote_get($url, $args = array()) { + $http = _wp_http_get_object(); + return $http->get( $url, $args ); +} + +/** + * Retrieve the raw response from the HTTP request using the POST method. + * + * @since 2.7.0 + * + * @see wp_remote_request() For more information on the response array format. + * @see WP_Http::request() For default arguments information. + * + * @param string $url Site URL to retrieve. + * @param array $args Optional. Request arguments. Default empty array. + * @return WP_Error|array The response or WP_Error on failure. + */ +function wp_remote_post($url, $args = array()) { + $http = _wp_http_get_object(); + return $http->post( $url, $args ); +} + +/** + * Retrieve the raw response from the HTTP request using the HEAD method. + * + * @since 2.7.0 + * + * @see wp_remote_request() For more information on the response array format. + * @see WP_Http::request() For default arguments information. + * + * @param string $url Site URL to retrieve. + * @param array $args Optional. Request arguments. Default empty array. + * @return WP_Error|array The response or WP_Error on failure. + */ +function wp_remote_head($url, $args = array()) { + $http = _wp_http_get_object(); + return $http->head( $url, $args ); +} + +/** + * Retrieve only the headers from the raw response. + * + * @since 2.7.0 + * + * @param array $response HTTP response. + * @return array The headers of the response. Empty array if incorrect parameter given. + */ +function wp_remote_retrieve_headers( $response ) { + if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers'])) + return array(); + + return $response['headers']; +} + +/** + * Retrieve a single header by name from the raw response. + * + * @since 2.7.0 + * + * @param array $response + * @param string $header Header name to retrieve value from. + * @return string The header value. Empty string on if incorrect parameter given, or if the header doesn't exist. + */ +function wp_remote_retrieve_header( $response, $header ) { + if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers'])) + return ''; + + if ( array_key_exists($header, $response['headers']) ) + return $response['headers'][$header]; + + return ''; +} + +/** + * Retrieve only the response code from the raw response. + * + * Will return an empty array if incorrect parameter value is given. + * + * @since 2.7.0 + * + * @param array $response HTTP response. + * @return int|string The response code as an integer. Empty string on incorrect parameter given. + */ +function wp_remote_retrieve_response_code( $response ) { + if ( is_wp_error($response) || ! isset($response['response']) || ! is_array($response['response'])) + return ''; + + return $response['response']['code']; +} + +/** + * Retrieve only the response message from the raw response. + * + * Will return an empty array if incorrect parameter value is given. + * + * @since 2.7.0 + * + * @param array $response HTTP response. + * @return string The response message. Empty string on incorrect parameter given. + */ +function wp_remote_retrieve_response_message( $response ) { + if ( is_wp_error($response) || ! isset($response['response']) || ! is_array($response['response'])) + return ''; + + return $response['response']['message']; +} + +/** + * Retrieve only the body from the raw response. + * + * @since 2.7.0 + * + * @param array $response HTTP response. + * @return string The body of the response. Empty string if no body or incorrect parameter given. + */ +function wp_remote_retrieve_body( $response ) { + if ( is_wp_error($response) || ! isset($response['body']) ) + return ''; + + return $response['body']; +} + +/** + * Determines if there is an HTTP Transport that can process this request. + * + * @since 3.2.0 + * + * @param array $capabilities Array of capabilities to test or a wp_remote_request() $args array. + * @param string $url Optional. If given, will check if the URL requires SSL and adds + * that requirement to the capabilities array. + * + * @return bool + */ +function wp_http_supports( $capabilities = array(), $url = null ) { + $http = _wp_http_get_object(); + + $capabilities = wp_parse_args( $capabilities ); + + $count = count( $capabilities ); + + // If we have a numeric $capabilities array, spoof a wp_remote_request() associative $args array + if ( $count && count( array_filter( array_keys( $capabilities ), 'is_numeric' ) ) == $count ) { + $capabilities = array_combine( array_values( $capabilities ), array_fill( 0, $count, true ) ); + } + + if ( $url && !isset( $capabilities['ssl'] ) ) { + $scheme = parse_url( $url, PHP_URL_SCHEME ); + if ( 'https' == $scheme || 'ssl' == $scheme ) { + $capabilities['ssl'] = true; + } + } + + return (bool) $http->_get_first_available_transport( $capabilities ); +} + +/** + * Get the HTTP Origin of the current request. + * + * @since 3.4.0 + * + * @return string URL of the origin. Empty string if no origin. + */ +function get_http_origin() { + $origin = ''; + if ( ! empty ( $_SERVER[ 'HTTP_ORIGIN' ] ) ) + $origin = $_SERVER[ 'HTTP_ORIGIN' ]; + + /** + * Change the origin of an HTTP request. + * + * @since 3.4.0 + * + * @param string $origin The original origin for the request. + */ + return apply_filters( 'http_origin', $origin ); +} + +/** + * Retrieve list of allowed HTTP origins. + * + * @since 3.4.0 + * + * @return array Array of origin URLs. + */ +function get_allowed_http_origins() { + $admin_origin = parse_url( admin_url() ); + $home_origin = parse_url( home_url() ); + + // @todo preserve port? + $allowed_origins = array_unique( array( + 'http://' . $admin_origin[ 'host' ], + 'https://' . $admin_origin[ 'host' ], + 'http://' . $home_origin[ 'host' ], + 'https://' . $home_origin[ 'host' ], + ) ); + + /** + * Change the origin types allowed for HTTP requests. + * + * @since 3.4.0 + * + * @param array $allowed_origins { + * Default allowed HTTP origins. + * @type string Non-secure URL for admin origin. + * @type string Secure URL for admin origin. + * @type string Non-secure URL for home origin. + * @type string Secure URL for home origin. + * } + */ + return apply_filters( 'allowed_http_origins' , $allowed_origins ); +} + +/** + * Determines if the HTTP origin is an authorized one. + * + * @since 3.4.0 + * + * @param null|string $origin Origin URL. If not provided, the value of get_http_origin() is used. + * @return string True if the origin is allowed. False otherwise. + */ +function is_allowed_http_origin( $origin = null ) { + $origin_arg = $origin; + + if ( null === $origin ) + $origin = get_http_origin(); + + if ( $origin && ! in_array( $origin, get_allowed_http_origins() ) ) + $origin = ''; + + /** + * Change the allowed HTTP origin result. + * + * @since 3.4.0 + * + * @param string $origin Result of check for allowed origin. + * @param string $origin_arg Original origin string passed into is_allowed_http_origin function. + */ + return apply_filters( 'allowed_http_origin', $origin, $origin_arg ); +} + +/** + * Send Access-Control-Allow-Origin and related headers if the current request + * is from an allowed origin. + * + * If the request is an OPTIONS request, the script exits with either access + * control headers sent, or a 403 response if the origin is not allowed. For + * other request methods, you will receive a return value. + * + * @since 3.4.0 + * + * @return string|false Returns the origin URL if headers are sent. Returns false + * if headers are not sent. + */ +function send_origin_headers() { + $origin = get_http_origin(); + + if ( is_allowed_http_origin( $origin ) ) { + @header( 'Access-Control-Allow-Origin: ' . $origin ); + @header( 'Access-Control-Allow-Credentials: true' ); + if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) + exit; + return $origin; + } + + if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) { + status_header( 403 ); + exit; + } + + return false; +} + +/** + * Validate a URL for safe use in the HTTP API. + * + * @since 3.5.2 + * + * @param string $url + * @return false|string URL or false on failure. + */ +function wp_http_validate_url( $url ) { + $original_url = $url; + $url = wp_kses_bad_protocol( $url, array( 'http', 'https' ) ); + if ( ! $url || strtolower( $url ) !== strtolower( $original_url ) ) + return false; + + $parsed_url = @parse_url( $url ); + if ( ! $parsed_url || empty( $parsed_url['host'] ) ) + return false; + + if ( isset( $parsed_url['user'] ) || isset( $parsed_url['pass'] ) ) + return false; + + if ( false !== strpbrk( $parsed_url['host'], ':#?[]' ) ) + return false; + + $parsed_home = @parse_url( get_option( 'home' ) ); + + $same_host = strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] ); + + if ( ! $same_host ) { + $host = trim( $parsed_url['host'], '.' ); + if ( preg_match( '#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $host ) ) { + $ip = $host; + } else { + $ip = gethostbyname( $host ); + if ( $ip === $host ) // Error condition for gethostbyname() + $ip = false; + } + if ( $ip ) { + $parts = array_map( 'intval', explode( '.', $ip ) ); + if ( 127 === $parts[0] || 10 === $parts[0] + || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] ) + || ( 192 === $parts[0] && 168 === $parts[1] ) + ) { + // If host appears local, reject unless specifically allowed. + /** + * Check if HTTP request is external or not. + * + * Allows to change and allow external requests for the HTTP request. + * + * @since 3.6.0 + * + * @param bool false Whether HTTP request is external or not. + * @param string $host IP of the requested host. + * @param string $url URL of the requested host. + */ + if ( ! apply_filters( 'http_request_host_is_external', false, $host, $url ) ) + return false; + } + } + } + + if ( empty( $parsed_url['port'] ) ) + return $url; + + $port = $parsed_url['port']; + if ( 80 === $port || 443 === $port || 8080 === $port ) + return $url; + + if ( $parsed_home && $same_host && isset( $parsed_home['port'] ) && $parsed_home['port'] === $port ) + return $url; + + return false; +} + +/** + * Whitelists allowed redirect hosts for safe HTTP requests as well. + * + * Attached to the http_request_host_is_external filter. + * + * @since 3.6.0 + * + * @param bool $is_external + * @param string $host + * @return bool + */ +function allowed_http_request_hosts( $is_external, $host ) { + if ( ! $is_external && wp_validate_redirect( 'http://' . $host ) ) + $is_external = true; + return $is_external; +} + +/** + * Whitelists any domain in a multisite installation for safe HTTP requests. + * + * Attached to the http_request_host_is_external filter. + * + * @since 3.6.0 + * + * @global wpdb $wpdb + * @staticvar array $queried + * + * @param bool $is_external + * @param string $host + * @return bool + */ +function ms_allowed_http_request_hosts( $is_external, $host ) { + global $wpdb; + static $queried = array(); + if ( $is_external ) + return $is_external; + if ( $host === get_current_site()->domain ) + return true; + if ( isset( $queried[ $host ] ) ) + return $queried[ $host ]; + $queried[ $host ] = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT domain FROM $wpdb->blogs WHERE domain = %s LIMIT 1", $host ) ); + return $queried[ $host ]; +} diff --git a/wp-includes/http.php b/wp-includes/http.php index 4768c7fdd7..99599047d8 100644 --- a/wp-includes/http.php +++ b/wp-includes/http.php @@ -2,7 +2,8 @@ /** * Simple and uniform HTTP request API. * - * Will eventually replace and standardize the WordPress HTTP requests made. + * Standardizes the HTTP requests for WordPress. Handles cookies, gzip encoding and decoding, chunk + * decoding, if HTTP 1.1 and various other difficult HTTP protocol implementations. * * @link https://core.trac.wordpress.org/ticket/4779 HTTP API Proposal * @@ -11,548 +12,10 @@ * @since 2.7.0 */ -/** - * Returns the initialized WP_Http Object - * - * @since 2.7.0 - * @access private - * - * @staticvar WP_Http $http - * - * @return WP_Http HTTP Transport object. - */ -function _wp_http_get_object() { - static $http = null; - - if ( is_null( $http ) ) { - $http = new WP_Http(); - } - return $http; -} - -/** - * Retrieve the raw response from a safe HTTP request. - * - * This function is ideal when the HTTP request is being made to an arbitrary - * URL. The URL is validated to avoid redirection and request forgery attacks. - * - * @since 3.6.0 - * - * @see wp_remote_request() For more information on the response array format. - * @see WP_Http::request() For default arguments information. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_safe_remote_request( $url, $args = array() ) { - $args['reject_unsafe_urls'] = true; - $http = _wp_http_get_object(); - return $http->request( $url, $args ); -} - -/** - * Retrieve the raw response from a safe HTTP request using the GET method. - * - * This function is ideal when the HTTP request is being made to an arbitrary - * URL. The URL is validated to avoid redirection and request forgery attacks. - * - * @since 3.6.0 - * - * @see wp_remote_request() For more information on the response array format. - * @see WP_Http::request() For default arguments information. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_safe_remote_get( $url, $args = array() ) { - $args['reject_unsafe_urls'] = true; - $http = _wp_http_get_object(); - return $http->get( $url, $args ); -} - -/** - * Retrieve the raw response from a safe HTTP request using the POST method. - * - * This function is ideal when the HTTP request is being made to an arbitrary - * URL. The URL is validated to avoid redirection and request forgery attacks. - * - * @since 3.6.0 - * - * @see wp_remote_request() For more information on the response array format. - * @see WP_Http::request() For default arguments information. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_safe_remote_post( $url, $args = array() ) { - $args['reject_unsafe_urls'] = true; - $http = _wp_http_get_object(); - return $http->post( $url, $args ); -} - -/** - * Retrieve the raw response from a safe HTTP request using the HEAD method. - * - * This function is ideal when the HTTP request is being made to an arbitrary - * URL. The URL is validated to avoid redirection and request forgery attacks. - * - * @since 3.6.0 - * - * @see wp_remote_request() For more information on the response array format. - * @see WP_Http::request() For default arguments information. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_safe_remote_head( $url, $args = array() ) { - $args['reject_unsafe_urls'] = true; - $http = _wp_http_get_object(); - return $http->head( $url, $args ); -} - -/** - * Retrieve the raw response from the HTTP request. - * - * The array structure is a little complex: - * - * $res = array( - * 'headers' => array(), - * 'response' => array( - * 'code' => int, - * 'message' => string - * ) - * ); - * - * All of the headers in $res['headers'] are with the name as the key and the - * value as the value. So to get the User-Agent, you would do the following. - * - * $user_agent = $res['headers']['user-agent']; - * - * The body is the raw response content and can be retrieved from $res['body']. - * - * This function is called first to make the request and there are other API - * functions to abstract out the above convoluted setup. - * - * Request method defaults for helper functions: - * - Default 'GET' for wp_remote_get() - * - Default 'POST' for wp_remote_post() - * - Default 'HEAD' for wp_remote_head() - * - * @since 2.7.0 - * - * @see WP_Http::request() For additional information on default arguments. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_remote_request($url, $args = array()) { - $http = _wp_http_get_object(); - return $http->request( $url, $args ); -} - -/** - * Retrieve the raw response from the HTTP request using the GET method. - * - * @since 2.7.0 - * - * @see wp_remote_request() For more information on the response array format. - * @see WP_Http::request() For default arguments information. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_remote_get($url, $args = array()) { - $http = _wp_http_get_object(); - return $http->get( $url, $args ); -} - -/** - * Retrieve the raw response from the HTTP request using the POST method. - * - * @since 2.7.0 - * - * @see wp_remote_request() For more information on the response array format. - * @see WP_Http::request() For default arguments information. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_remote_post($url, $args = array()) { - $http = _wp_http_get_object(); - return $http->post( $url, $args ); -} - -/** - * Retrieve the raw response from the HTTP request using the HEAD method. - * - * @since 2.7.0 - * - * @see wp_remote_request() For more information on the response array format. - * @see WP_Http::request() For default arguments information. - * - * @param string $url Site URL to retrieve. - * @param array $args Optional. Request arguments. Default empty array. - * @return WP_Error|array The response or WP_Error on failure. - */ -function wp_remote_head($url, $args = array()) { - $http = _wp_http_get_object(); - return $http->head( $url, $args ); -} - -/** - * Retrieve only the headers from the raw response. - * - * @since 2.7.0 - * - * @param array $response HTTP response. - * @return array The headers of the response. Empty array if incorrect parameter given. - */ -function wp_remote_retrieve_headers( $response ) { - if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers'])) - return array(); - - return $response['headers']; -} - -/** - * Retrieve a single header by name from the raw response. - * - * @since 2.7.0 - * - * @param array $response - * @param string $header Header name to retrieve value from. - * @return string The header value. Empty string on if incorrect parameter given, or if the header doesn't exist. - */ -function wp_remote_retrieve_header( $response, $header ) { - if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers'])) - return ''; - - if ( array_key_exists($header, $response['headers']) ) - return $response['headers'][$header]; - - return ''; -} - -/** - * Retrieve only the response code from the raw response. - * - * Will return an empty array if incorrect parameter value is given. - * - * @since 2.7.0 - * - * @param array $response HTTP response. - * @return int|string The response code as an integer. Empty string on incorrect parameter given. - */ -function wp_remote_retrieve_response_code( $response ) { - if ( is_wp_error($response) || ! isset($response['response']) || ! is_array($response['response'])) - return ''; - - return $response['response']['code']; -} - -/** - * Retrieve only the response message from the raw response. - * - * Will return an empty array if incorrect parameter value is given. - * - * @since 2.7.0 - * - * @param array $response HTTP response. - * @return string The response message. Empty string on incorrect parameter given. - */ -function wp_remote_retrieve_response_message( $response ) { - if ( is_wp_error($response) || ! isset($response['response']) || ! is_array($response['response'])) - return ''; - - return $response['response']['message']; -} - -/** - * Retrieve only the body from the raw response. - * - * @since 2.7.0 - * - * @param array $response HTTP response. - * @return string The body of the response. Empty string if no body or incorrect parameter given. - */ -function wp_remote_retrieve_body( $response ) { - if ( is_wp_error($response) || ! isset($response['body']) ) - return ''; - - return $response['body']; -} - -/** - * Determines if there is an HTTP Transport that can process this request. - * - * @since 3.2.0 - * - * @param array $capabilities Array of capabilities to test or a wp_remote_request() $args array. - * @param string $url Optional. If given, will check if the URL requires SSL and adds - * that requirement to the capabilities array. - * - * @return bool - */ -function wp_http_supports( $capabilities = array(), $url = null ) { - $http = _wp_http_get_object(); - - $capabilities = wp_parse_args( $capabilities ); - - $count = count( $capabilities ); - - // If we have a numeric $capabilities array, spoof a wp_remote_request() associative $args array - if ( $count && count( array_filter( array_keys( $capabilities ), 'is_numeric' ) ) == $count ) { - $capabilities = array_combine( array_values( $capabilities ), array_fill( 0, $count, true ) ); - } - - if ( $url && !isset( $capabilities['ssl'] ) ) { - $scheme = parse_url( $url, PHP_URL_SCHEME ); - if ( 'https' == $scheme || 'ssl' == $scheme ) { - $capabilities['ssl'] = true; - } - } - - return (bool) $http->_get_first_available_transport( $capabilities ); -} - -/** - * Get the HTTP Origin of the current request. - * - * @since 3.4.0 - * - * @return string URL of the origin. Empty string if no origin. - */ -function get_http_origin() { - $origin = ''; - if ( ! empty ( $_SERVER[ 'HTTP_ORIGIN' ] ) ) - $origin = $_SERVER[ 'HTTP_ORIGIN' ]; - - /** - * Change the origin of an HTTP request. - * - * @since 3.4.0 - * - * @param string $origin The original origin for the request. - */ - return apply_filters( 'http_origin', $origin ); -} - -/** - * Retrieve list of allowed HTTP origins. - * - * @since 3.4.0 - * - * @return array Array of origin URLs. - */ -function get_allowed_http_origins() { - $admin_origin = parse_url( admin_url() ); - $home_origin = parse_url( home_url() ); - - // @todo preserve port? - $allowed_origins = array_unique( array( - 'http://' . $admin_origin[ 'host' ], - 'https://' . $admin_origin[ 'host' ], - 'http://' . $home_origin[ 'host' ], - 'https://' . $home_origin[ 'host' ], - ) ); - - /** - * Change the origin types allowed for HTTP requests. - * - * @since 3.4.0 - * - * @param array $allowed_origins { - * Default allowed HTTP origins. - * @type string Non-secure URL for admin origin. - * @type string Secure URL for admin origin. - * @type string Non-secure URL for home origin. - * @type string Secure URL for home origin. - * } - */ - return apply_filters( 'allowed_http_origins' , $allowed_origins ); -} - -/** - * Determines if the HTTP origin is an authorized one. - * - * @since 3.4.0 - * - * @param null|string $origin Origin URL. If not provided, the value of get_http_origin() is used. - * @return string True if the origin is allowed. False otherwise. - */ -function is_allowed_http_origin( $origin = null ) { - $origin_arg = $origin; - - if ( null === $origin ) - $origin = get_http_origin(); - - if ( $origin && ! in_array( $origin, get_allowed_http_origins() ) ) - $origin = ''; - - /** - * Change the allowed HTTP origin result. - * - * @since 3.4.0 - * - * @param string $origin Result of check for allowed origin. - * @param string $origin_arg Original origin string passed into is_allowed_http_origin function. - */ - return apply_filters( 'allowed_http_origin', $origin, $origin_arg ); -} - -/** - * Send Access-Control-Allow-Origin and related headers if the current request - * is from an allowed origin. - * - * If the request is an OPTIONS request, the script exits with either access - * control headers sent, or a 403 response if the origin is not allowed. For - * other request methods, you will receive a return value. - * - * @since 3.4.0 - * - * @return string|false Returns the origin URL if headers are sent. Returns false - * if headers are not sent. - */ -function send_origin_headers() { - $origin = get_http_origin(); - - if ( is_allowed_http_origin( $origin ) ) { - @header( 'Access-Control-Allow-Origin: ' . $origin ); - @header( 'Access-Control-Allow-Credentials: true' ); - if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) - exit; - return $origin; - } - - if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) { - status_header( 403 ); - exit; - } - - return false; -} - -/** - * Validate a URL for safe use in the HTTP API. - * - * @since 3.5.2 - * - * @param string $url - * @return false|string URL or false on failure. - */ -function wp_http_validate_url( $url ) { - $original_url = $url; - $url = wp_kses_bad_protocol( $url, array( 'http', 'https' ) ); - if ( ! $url || strtolower( $url ) !== strtolower( $original_url ) ) - return false; - - $parsed_url = @parse_url( $url ); - if ( ! $parsed_url || empty( $parsed_url['host'] ) ) - return false; - - if ( isset( $parsed_url['user'] ) || isset( $parsed_url['pass'] ) ) - return false; - - if ( false !== strpbrk( $parsed_url['host'], ':#?[]' ) ) - return false; - - $parsed_home = @parse_url( get_option( 'home' ) ); - - $same_host = strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] ); - - if ( ! $same_host ) { - $host = trim( $parsed_url['host'], '.' ); - if ( preg_match( '#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $host ) ) { - $ip = $host; - } else { - $ip = gethostbyname( $host ); - if ( $ip === $host ) // Error condition for gethostbyname() - $ip = false; - } - if ( $ip ) { - $parts = array_map( 'intval', explode( '.', $ip ) ); - if ( 127 === $parts[0] || 10 === $parts[0] - || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] ) - || ( 192 === $parts[0] && 168 === $parts[1] ) - ) { - // If host appears local, reject unless specifically allowed. - /** - * Check if HTTP request is external or not. - * - * Allows to change and allow external requests for the HTTP request. - * - * @since 3.6.0 - * - * @param bool false Whether HTTP request is external or not. - * @param string $host IP of the requested host. - * @param string $url URL of the requested host. - */ - if ( ! apply_filters( 'http_request_host_is_external', false, $host, $url ) ) - return false; - } - } - } - - if ( empty( $parsed_url['port'] ) ) - return $url; - - $port = $parsed_url['port']; - if ( 80 === $port || 443 === $port || 8080 === $port ) - return $url; - - if ( $parsed_home && $same_host && isset( $parsed_home['port'] ) && $parsed_home['port'] === $port ) - return $url; - - return false; -} - -/** - * Whitelists allowed redirect hosts for safe HTTP requests as well. - * - * Attached to the http_request_host_is_external filter. - * - * @since 3.6.0 - * - * @param bool $is_external - * @param string $host - * @return bool - */ -function allowed_http_request_hosts( $is_external, $host ) { - if ( ! $is_external && wp_validate_redirect( 'http://' . $host ) ) - $is_external = true; - return $is_external; -} - -/** - * Whitelists any domain in a multisite installation for safe HTTP requests. - * - * Attached to the http_request_host_is_external filter. - * - * @since 3.6.0 - * - * @global wpdb $wpdb - * @staticvar array $queried - * - * @param bool $is_external - * @param string $host - * @return bool - */ -function ms_allowed_http_request_hosts( $is_external, $host ) { - global $wpdb; - static $queried = array(); - if ( $is_external ) - return $is_external; - if ( $host === get_current_site()->domain ) - return true; - if ( isset( $queried[ $host ] ) ) - return $queried[ $host ]; - $queried[ $host ] = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT domain FROM $wpdb->blogs WHERE domain = %s LIMIT 1", $host ) ); - return $queried[ $host ]; -} +require_once( ABSPATH . WPINC . '/http-functions.php' ); +require_once( ABSPATH . WPINC . '/class-http.php' ); +require_once( ABSPATH . WPINC . '/class-wp-http-streams.php' ); +require_once( ABSPATH . WPINC . '/class-wp-http-curl.php' ); +require_once( ABSPATH . WPINC . '/class-wp-http-proxy.php' ); +require_once( ABSPATH . WPINC . '/class-wp-http-cookie.php' ); +require_once( ABSPATH . WPINC . '/class-wp-http-encoding.php' ); \ No newline at end of file diff --git a/wp-includes/version.php b/wp-includes/version.php index 99ffa43978..ae9a2125e1 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.4-alpha-33747'; +$wp_version = '4.4-alpha-33748'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/wp-settings.php b/wp-settings.php index 69aa124626..993a12096e 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -148,7 +148,6 @@ require( ABSPATH . WPINC . '/shortcodes.php' ); require( ABSPATH . WPINC . '/class-wp-embed.php' ); require( ABSPATH . WPINC . '/media.php' ); require( ABSPATH . WPINC . '/http.php' ); -require( ABSPATH . WPINC . '/class-http.php' ); require( ABSPATH . WPINC . '/widgets.php' ); require( ABSPATH . WPINC . '/nav-menu.php' ); require( ABSPATH . WPINC . '/nav-menu-template.php' );