wp/wp-includes/class-wp-http.php
changeset 19 3d72ae0968f4
child 21 48c4eec2b7e6
equal deleted inserted replaced
18:be944660c56a 19:3d72ae0968f4
       
     1 <?php
       
     2 /**
       
     3  * HTTP API: WP_Http class
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage HTTP
       
     7  * @since 2.7.0
       
     8  */
       
     9 
       
    10 if ( ! class_exists( 'Requests' ) ) {
       
    11 	require ABSPATH . WPINC . '/class-requests.php';
       
    12 
       
    13 	Requests::register_autoloader();
       
    14 	Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' );
       
    15 }
       
    16 
       
    17 /**
       
    18  * Core class used for managing HTTP transports and making HTTP requests.
       
    19  *
       
    20  * This class is used to consistently make outgoing HTTP requests easy for developers
       
    21  * while still being compatible with the many PHP configurations under which
       
    22  * WordPress runs.
       
    23  *
       
    24  * Debugging includes several actions, which pass different variables for debugging the HTTP API.
       
    25  *
       
    26  * @since 2.7.0
       
    27  */
       
    28 class WP_Http {
       
    29 
       
    30 	// Aliases for HTTP response codes.
       
    31 	const HTTP_CONTINUE       = 100;
       
    32 	const SWITCHING_PROTOCOLS = 101;
       
    33 	const PROCESSING          = 102;
       
    34 	const EARLY_HINTS         = 103;
       
    35 
       
    36 	const OK                            = 200;
       
    37 	const CREATED                       = 201;
       
    38 	const ACCEPTED                      = 202;
       
    39 	const NON_AUTHORITATIVE_INFORMATION = 203;
       
    40 	const NO_CONTENT                    = 204;
       
    41 	const RESET_CONTENT                 = 205;
       
    42 	const PARTIAL_CONTENT               = 206;
       
    43 	const MULTI_STATUS                  = 207;
       
    44 	const IM_USED                       = 226;
       
    45 
       
    46 	const MULTIPLE_CHOICES   = 300;
       
    47 	const MOVED_PERMANENTLY  = 301;
       
    48 	const FOUND              = 302;
       
    49 	const SEE_OTHER          = 303;
       
    50 	const NOT_MODIFIED       = 304;
       
    51 	const USE_PROXY          = 305;
       
    52 	const RESERVED           = 306;
       
    53 	const TEMPORARY_REDIRECT = 307;
       
    54 	const PERMANENT_REDIRECT = 308;
       
    55 
       
    56 	const BAD_REQUEST                     = 400;
       
    57 	const UNAUTHORIZED                    = 401;
       
    58 	const PAYMENT_REQUIRED                = 402;
       
    59 	const FORBIDDEN                       = 403;
       
    60 	const NOT_FOUND                       = 404;
       
    61 	const METHOD_NOT_ALLOWED              = 405;
       
    62 	const NOT_ACCEPTABLE                  = 406;
       
    63 	const PROXY_AUTHENTICATION_REQUIRED   = 407;
       
    64 	const REQUEST_TIMEOUT                 = 408;
       
    65 	const CONFLICT                        = 409;
       
    66 	const GONE                            = 410;
       
    67 	const LENGTH_REQUIRED                 = 411;
       
    68 	const PRECONDITION_FAILED             = 412;
       
    69 	const REQUEST_ENTITY_TOO_LARGE        = 413;
       
    70 	const REQUEST_URI_TOO_LONG            = 414;
       
    71 	const UNSUPPORTED_MEDIA_TYPE          = 415;
       
    72 	const REQUESTED_RANGE_NOT_SATISFIABLE = 416;
       
    73 	const EXPECTATION_FAILED              = 417;
       
    74 	const IM_A_TEAPOT                     = 418;
       
    75 	const MISDIRECTED_REQUEST             = 421;
       
    76 	const UNPROCESSABLE_ENTITY            = 422;
       
    77 	const LOCKED                          = 423;
       
    78 	const FAILED_DEPENDENCY               = 424;
       
    79 	const UPGRADE_REQUIRED                = 426;
       
    80 	const PRECONDITION_REQUIRED           = 428;
       
    81 	const TOO_MANY_REQUESTS               = 429;
       
    82 	const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
       
    83 	const UNAVAILABLE_FOR_LEGAL_REASONS   = 451;
       
    84 
       
    85 	const INTERNAL_SERVER_ERROR           = 500;
       
    86 	const NOT_IMPLEMENTED                 = 501;
       
    87 	const BAD_GATEWAY                     = 502;
       
    88 	const SERVICE_UNAVAILABLE             = 503;
       
    89 	const GATEWAY_TIMEOUT                 = 504;
       
    90 	const HTTP_VERSION_NOT_SUPPORTED      = 505;
       
    91 	const VARIANT_ALSO_NEGOTIATES         = 506;
       
    92 	const INSUFFICIENT_STORAGE            = 507;
       
    93 	const NOT_EXTENDED                    = 510;
       
    94 	const NETWORK_AUTHENTICATION_REQUIRED = 511;
       
    95 
       
    96 	/**
       
    97 	 * Send an HTTP request to a URI.
       
    98 	 *
       
    99 	 * Please note: The only URI that are supported in the HTTP Transport implementation
       
   100 	 * are the HTTP and HTTPS protocols.
       
   101 	 *
       
   102 	 * @since 2.7.0
       
   103 	 *
       
   104 	 * @param string       $url  The request URL.
       
   105 	 * @param string|array $args {
       
   106 	 *     Optional. Array or string of HTTP request arguments.
       
   107 	 *
       
   108 	 *     @type string       $method              Request method. Accepts 'GET', 'POST', 'HEAD', 'PUT', 'DELETE',
       
   109 	 *                                             'TRACE', 'OPTIONS', or 'PATCH'.
       
   110 	 *                                             Some transports technically allow others, but should not be
       
   111 	 *                                             assumed. Default 'GET'.
       
   112 	 *     @type float        $timeout             How long the connection should stay open in seconds. Default 5.
       
   113 	 *     @type int          $redirection         Number of allowed redirects. Not supported by all transports.
       
   114 	 *                                             Default 5.
       
   115 	 *     @type string       $httpversion         Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
       
   116 	 *                                             Default '1.0'.
       
   117 	 *     @type string       $user-agent          User-agent value sent.
       
   118 	 *                                             Default 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ).
       
   119 	 *     @type bool         $reject_unsafe_urls  Whether to pass URLs through wp_http_validate_url().
       
   120 	 *                                             Default false.
       
   121 	 *     @type bool         $blocking            Whether the calling code requires the result of the request.
       
   122 	 *                                             If set to false, the request will be sent to the remote server,
       
   123 	 *                                             and processing returned to the calling code immediately, the caller
       
   124 	 *                                             will know if the request succeeded or failed, but will not receive
       
   125 	 *                                             any response from the remote server. Default true.
       
   126 	 *     @type string|array $headers             Array or string of headers to send with the request.
       
   127 	 *                                             Default empty array.
       
   128 	 *     @type array        $cookies             List of cookies to send with the request. Default empty array.
       
   129 	 *     @type string|array $body                Body to send with the request. Default null.
       
   130 	 *     @type bool         $compress            Whether to compress the $body when sending the request.
       
   131 	 *                                             Default false.
       
   132 	 *     @type bool         $decompress          Whether to decompress a compressed response. If set to false and
       
   133 	 *                                             compressed content is returned in the response anyway, it will
       
   134 	 *                                             need to be separately decompressed. Default true.
       
   135 	 *     @type bool         $sslverify           Whether to verify SSL for the request. Default true.
       
   136 	 *     @type string       $sslcertificates     Absolute path to an SSL certificate .crt file.
       
   137 	 *                                             Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
       
   138 	 *     @type bool         $stream              Whether to stream to a file. If set to true and no filename was
       
   139 	 *                                             given, it will be dropped it in the WP temp dir and its name will
       
   140 	 *                                             be set using the basename of the URL. Default false.
       
   141 	 *     @type string       $filename            Filename of the file to write to when streaming. $stream must be
       
   142 	 *                                             set to true. Default null.
       
   143 	 *     @type int          $limit_response_size Size in bytes to limit the response to. Default null.
       
   144 	 *
       
   145 	 * }
       
   146 	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
       
   147 	 *                        A WP_Error instance upon error.
       
   148 	 */
       
   149 	public function request( $url, $args = array() ) {
       
   150 		$defaults = array(
       
   151 			'method'              => 'GET',
       
   152 			/**
       
   153 			 * Filters the timeout value for an HTTP request.
       
   154 			 *
       
   155 			 * @since 2.7.0
       
   156 			 * @since 5.1.0 The `$url` parameter was added.
       
   157 			 *
       
   158 			 * @param float  $timeout_value Time in seconds until a request times out. Default 5.
       
   159 			 * @param string $url           The request URL.
       
   160 			 */
       
   161 			'timeout'             => apply_filters( 'http_request_timeout', 5, $url ),
       
   162 			/**
       
   163 			 * Filters the number of redirects allowed during an HTTP request.
       
   164 			 *
       
   165 			 * @since 2.7.0
       
   166 			 * @since 5.1.0 The `$url` parameter was added.
       
   167 			 *
       
   168 			 * @param int    $redirect_count Number of redirects allowed. Default 5.
       
   169 			 * @param string $url            The request URL.
       
   170 			 */
       
   171 			'redirection'         => apply_filters( 'http_request_redirection_count', 5, $url ),
       
   172 			/**
       
   173 			 * Filters the version of the HTTP protocol used in a request.
       
   174 			 *
       
   175 			 * @since 2.7.0
       
   176 			 * @since 5.1.0 The `$url` parameter was added.
       
   177 			 *
       
   178 			 * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'. Default '1.0'.
       
   179 			 * @param string $url     The request URL.
       
   180 			 */
       
   181 			'httpversion'         => apply_filters( 'http_request_version', '1.0', $url ),
       
   182 			/**
       
   183 			 * Filters the user agent value sent with an HTTP request.
       
   184 			 *
       
   185 			 * @since 2.7.0
       
   186 			 * @since 5.1.0 The `$url` parameter was added.
       
   187 			 *
       
   188 			 * @param string $user_agent WordPress user agent string.
       
   189 			 * @param string $url        The request URL.
       
   190 			 */
       
   191 			'user-agent'          => apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $url ),
       
   192 			/**
       
   193 			 * Filters whether to pass URLs through wp_http_validate_url() in an HTTP request.
       
   194 			 *
       
   195 			 * @since 3.6.0
       
   196 			 * @since 5.1.0 The `$url` parameter was added.
       
   197 			 *
       
   198 			 * @param bool   $pass_url Whether to pass URLs through wp_http_validate_url(). Default false.
       
   199 			 * @param string $url      The request URL.
       
   200 			 */
       
   201 			'reject_unsafe_urls'  => apply_filters( 'http_request_reject_unsafe_urls', false, $url ),
       
   202 			'blocking'            => true,
       
   203 			'headers'             => array(),
       
   204 			'cookies'             => array(),
       
   205 			'body'                => null,
       
   206 			'compress'            => false,
       
   207 			'decompress'          => true,
       
   208 			'sslverify'           => true,
       
   209 			'sslcertificates'     => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
       
   210 			'stream'              => false,
       
   211 			'filename'            => null,
       
   212 			'limit_response_size' => null,
       
   213 		);
       
   214 
       
   215 		// Pre-parse for the HEAD checks.
       
   216 		$args = wp_parse_args( $args );
       
   217 
       
   218 		// By default, HEAD requests do not cause redirections.
       
   219 		if ( isset( $args['method'] ) && 'HEAD' === $args['method'] ) {
       
   220 			$defaults['redirection'] = 0;
       
   221 		}
       
   222 
       
   223 		$parsed_args = wp_parse_args( $args, $defaults );
       
   224 		/**
       
   225 		 * Filters the arguments used in an HTTP request.
       
   226 		 *
       
   227 		 * @since 2.7.0
       
   228 		 *
       
   229 		 * @param array  $parsed_args An array of HTTP request arguments.
       
   230 		 * @param string $url         The request URL.
       
   231 		 */
       
   232 		$parsed_args = apply_filters( 'http_request_args', $parsed_args, $url );
       
   233 
       
   234 		// The transports decrement this, store a copy of the original value for loop purposes.
       
   235 		if ( ! isset( $parsed_args['_redirection'] ) ) {
       
   236 			$parsed_args['_redirection'] = $parsed_args['redirection'];
       
   237 		}
       
   238 
       
   239 		/**
       
   240 		 * Filters the preemptive return value of an HTTP request.
       
   241 		 *
       
   242 		 * Returning a non-false value from the filter will short-circuit the HTTP request and return
       
   243 		 * early with that value. A filter should return one of:
       
   244 		 *
       
   245 		 *  - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements
       
   246 		 *  - A WP_Error instance
       
   247 		 *  - boolean false to avoid short-circuiting the response
       
   248 		 *
       
   249 		 * Returning any other value may result in unexpected behaviour.
       
   250 		 *
       
   251 		 * @since 2.9.0
       
   252 		 *
       
   253 		 * @param false|array|WP_Error $preempt     A preemptive return value of an HTTP request. Default false.
       
   254 		 * @param array                $parsed_args HTTP request arguments.
       
   255 		 * @param string               $url         The request URL.
       
   256 		 */
       
   257 		$pre = apply_filters( 'pre_http_request', false, $parsed_args, $url );
       
   258 
       
   259 		if ( false !== $pre ) {
       
   260 			return $pre;
       
   261 		}
       
   262 
       
   263 		if ( function_exists( 'wp_kses_bad_protocol' ) ) {
       
   264 			if ( $parsed_args['reject_unsafe_urls'] ) {
       
   265 				$url = wp_http_validate_url( $url );
       
   266 			}
       
   267 			if ( $url ) {
       
   268 				$url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
       
   269 			}
       
   270 		}
       
   271 
       
   272 		$parsed_url = parse_url( $url );
       
   273 
       
   274 		if ( empty( $url ) || empty( $parsed_url['scheme'] ) ) {
       
   275 			$response = new WP_Error( 'http_request_failed', __( 'A valid URL was not provided.' ) );
       
   276 			/** This action is documented in wp-includes/class-wp-http.php */
       
   277 			do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
       
   278 			return $response;
       
   279 		}
       
   280 
       
   281 		if ( $this->block_request( $url ) ) {
       
   282 			$response = new WP_Error( 'http_request_not_executed', __( 'User has blocked requests through HTTP.' ) );
       
   283 			/** This action is documented in wp-includes/class-wp-http.php */
       
   284 			do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
       
   285 			return $response;
       
   286 		}
       
   287 
       
   288 		// If we are streaming to a file but no filename was given drop it in the WP temp dir
       
   289 		// and pick its name using the basename of the $url.
       
   290 		if ( $parsed_args['stream'] ) {
       
   291 			if ( empty( $parsed_args['filename'] ) ) {
       
   292 				$parsed_args['filename'] = get_temp_dir() . basename( $url );
       
   293 			}
       
   294 
       
   295 			// Force some settings if we are streaming to a file and check for existence
       
   296 			// and perms of destination directory.
       
   297 			$parsed_args['blocking'] = true;
       
   298 			if ( ! wp_is_writable( dirname( $parsed_args['filename'] ) ) ) {
       
   299 				$response = new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
       
   300 				/** This action is documented in wp-includes/class-wp-http.php */
       
   301 				do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
       
   302 				return $response;
       
   303 			}
       
   304 		}
       
   305 
       
   306 		if ( is_null( $parsed_args['headers'] ) ) {
       
   307 			$parsed_args['headers'] = array();
       
   308 		}
       
   309 
       
   310 		// WP allows passing in headers as a string, weirdly.
       
   311 		if ( ! is_array( $parsed_args['headers'] ) ) {
       
   312 			$processed_headers      = WP_Http::processHeaders( $parsed_args['headers'] );
       
   313 			$parsed_args['headers'] = $processed_headers['headers'];
       
   314 		}
       
   315 
       
   316 		// Setup arguments.
       
   317 		$headers = $parsed_args['headers'];
       
   318 		$data    = $parsed_args['body'];
       
   319 		$type    = $parsed_args['method'];
       
   320 		$options = array(
       
   321 			'timeout'   => $parsed_args['timeout'],
       
   322 			'useragent' => $parsed_args['user-agent'],
       
   323 			'blocking'  => $parsed_args['blocking'],
       
   324 			'hooks'     => new WP_HTTP_Requests_Hooks( $url, $parsed_args ),
       
   325 		);
       
   326 
       
   327 		// Ensure redirects follow browser behaviour.
       
   328 		$options['hooks']->register( 'requests.before_redirect', array( get_class(), 'browser_redirect_compatibility' ) );
       
   329 
       
   330 		// Validate redirected URLs.
       
   331 		if ( function_exists( 'wp_kses_bad_protocol' ) && $parsed_args['reject_unsafe_urls'] ) {
       
   332 			$options['hooks']->register( 'requests.before_redirect', array( get_class(), 'validate_redirects' ) );
       
   333 		}
       
   334 
       
   335 		if ( $parsed_args['stream'] ) {
       
   336 			$options['filename'] = $parsed_args['filename'];
       
   337 		}
       
   338 		if ( empty( $parsed_args['redirection'] ) ) {
       
   339 			$options['follow_redirects'] = false;
       
   340 		} else {
       
   341 			$options['redirects'] = $parsed_args['redirection'];
       
   342 		}
       
   343 
       
   344 		// Use byte limit, if we can.
       
   345 		if ( isset( $parsed_args['limit_response_size'] ) ) {
       
   346 			$options['max_bytes'] = $parsed_args['limit_response_size'];
       
   347 		}
       
   348 
       
   349 		// If we've got cookies, use and convert them to Requests_Cookie.
       
   350 		if ( ! empty( $parsed_args['cookies'] ) ) {
       
   351 			$options['cookies'] = WP_Http::normalize_cookies( $parsed_args['cookies'] );
       
   352 		}
       
   353 
       
   354 		// SSL certificate handling.
       
   355 		if ( ! $parsed_args['sslverify'] ) {
       
   356 			$options['verify']     = false;
       
   357 			$options['verifyname'] = false;
       
   358 		} else {
       
   359 			$options['verify'] = $parsed_args['sslcertificates'];
       
   360 		}
       
   361 
       
   362 		// All non-GET/HEAD requests should put the arguments in the form body.
       
   363 		if ( 'HEAD' !== $type && 'GET' !== $type ) {
       
   364 			$options['data_format'] = 'body';
       
   365 		}
       
   366 
       
   367 		/**
       
   368 		 * Filters whether SSL should be verified for non-local requests.
       
   369 		 *
       
   370 		 * @since 2.8.0
       
   371 		 * @since 5.1.0 The `$url` parameter was added.
       
   372 		 *
       
   373 		 * @param bool   $ssl_verify Whether to verify the SSL connection. Default true.
       
   374 		 * @param string $url        The request URL.
       
   375 		 */
       
   376 		$options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'], $url );
       
   377 
       
   378 		// Check for proxies.
       
   379 		$proxy = new WP_HTTP_Proxy();
       
   380 		if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
       
   381 			$options['proxy'] = new Requests_Proxy_HTTP( $proxy->host() . ':' . $proxy->port() );
       
   382 
       
   383 			if ( $proxy->use_authentication() ) {
       
   384 				$options['proxy']->use_authentication = true;
       
   385 				$options['proxy']->user               = $proxy->username();
       
   386 				$options['proxy']->pass               = $proxy->password();
       
   387 			}
       
   388 		}
       
   389 
       
   390 		// Avoid issues where mbstring.func_overload is enabled.
       
   391 		mbstring_binary_safe_encoding();
       
   392 
       
   393 		try {
       
   394 			$requests_response = Requests::request( $url, $headers, $data, $type, $options );
       
   395 
       
   396 			// Convert the response into an array.
       
   397 			$http_response = new WP_HTTP_Requests_Response( $requests_response, $parsed_args['filename'] );
       
   398 			$response      = $http_response->to_array();
       
   399 
       
   400 			// Add the original object to the array.
       
   401 			$response['http_response'] = $http_response;
       
   402 		} catch ( Requests_Exception $e ) {
       
   403 			$response = new WP_Error( 'http_request_failed', $e->getMessage() );
       
   404 		}
       
   405 
       
   406 		reset_mbstring_encoding();
       
   407 
       
   408 		/**
       
   409 		 * Fires after an HTTP API response is received and before the response is returned.
       
   410 		 *
       
   411 		 * @since 2.8.0
       
   412 		 *
       
   413 		 * @param array|WP_Error $response    HTTP response or WP_Error object.
       
   414 		 * @param string         $context     Context under which the hook is fired.
       
   415 		 * @param string         $class       HTTP transport used.
       
   416 		 * @param array          $parsed_args HTTP request arguments.
       
   417 		 * @param string         $url         The request URL.
       
   418 		 */
       
   419 		do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
       
   420 		if ( is_wp_error( $response ) ) {
       
   421 			return $response;
       
   422 		}
       
   423 
       
   424 		if ( ! $parsed_args['blocking'] ) {
       
   425 			return array(
       
   426 				'headers'       => array(),
       
   427 				'body'          => '',
       
   428 				'response'      => array(
       
   429 					'code'    => false,
       
   430 					'message' => false,
       
   431 				),
       
   432 				'cookies'       => array(),
       
   433 				'http_response' => null,
       
   434 			);
       
   435 		}
       
   436 
       
   437 		/**
       
   438 		 * Filters a successful HTTP API response immediately before the response is returned.
       
   439 		 *
       
   440 		 * @since 2.9.0
       
   441 		 *
       
   442 		 * @param array  $response    HTTP response.
       
   443 		 * @param array  $parsed_args HTTP request arguments.
       
   444 		 * @param string $url         The request URL.
       
   445 		 */
       
   446 		return apply_filters( 'http_response', $response, $parsed_args, $url );
       
   447 	}
       
   448 
       
   449 	/**
       
   450 	 * Normalizes cookies for using in Requests.
       
   451 	 *
       
   452 	 * @since 4.6.0
       
   453 	 *
       
   454 	 * @param array $cookies Array of cookies to send with the request.
       
   455 	 * @return Requests_Cookie_Jar Cookie holder object.
       
   456 	 */
       
   457 	public static function normalize_cookies( $cookies ) {
       
   458 		$cookie_jar = new Requests_Cookie_Jar();
       
   459 
       
   460 		foreach ( $cookies as $name => $value ) {
       
   461 			if ( $value instanceof WP_Http_Cookie ) {
       
   462 				$attributes                 = array_filter(
       
   463 					$value->get_attributes(),
       
   464 					static function( $attr ) {
       
   465 						return null !== $attr;
       
   466 					}
       
   467 				);
       
   468 				$cookie_jar[ $value->name ] = new Requests_Cookie( $value->name, $value->value, $attributes, array( 'host-only' => $value->host_only ) );
       
   469 			} elseif ( is_scalar( $value ) ) {
       
   470 				$cookie_jar[ $name ] = new Requests_Cookie( $name, $value );
       
   471 			}
       
   472 		}
       
   473 
       
   474 		return $cookie_jar;
       
   475 	}
       
   476 
       
   477 	/**
       
   478 	 * Match redirect behaviour to browser handling.
       
   479 	 *
       
   480 	 * Changes 302 redirects from POST to GET to match browser handling. Per
       
   481 	 * RFC 7231, user agents can deviate from the strict reading of the
       
   482 	 * specification for compatibility purposes.
       
   483 	 *
       
   484 	 * @since 4.6.0
       
   485 	 *
       
   486 	 * @param string            $location URL to redirect to.
       
   487 	 * @param array             $headers  Headers for the redirect.
       
   488 	 * @param string|array      $data     Body to send with the request.
       
   489 	 * @param array             $options  Redirect request options.
       
   490 	 * @param Requests_Response $original Response object.
       
   491 	 */
       
   492 	public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) {
       
   493 		// Browser compatibility.
       
   494 		if ( 302 === $original->status_code ) {
       
   495 			$options['type'] = Requests::GET;
       
   496 		}
       
   497 	}
       
   498 
       
   499 	/**
       
   500 	 * Validate redirected URLs.
       
   501 	 *
       
   502 	 * @since 4.7.5
       
   503 	 *
       
   504 	 * @throws Requests_Exception On unsuccessful URL validation.
       
   505 	 * @param string $location URL to redirect to.
       
   506 	 */
       
   507 	public static function validate_redirects( $location ) {
       
   508 		if ( ! wp_http_validate_url( $location ) ) {
       
   509 			throw new Requests_Exception( __( 'A valid URL was not provided.' ), 'wp_http.redirect_failed_validation' );
       
   510 		}
       
   511 	}
       
   512 
       
   513 	/**
       
   514 	 * Tests which transports are capable of supporting the request.
       
   515 	 *
       
   516 	 * @since 3.2.0
       
   517 	 *
       
   518 	 * @param array  $args Request arguments.
       
   519 	 * @param string $url  URL to request.
       
   520 	 * @return string|false Class name for the first transport that claims to support the request.
       
   521 	 *                      False if no transport claims to support the request.
       
   522 	 */
       
   523 	public function _get_first_available_transport( $args, $url = null ) {
       
   524 		$transports = array( 'curl', 'streams' );
       
   525 
       
   526 		/**
       
   527 		 * Filters which HTTP transports are available and in what order.
       
   528 		 *
       
   529 		 * @since 3.7.0
       
   530 		 *
       
   531 		 * @param string[] $transports Array of HTTP transports to check. Default array contains
       
   532 		 *                             'curl' and 'streams', in that order.
       
   533 		 * @param array    $args       HTTP request arguments.
       
   534 		 * @param string   $url        The URL to request.
       
   535 		 */
       
   536 		$request_order = apply_filters( 'http_api_transports', $transports, $args, $url );
       
   537 
       
   538 		// Loop over each transport on each HTTP request looking for one which will serve this request's needs.
       
   539 		foreach ( $request_order as $transport ) {
       
   540 			if ( in_array( $transport, $transports, true ) ) {
       
   541 				$transport = ucfirst( $transport );
       
   542 			}
       
   543 			$class = 'WP_Http_' . $transport;
       
   544 
       
   545 			// Check to see if this transport is a possibility, calls the transport statically.
       
   546 			if ( ! call_user_func( array( $class, 'test' ), $args, $url ) ) {
       
   547 				continue;
       
   548 			}
       
   549 
       
   550 			return $class;
       
   551 		}
       
   552 
       
   553 		return false;
       
   554 	}
       
   555 
       
   556 	/**
       
   557 	 * Dispatches a HTTP request to a supporting transport.
       
   558 	 *
       
   559 	 * Tests each transport in order to find a transport which matches the request arguments.
       
   560 	 * Also caches the transport instance to be used later.
       
   561 	 *
       
   562 	 * The order for requests is cURL, and then PHP Streams.
       
   563 	 *
       
   564 	 * @since 3.2.0
       
   565 	 * @deprecated 5.1.0 Use WP_Http::request()
       
   566 	 * @see WP_Http::request()
       
   567 	 *
       
   568 	 * @param string $url  URL to request.
       
   569 	 * @param array  $args Request arguments.
       
   570 	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
       
   571 	 *                        A WP_Error instance upon error.
       
   572 	 */
       
   573 	private function _dispatch_request( $url, $args ) {
       
   574 		static $transports = array();
       
   575 
       
   576 		$class = $this->_get_first_available_transport( $args, $url );
       
   577 		if ( ! $class ) {
       
   578 			return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
       
   579 		}
       
   580 
       
   581 		// Transport claims to support request, instantiate it and give it a whirl.
       
   582 		if ( empty( $transports[ $class ] ) ) {
       
   583 			$transports[ $class ] = new $class;
       
   584 		}
       
   585 
       
   586 		$response = $transports[ $class ]->request( $url, $args );
       
   587 
       
   588 		/** This action is documented in wp-includes/class-wp-http.php */
       
   589 		do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
       
   590 
       
   591 		if ( is_wp_error( $response ) ) {
       
   592 			return $response;
       
   593 		}
       
   594 
       
   595 		/** This filter is documented in wp-includes/class-wp-http.php */
       
   596 		return apply_filters( 'http_response', $response, $args, $url );
       
   597 	}
       
   598 
       
   599 	/**
       
   600 	 * Uses the POST HTTP method.
       
   601 	 *
       
   602 	 * Used for sending data that is expected to be in the body.
       
   603 	 *
       
   604 	 * @since 2.7.0
       
   605 	 *
       
   606 	 * @param string       $url  The request URL.
       
   607 	 * @param string|array $args Optional. Override the defaults.
       
   608 	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
       
   609 	 *                        A WP_Error instance upon error.
       
   610 	 */
       
   611 	public function post( $url, $args = array() ) {
       
   612 		$defaults    = array( 'method' => 'POST' );
       
   613 		$parsed_args = wp_parse_args( $args, $defaults );
       
   614 		return $this->request( $url, $parsed_args );
       
   615 	}
       
   616 
       
   617 	/**
       
   618 	 * Uses the GET HTTP method.
       
   619 	 *
       
   620 	 * Used for sending data that is expected to be in the body.
       
   621 	 *
       
   622 	 * @since 2.7.0
       
   623 	 *
       
   624 	 * @param string       $url  The request URL.
       
   625 	 * @param string|array $args Optional. Override the defaults.
       
   626 	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
       
   627 	 *                        A WP_Error instance upon error.
       
   628 	 */
       
   629 	public function get( $url, $args = array() ) {
       
   630 		$defaults    = array( 'method' => 'GET' );
       
   631 		$parsed_args = wp_parse_args( $args, $defaults );
       
   632 		return $this->request( $url, $parsed_args );
       
   633 	}
       
   634 
       
   635 	/**
       
   636 	 * Uses the HEAD HTTP method.
       
   637 	 *
       
   638 	 * Used for sending data that is expected to be in the body.
       
   639 	 *
       
   640 	 * @since 2.7.0
       
   641 	 *
       
   642 	 * @param string       $url  The request URL.
       
   643 	 * @param string|array $args Optional. Override the defaults.
       
   644 	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
       
   645 	 *                        A WP_Error instance upon error.
       
   646 	 */
       
   647 	public function head( $url, $args = array() ) {
       
   648 		$defaults    = array( 'method' => 'HEAD' );
       
   649 		$parsed_args = wp_parse_args( $args, $defaults );
       
   650 		return $this->request( $url, $parsed_args );
       
   651 	}
       
   652 
       
   653 	/**
       
   654 	 * Parses the responses and splits the parts into headers and body.
       
   655 	 *
       
   656 	 * @since 2.7.0
       
   657 	 *
       
   658 	 * @param string $response The full response string.
       
   659 	 * @return array {
       
   660 	 *     Array with response headers and body.
       
   661 	 *
       
   662 	 *     @type string $headers HTTP response headers.
       
   663 	 *     @type string $body    HTTP response body.
       
   664 	 * }
       
   665 	 */
       
   666 	public static function processResponse( $response ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
       
   667 		$response = explode( "\r\n\r\n", $response, 2 );
       
   668 
       
   669 		return array(
       
   670 			'headers' => $response[0],
       
   671 			'body'    => isset( $response[1] ) ? $response[1] : '',
       
   672 		);
       
   673 	}
       
   674 
       
   675 	/**
       
   676 	 * Transforms header string into an array.
       
   677 	 *
       
   678 	 * @since 2.7.0
       
   679 	 *
       
   680 	 * @param string|array $headers The original headers. If a string is passed, it will be converted
       
   681 	 *                              to an array. If an array is passed, then it is assumed to be
       
   682 	 *                              raw header data with numeric keys with the headers as the values.
       
   683 	 *                              No headers must be passed that were already processed.
       
   684 	 * @param string       $url     Optional. The URL that was requested. Default empty.
       
   685 	 * @return array {
       
   686 	 *     Processed string headers. If duplicate headers are encountered,
       
   687 	 *     then a numbered array is returned as the value of that header-key.
       
   688 	 *
       
   689 	 *     @type array            $response {
       
   690 	 *          @type int    $code    The response status code. Default 0.
       
   691 	 *          @type string $message The response message. Default empty.
       
   692 	 *     }
       
   693 	 *     @type array            $newheaders The processed header data as a multidimensional array.
       
   694 	 *     @type WP_Http_Cookie[] $cookies    If the original headers contain the 'Set-Cookie' key,
       
   695 	 *                                        an array containing `WP_Http_Cookie` objects is returned.
       
   696 	 * }
       
   697 	 */
       
   698 	public static function processHeaders( $headers, $url = '' ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
       
   699 		// Split headers, one per array element.
       
   700 		if ( is_string( $headers ) ) {
       
   701 			// Tolerate line terminator: CRLF = LF (RFC 2616 19.3).
       
   702 			$headers = str_replace( "\r\n", "\n", $headers );
       
   703 			/*
       
   704 			 * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>,
       
   705 			 * <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2).
       
   706 			 */
       
   707 			$headers = preg_replace( '/\n[ \t]/', ' ', $headers );
       
   708 			// Create the headers array.
       
   709 			$headers = explode( "\n", $headers );
       
   710 		}
       
   711 
       
   712 		$response = array(
       
   713 			'code'    => 0,
       
   714 			'message' => '',
       
   715 		);
       
   716 
       
   717 		/*
       
   718 		 * If a redirection has taken place, The headers for each page request may have been passed.
       
   719 		 * In this case, determine the final HTTP header and parse from there.
       
   720 		 */
       
   721 		for ( $i = count( $headers ) - 1; $i >= 0; $i-- ) {
       
   722 			if ( ! empty( $headers[ $i ] ) && false === strpos( $headers[ $i ], ':' ) ) {
       
   723 				$headers = array_splice( $headers, $i );
       
   724 				break;
       
   725 			}
       
   726 		}
       
   727 
       
   728 		$cookies    = array();
       
   729 		$newheaders = array();
       
   730 		foreach ( (array) $headers as $tempheader ) {
       
   731 			if ( empty( $tempheader ) ) {
       
   732 				continue;
       
   733 			}
       
   734 
       
   735 			if ( false === strpos( $tempheader, ':' ) ) {
       
   736 				$stack   = explode( ' ', $tempheader, 3 );
       
   737 				$stack[] = '';
       
   738 				list( , $response['code'], $response['message']) = $stack;
       
   739 				continue;
       
   740 			}
       
   741 
       
   742 			list($key, $value) = explode( ':', $tempheader, 2 );
       
   743 
       
   744 			$key   = strtolower( $key );
       
   745 			$value = trim( $value );
       
   746 
       
   747 			if ( isset( $newheaders[ $key ] ) ) {
       
   748 				if ( ! is_array( $newheaders[ $key ] ) ) {
       
   749 					$newheaders[ $key ] = array( $newheaders[ $key ] );
       
   750 				}
       
   751 				$newheaders[ $key ][] = $value;
       
   752 			} else {
       
   753 				$newheaders[ $key ] = $value;
       
   754 			}
       
   755 			if ( 'set-cookie' === $key ) {
       
   756 				$cookies[] = new WP_Http_Cookie( $value, $url );
       
   757 			}
       
   758 		}
       
   759 
       
   760 		// Cast the Response Code to an int.
       
   761 		$response['code'] = (int) $response['code'];
       
   762 
       
   763 		return array(
       
   764 			'response' => $response,
       
   765 			'headers'  => $newheaders,
       
   766 			'cookies'  => $cookies,
       
   767 		);
       
   768 	}
       
   769 
       
   770 	/**
       
   771 	 * Takes the arguments for a ::request() and checks for the cookie array.
       
   772 	 *
       
   773 	 * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances,
       
   774 	 * which are each parsed into strings and added to the Cookie: header (within the arguments array).
       
   775 	 * Edits the array by reference.
       
   776 	 *
       
   777 	 * @since 2.8.0
       
   778 	 *
       
   779 	 * @param array $r Full array of args passed into ::request()
       
   780 	 */
       
   781 	public static function buildCookieHeader( &$r ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
       
   782 		if ( ! empty( $r['cookies'] ) ) {
       
   783 			// Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances.
       
   784 			foreach ( $r['cookies'] as $name => $value ) {
       
   785 				if ( ! is_object( $value ) ) {
       
   786 					$r['cookies'][ $name ] = new WP_Http_Cookie(
       
   787 						array(
       
   788 							'name'  => $name,
       
   789 							'value' => $value,
       
   790 						)
       
   791 					);
       
   792 				}
       
   793 			}
       
   794 
       
   795 			$cookies_header = '';
       
   796 			foreach ( (array) $r['cookies'] as $cookie ) {
       
   797 				$cookies_header .= $cookie->getHeaderValue() . '; ';
       
   798 			}
       
   799 
       
   800 			$cookies_header         = substr( $cookies_header, 0, -2 );
       
   801 			$r['headers']['cookie'] = $cookies_header;
       
   802 		}
       
   803 	}
       
   804 
       
   805 	/**
       
   806 	 * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
       
   807 	 *
       
   808 	 * Based off the HTTP http_encoding_dechunk function.
       
   809 	 *
       
   810 	 * @link https://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
       
   811 	 *
       
   812 	 * @since 2.7.0
       
   813 	 *
       
   814 	 * @param string $body Body content.
       
   815 	 * @return string Chunked decoded body on success or raw body on failure.
       
   816 	 */
       
   817 	public static function chunkTransferDecode( $body ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
       
   818 		// The body is not chunked encoded or is malformed.
       
   819 		if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) ) {
       
   820 			return $body;
       
   821 		}
       
   822 
       
   823 		$parsed_body = '';
       
   824 
       
   825 		// We'll be altering $body, so need a backup in case of error.
       
   826 		$body_original = $body;
       
   827 
       
   828 		while ( true ) {
       
   829 			$has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match );
       
   830 			if ( ! $has_chunk || empty( $match[1] ) ) {
       
   831 				return $body_original;
       
   832 			}
       
   833 
       
   834 			$length       = hexdec( $match[1] );
       
   835 			$chunk_length = strlen( $match[0] );
       
   836 
       
   837 			// Parse out the chunk of data.
       
   838 			$parsed_body .= substr( $body, $chunk_length, $length );
       
   839 
       
   840 			// Remove the chunk from the raw data.
       
   841 			$body = substr( $body, $length + $chunk_length );
       
   842 
       
   843 			// End of the document.
       
   844 			if ( '0' === trim( $body ) ) {
       
   845 				return $parsed_body;
       
   846 			}
       
   847 		}
       
   848 	}
       
   849 
       
   850 	/**
       
   851 	 * Determines whether an HTTP API request to the given URL should be blocked.
       
   852 	 *
       
   853 	 * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
       
   854 	 * prevent plugins from working and core functionality, if you don't include `api.wordpress.org`.
       
   855 	 *
       
   856 	 * You block external URL requests by defining `WP_HTTP_BLOCK_EXTERNAL` as true in your `wp-config.php`
       
   857 	 * file and this will only allow localhost and your site to make requests. The constant
       
   858 	 * `WP_ACCESSIBLE_HOSTS` will allow additional hosts to go through for requests. The format of the
       
   859 	 * `WP_ACCESSIBLE_HOSTS` constant is a comma separated list of hostnames to allow, wildcard domains
       
   860 	 * are supported, eg `*.wordpress.org` will allow for all subdomains of `wordpress.org` to be contacted.
       
   861 	 *
       
   862 	 * @since 2.8.0
       
   863 	 *
       
   864 	 * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
       
   865 	 * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
       
   866 	 *
       
   867 	 * @param string $uri URI of url.
       
   868 	 * @return bool True to block, false to allow.
       
   869 	 */
       
   870 	public function block_request( $uri ) {
       
   871 		// We don't need to block requests, because nothing is blocked.
       
   872 		if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL ) {
       
   873 			return false;
       
   874 		}
       
   875 
       
   876 		$check = parse_url( $uri );
       
   877 		if ( ! $check ) {
       
   878 			return true;
       
   879 		}
       
   880 
       
   881 		$home = parse_url( get_option( 'siteurl' ) );
       
   882 
       
   883 		// Don't block requests back to ourselves by default.
       
   884 		if ( 'localhost' === $check['host'] || ( isset( $home['host'] ) && $home['host'] === $check['host'] ) ) {
       
   885 			/**
       
   886 			 * Filters whether to block local HTTP API requests.
       
   887 			 *
       
   888 			 * A local request is one to `localhost` or to the same host as the site itself.
       
   889 			 *
       
   890 			 * @since 2.8.0
       
   891 			 *
       
   892 			 * @param bool $block Whether to block local requests. Default false.
       
   893 			 */
       
   894 			return apply_filters( 'block_local_requests', false );
       
   895 		}
       
   896 
       
   897 		if ( ! defined( 'WP_ACCESSIBLE_HOSTS' ) ) {
       
   898 			return true;
       
   899 		}
       
   900 
       
   901 		static $accessible_hosts = null;
       
   902 		static $wildcard_regex   = array();
       
   903 		if ( null === $accessible_hosts ) {
       
   904 			$accessible_hosts = preg_split( '|,\s*|', WP_ACCESSIBLE_HOSTS );
       
   905 
       
   906 			if ( false !== strpos( WP_ACCESSIBLE_HOSTS, '*' ) ) {
       
   907 				$wildcard_regex = array();
       
   908 				foreach ( $accessible_hosts as $host ) {
       
   909 					$wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
       
   910 				}
       
   911 				$wildcard_regex = '/^(' . implode( '|', $wildcard_regex ) . ')$/i';
       
   912 			}
       
   913 		}
       
   914 
       
   915 		if ( ! empty( $wildcard_regex ) ) {
       
   916 			return ! preg_match( $wildcard_regex, $check['host'] );
       
   917 		} else {
       
   918 			return ! in_array( $check['host'], $accessible_hosts, true ); // Inverse logic, if it's in the array, then don't block it.
       
   919 		}
       
   920 
       
   921 	}
       
   922 
       
   923 	/**
       
   924 	 * Used as a wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7.
       
   925 	 *
       
   926 	 * @deprecated 4.4.0 Use wp_parse_url()
       
   927 	 * @see wp_parse_url()
       
   928 	 *
       
   929 	 * @param string $url The URL to parse.
       
   930 	 * @return bool|array False on failure; Array of URL components on success;
       
   931 	 *                    See parse_url()'s return values.
       
   932 	 */
       
   933 	protected static function parse_url( $url ) {
       
   934 		_deprecated_function( __METHOD__, '4.4.0', 'wp_parse_url()' );
       
   935 		return wp_parse_url( $url );
       
   936 	}
       
   937 
       
   938 	/**
       
   939 	 * Converts a relative URL to an absolute URL relative to a given URL.
       
   940 	 *
       
   941 	 * If an Absolute URL is provided, no processing of that URL is done.
       
   942 	 *
       
   943 	 * @since 3.4.0
       
   944 	 *
       
   945 	 * @param string $maybe_relative_path The URL which might be relative.
       
   946 	 * @param string $url                 The URL which $maybe_relative_path is relative to.
       
   947 	 * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned.
       
   948 	 */
       
   949 	public static function make_absolute_url( $maybe_relative_path, $url ) {
       
   950 		if ( empty( $url ) ) {
       
   951 			return $maybe_relative_path;
       
   952 		}
       
   953 
       
   954 		$url_parts = wp_parse_url( $url );
       
   955 		if ( ! $url_parts ) {
       
   956 			return $maybe_relative_path;
       
   957 		}
       
   958 
       
   959 		$relative_url_parts = wp_parse_url( $maybe_relative_path );
       
   960 		if ( ! $relative_url_parts ) {
       
   961 			return $maybe_relative_path;
       
   962 		}
       
   963 
       
   964 		// Check for a scheme on the 'relative' URL.
       
   965 		if ( ! empty( $relative_url_parts['scheme'] ) ) {
       
   966 			return $maybe_relative_path;
       
   967 		}
       
   968 
       
   969 		$absolute_path = $url_parts['scheme'] . '://';
       
   970 
       
   971 		// Schemeless URLs will make it this far, so we check for a host in the relative URL
       
   972 		// and convert it to a protocol-URL.
       
   973 		if ( isset( $relative_url_parts['host'] ) ) {
       
   974 			$absolute_path .= $relative_url_parts['host'];
       
   975 			if ( isset( $relative_url_parts['port'] ) ) {
       
   976 				$absolute_path .= ':' . $relative_url_parts['port'];
       
   977 			}
       
   978 		} else {
       
   979 			$absolute_path .= $url_parts['host'];
       
   980 			if ( isset( $url_parts['port'] ) ) {
       
   981 				$absolute_path .= ':' . $url_parts['port'];
       
   982 			}
       
   983 		}
       
   984 
       
   985 		// Start off with the absolute URL path.
       
   986 		$path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/';
       
   987 
       
   988 		// If it's a root-relative path, then great.
       
   989 		if ( ! empty( $relative_url_parts['path'] ) && '/' === $relative_url_parts['path'][0] ) {
       
   990 			$path = $relative_url_parts['path'];
       
   991 
       
   992 			// Else it's a relative path.
       
   993 		} elseif ( ! empty( $relative_url_parts['path'] ) ) {
       
   994 			// Strip off any file components from the absolute path.
       
   995 			$path = substr( $path, 0, strrpos( $path, '/' ) + 1 );
       
   996 
       
   997 			// Build the new path.
       
   998 			$path .= $relative_url_parts['path'];
       
   999 
       
  1000 			// Strip all /path/../ out of the path.
       
  1001 			while ( strpos( $path, '../' ) > 1 ) {
       
  1002 				$path = preg_replace( '![^/]+/\.\./!', '', $path );
       
  1003 			}
       
  1004 
       
  1005 			// Strip any final leading ../ from the path.
       
  1006 			$path = preg_replace( '!^/(\.\./)+!', '', $path );
       
  1007 		}
       
  1008 
       
  1009 		// Add the query string.
       
  1010 		if ( ! empty( $relative_url_parts['query'] ) ) {
       
  1011 			$path .= '?' . $relative_url_parts['query'];
       
  1012 		}
       
  1013 
       
  1014 		return $absolute_path . '/' . ltrim( $path, '/' );
       
  1015 	}
       
  1016 
       
  1017 	/**
       
  1018 	 * Handles an HTTP redirect and follows it if appropriate.
       
  1019 	 *
       
  1020 	 * @since 3.7.0
       
  1021 	 *
       
  1022 	 * @param string $url      The URL which was requested.
       
  1023 	 * @param array  $args     The arguments which were used to make the request.
       
  1024 	 * @param array  $response The response of the HTTP request.
       
  1025 	 * @return array|false|WP_Error An HTTP API response array if the redirect is successfully followed,
       
  1026 	 *                              false if no redirect is present, or a WP_Error object if there's an error.
       
  1027 	 */
       
  1028 	public static function handle_redirects( $url, $args, $response ) {
       
  1029 		// If no redirects are present, or, redirects were not requested, perform no action.
       
  1030 		if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] ) {
       
  1031 			return false;
       
  1032 		}
       
  1033 
       
  1034 		// Only perform redirections on redirection http codes.
       
  1035 		if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 ) {
       
  1036 			return false;
       
  1037 		}
       
  1038 
       
  1039 		// Don't redirect if we've run out of redirects.
       
  1040 		if ( $args['redirection']-- <= 0 ) {
       
  1041 			return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
       
  1042 		}
       
  1043 
       
  1044 		$redirect_location = $response['headers']['location'];
       
  1045 
       
  1046 		// If there were multiple Location headers, use the last header specified.
       
  1047 		if ( is_array( $redirect_location ) ) {
       
  1048 			$redirect_location = array_pop( $redirect_location );
       
  1049 		}
       
  1050 
       
  1051 		$redirect_location = WP_Http::make_absolute_url( $redirect_location, $url );
       
  1052 
       
  1053 		// POST requests should not POST to a redirected location.
       
  1054 		if ( 'POST' === $args['method'] ) {
       
  1055 			if ( in_array( $response['response']['code'], array( 302, 303 ), true ) ) {
       
  1056 				$args['method'] = 'GET';
       
  1057 			}
       
  1058 		}
       
  1059 
       
  1060 		// Include valid cookies in the redirect process.
       
  1061 		if ( ! empty( $response['cookies'] ) ) {
       
  1062 			foreach ( $response['cookies'] as $cookie ) {
       
  1063 				if ( $cookie->test( $redirect_location ) ) {
       
  1064 					$args['cookies'][] = $cookie;
       
  1065 				}
       
  1066 			}
       
  1067 		}
       
  1068 
       
  1069 		return wp_remote_request( $redirect_location, $args );
       
  1070 	}
       
  1071 
       
  1072 	/**
       
  1073 	 * Determines if a specified string represents an IP address or not.
       
  1074 	 *
       
  1075 	 * This function also detects the type of the IP address, returning either
       
  1076 	 * '4' or '6' to represent a IPv4 and IPv6 address respectively.
       
  1077 	 * This does not verify if the IP is a valid IP, only that it appears to be
       
  1078 	 * an IP address.
       
  1079 	 *
       
  1080 	 * @link http://home.deds.nl/~aeron/regex/ for IPv6 regex.
       
  1081 	 *
       
  1082 	 * @since 3.7.0
       
  1083 	 *
       
  1084 	 * @param string $maybe_ip A suspected IP address.
       
  1085 	 * @return int|false Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure
       
  1086 	 */
       
  1087 	public static function is_ip_address( $maybe_ip ) {
       
  1088 		if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) ) {
       
  1089 			return 4;
       
  1090 		}
       
  1091 
       
  1092 		if ( false !== strpos( $maybe_ip, ':' ) && preg_match( '/^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i', trim( $maybe_ip, ' []' ) ) ) {
       
  1093 			return 6;
       
  1094 		}
       
  1095 
       
  1096 		return false;
       
  1097 	}
       
  1098 
       
  1099 }