X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/4feeb71a9d812a9ae371c28a3d8b442a4394ded7..607b7e02d77e7326161e8ec15639052d2040f745:/wp-includes/class-http.php diff --git a/wp-includes/class-http.php b/wp-includes/class-http.php index 838fbe81..d3a89378 100644 --- a/wp-includes/class-http.php +++ b/wp-includes/class-http.php @@ -7,6 +7,13 @@ * @since 2.7.0 */ +if ( ! class_exists( 'Requests' ) ) { + require( ABSPATH . WPINC . '/class-requests.php' ); + + Requests::register_autoloader(); + Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' ); +} + /** * Core class used for managing HTTP transports and making HTTP requests. * @@ -110,7 +117,7 @@ class WP_Http { * Default '1.0'. * @type string $user-agent User-agent value sent. * Default WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ). - * @type bool $reject_unsafe_urls Whether to pass URLs through {@see wp_http_validate_url()}. + * @type bool $reject_unsafe_urls Whether to pass URLs through wp_http_validate_url(). * Default false. * @type bool $blocking Whether the calling code requires the result of the request. * If set to false, the request will be sent to the remote server, @@ -146,7 +153,7 @@ class WP_Http { $defaults = array( 'method' => 'GET', /** - * Filter the timeout value for an HTTP request. + * Filters the timeout value for an HTTP request. * * @since 2.7.0 * @@ -155,7 +162,7 @@ class WP_Http { */ 'timeout' => apply_filters( 'http_request_timeout', 5 ), /** - * Filter the number of redirects allowed during an HTTP request. + * Filters the number of redirects allowed during an HTTP request. * * @since 2.7.0 * @@ -163,7 +170,7 @@ class WP_Http { */ 'redirection' => apply_filters( 'http_request_redirection_count', 5 ), /** - * Filter the version of the HTTP protocol used in a request. + * Filters the version of the HTTP protocol used in a request. * * @since 2.7.0 * @@ -172,7 +179,7 @@ class WP_Http { */ 'httpversion' => apply_filters( 'http_request_version', '1.0' ), /** - * Filter the user agent value sent with an HTTP request. + * Filters the user agent value sent with an HTTP request. * * @since 2.7.0 * @@ -180,7 +187,7 @@ class WP_Http { */ 'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ), /** - * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request. + * Filters whether to pass URLs through wp_http_validate_url() in an HTTP request. * * @since 3.6.0 * @@ -210,7 +217,7 @@ class WP_Http { $r = wp_parse_args( $args, $defaults ); /** - * Filter the arguments used in an HTTP request. + * Filters the arguments used in an HTTP request. * * @since 2.7.0 * @@ -224,7 +231,7 @@ class WP_Http { $r['_redirection'] = $r['redirection']; /** - * Filter whether to preempt an HTTP request's return value. + * Filters whether to preempt an HTTP request's return value. * * Returning a non-false value from the filter will short-circuit the HTTP request and return * early with that value. A filter should return either: @@ -247,8 +254,9 @@ class WP_Http { return $pre; if ( function_exists( 'wp_kses_bad_protocol' ) ) { - if ( $r['reject_unsafe_urls'] ) + if ( $r['reject_unsafe_urls'] ) { $url = wp_http_validate_url( $url ); + } if ( $url ) { $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) ); } @@ -256,107 +264,204 @@ class WP_Http { $arrURL = @parse_url( $url ); - if ( empty( $url ) || empty( $arrURL['scheme'] ) ) + if ( empty( $url ) || empty( $arrURL['scheme'] ) ) { return new WP_Error('http_request_failed', __('A valid URL was not provided.')); + } - if ( $this->block_request( $url ) ) + if ( $this->block_request( $url ) ) { return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) ); - - /* - * Determine if this is a https call and pass that on to the transport functions - * so that we can blacklist the transports that do not support ssl verification - */ - $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl'; - - // Determine if this request is to OUR install of WordPress. - $homeURL = parse_url( get_bloginfo( 'url' ) ); - $r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] ); - unset( $homeURL ); - - /* - * If we are streaming to a file but no filename was given drop it in the WP temp dir - * and pick its name using the basename of the $url. - */ - if ( $r['stream'] && empty( $r['filename'] ) ) { - $r['filename'] = get_temp_dir() . wp_unique_filename( get_temp_dir(), basename( $url ) ); } - /* - * Force some settings if we are streaming to a file and check for existence and perms - * of destination directory. - */ + // If we are streaming to a file but no filename was given drop it in the WP temp dir + // and pick its name using the basename of the $url if ( $r['stream'] ) { + if ( empty( $r['filename'] ) ) { + $r['filename'] = get_temp_dir() . basename( $url ); + } + + // Force some settings if we are streaming to a file and check for existence and perms of destination directory $r['blocking'] = true; - if ( ! wp_is_writable( dirname( $r['filename'] ) ) ) + if ( ! wp_is_writable( dirname( $r['filename'] ) ) ) { return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) ); + } } - if ( is_null( $r['headers'] ) ) + if ( is_null( $r['headers'] ) ) { $r['headers'] = array(); + } + // WP allows passing in headers as a string, weirdly. if ( ! is_array( $r['headers'] ) ) { - $processedHeaders = self::processHeaders( $r['headers'], $url ); + $processedHeaders = WP_Http::processHeaders( $r['headers'] ); $r['headers'] = $processedHeaders['headers']; } - if ( isset( $r['headers']['User-Agent'] ) ) { - $r['user-agent'] = $r['headers']['User-Agent']; - unset( $r['headers']['User-Agent'] ); - } + // Setup arguments + $headers = $r['headers']; + $data = $r['body']; + $type = $r['method']; + $options = array( + 'timeout' => $r['timeout'], + 'useragent' => $r['user-agent'], + 'blocking' => $r['blocking'], + 'hooks' => new Requests_Hooks(), + ); + + // Ensure redirects follow browser behaviour. + $options['hooks']->register( 'requests.before_redirect', array( get_class(), 'browser_redirect_compatibility' ) ); - if ( isset( $r['headers']['user-agent'] ) ) { - $r['user-agent'] = $r['headers']['user-agent']; - unset( $r['headers']['user-agent'] ); + if ( $r['stream'] ) { + $options['filename'] = $r['filename']; + } + if ( empty( $r['redirection'] ) ) { + $options['follow_redirects'] = false; + } else { + $options['redirects'] = $r['redirection']; } - if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) { - $r['headers']['connection'] = 'close'; + // Use byte limit, if we can + if ( isset( $r['limit_response_size'] ) ) { + $options['max_bytes'] = $r['limit_response_size']; } - // Construct Cookie: header if any cookies are set. - self::buildCookieHeader( $r ); + // If we've got cookies, use and convert them to Requests_Cookie. + if ( ! empty( $r['cookies'] ) ) { + $options['cookies'] = WP_Http::normalize_cookies( $r['cookies'] ); + } - // Avoid issues where mbstring.func_overload is enabled. - mbstring_binary_safe_encoding(); + // SSL certificate handling + if ( ! $r['sslverify'] ) { + $options['verify'] = false; + } else { + $options['verify'] = $r['sslcertificates']; + } - if ( ! isset( $r['headers']['Accept-Encoding'] ) ) { - if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) ) - $r['headers']['Accept-Encoding'] = $encoding; + // All non-GET/HEAD requests should put the arguments in the form body. + if ( 'HEAD' !== $type && 'GET' !== $type ) { + $options['data_format'] = 'body'; } - if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) { - if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) { - $r['body'] = http_build_query( $r['body'], null, '&' ); + /** + * Filters 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. + */ + $options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'] ); + + // Check for proxies. + $proxy = new WP_HTTP_Proxy(); + if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { + $options['proxy'] = new Requests_Proxy_HTTP( $proxy->host() . ':' . $proxy->port() ); - if ( ! isset( $r['headers']['Content-Type'] ) ) - $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ); + if ( $proxy->use_authentication() ) { + $options['proxy']->use_authentication = true; + $options['proxy']->user = $proxy->username(); + $options['proxy']->pass = $proxy->password(); } + } + + try { + $requests_response = Requests::request( $url, $headers, $data, $type, $options ); - if ( '' === $r['body'] ) - $r['body'] = null; + // Convert the response into an array + $http_response = new WP_HTTP_Requests_Response( $requests_response, $r['filename'] ); + $response = $http_response->to_array(); - if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) ) - $r['headers']['Content-Length'] = strlen( $r['body'] ); + // Add the original object to the array. + $response['http_response'] = $http_response; + } + catch ( Requests_Exception $e ) { + $response = new WP_Error( 'http_request_failed', $e->getMessage() ); } - $response = $this->_dispatch_request( $url, $r ); + /** + * Fires after an HTTP API response is received and before the response is returned. + * + * @since 2.8.0 + * + * @param array|WP_Error $response HTTP response or WP_Error object. + * @param string $context Context under which the hook is fired. + * @param string $class HTTP transport used. + * @param array $args HTTP request arguments. + * @param string $url The request URL. + */ + do_action( 'http_api_debug', $response, 'response', 'Requests', $r, $url ); + if ( is_wp_error( $response ) ) { + return $response; + } - reset_mbstring_encoding(); + if ( ! $r['blocking'] ) { + return array( + 'headers' => array(), + 'body' => '', + 'response' => array( + 'code' => false, + 'message' => false, + ), + 'cookies' => array(), + 'http_response' => null, + ); + } - if ( is_wp_error( $response ) ) - return $response; + /** + * Filters the HTTP API response immediately before the response is returned. + * + * @since 2.9.0 + * + * @param array $response HTTP response. + * @param array $r HTTP request arguments. + * @param string $url The request URL. + */ + return apply_filters( 'http_response', $response, $r, $url ); + } - // Append cookies that were used in this request to the response - if ( ! empty( $r['cookies'] ) ) { - $cookies_set = wp_list_pluck( $response['cookies'], 'name' ); - foreach ( $r['cookies'] as $cookie ) { - if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) { - $response['cookies'][] = $cookie; - } + /** + * Normalizes cookies for using in Requests. + * + * @since 4.6.0 + * @access public + * @static + * + * @param array $cookies List of cookies to send with the request. + * @return Requests_Cookie_Jar Cookie holder object. + */ + public static function normalize_cookies( $cookies ) { + $cookie_jar = new Requests_Cookie_Jar(); + + foreach ( $cookies as $name => $value ) { + if ( $value instanceof WP_Http_Cookie ) { + $cookie_jar[ $value->name ] = new Requests_Cookie( $value->name, $value->value, $value->get_attributes() ); + } elseif ( is_string( $value ) ) { + $cookie_jar[ $name ] = new Requests_Cookie( $name, $value ); } } - return $response; + return $cookie_jar; + } + + /** + * Match redirect behaviour to browser handling. + * + * Changes 302 redirects from POST to GET to match browser handling. Per + * RFC 7231, user agents can deviate from the strict reading of the + * specification for compatibility purposes. + * + * @since 4.6.0 + * @access public + * @static + * + * @param string $location URL to redirect to. + * @param array $headers Headers for the redirect. + * @param array $options Redirect request options. + * @param Requests_Response $original Response object. + */ + public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) { + // Browser compat + if ( $original->status_code === 302 ) { + $options['type'] = Requests::GET; + } } /** @@ -374,7 +479,7 @@ class WP_Http { $transports = array( 'curl', 'streams' ); /** - * Filter which HTTP transports are available and in what order. + * Filters which HTTP transports are available and in what order. * * @since 3.7.0 * @@ -432,24 +537,14 @@ class WP_Http { $response = $transports[$class]->request( $url, $args ); - /** - * Fires after an HTTP API response is received and before the response is returned. - * - * @since 2.8.0 - * - * @param array|WP_Error $response HTTP response or WP_Error object. - * @param string $context Context under which the hook is fired. - * @param string $class HTTP transport used. - * @param array $args HTTP request arguments. - * @param string $url The request URL. - */ + /** This action is documented in wp-includes/class-http.php */ do_action( 'http_api_debug', $response, 'response', $class, $args, $url ); if ( is_wp_error( $response ) ) return $response; /** - * Filter the HTTP API response immediately before the response is returned. + * Filters the HTTP API response immediately before the response is returned. * * @since 2.9.0 * @@ -643,7 +738,7 @@ class WP_Http { * * Based off the HTTP http_encoding_dechunk function. * - * @link http://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding. + * @link https://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding. * * @access public * @since 2.7.0 @@ -718,7 +813,7 @@ class WP_Http { // Don't block requests back to ourselves by default. if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) { /** - * Filter whether to block local requests through the proxy. + * Filters whether to block local requests through the proxy. * * @since 2.8.0 *