* HTTP API: WP_Http class
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.
* This class is used to consistently make outgoing HTTP requests easy for developers
* while still being compatible with the many PHP configurations under which
* Debugging includes several actions, which pass different variables for debugging the HTTP API.
// Aliases for HTTP response codes.
const HTTP_CONTINUE = 100;
const SWITCHING_PROTOCOLS = 101;
const NON_AUTHORITATIVE_INFORMATION = 203;
const RESET_CONTENT = 205;
const PARTIAL_CONTENT = 206;
const MULTI_STATUS = 207;
const MULTIPLE_CHOICES = 300;
const MOVED_PERMANENTLY = 301;
const NOT_MODIFIED = 304;
const TEMPORARY_REDIRECT = 307;
const PERMANENT_REDIRECT = 308;
const UNAUTHORIZED = 401;
const PAYMENT_REQUIRED = 402;
const METHOD_NOT_ALLOWED = 405;
const NOT_ACCEPTABLE = 406;
const PROXY_AUTHENTICATION_REQUIRED = 407;
const REQUEST_TIMEOUT = 408;
const LENGTH_REQUIRED = 411;
const PRECONDITION_FAILED = 412;
const REQUEST_ENTITY_TOO_LARGE = 413;
const REQUEST_URI_TOO_LONG = 414;
const UNSUPPORTED_MEDIA_TYPE = 415;
const REQUESTED_RANGE_NOT_SATISFIABLE = 416;
const EXPECTATION_FAILED = 417;
const MISDIRECTED_REQUEST = 421;
const UNPROCESSABLE_ENTITY = 422;
const FAILED_DEPENDENCY = 424;
const UPGRADE_REQUIRED = 426;
const PRECONDITION_REQUIRED = 428;
const TOO_MANY_REQUESTS = 429;
const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
const UNAVAILABLE_FOR_LEGAL_REASONS = 451;
const INTERNAL_SERVER_ERROR = 500;
const NOT_IMPLEMENTED = 501;
const SERVICE_UNAVAILABLE = 503;
const GATEWAY_TIMEOUT = 504;
const HTTP_VERSION_NOT_SUPPORTED = 505;
const VARIANT_ALSO_NEGOTIATES = 506;
const INSUFFICIENT_STORAGE = 507;
const NOT_EXTENDED = 510;
const NETWORK_AUTHENTICATION_REQUIRED = 511;
* Send an HTTP request to a URI.
* Please note: The only URI that are supported in the HTTP Transport implementation
* are the HTTP and HTTPS protocols.
* @param string $url The request URL.
* @param string|array $args {
* Optional. Array or string of HTTP request arguments.
* @type string $method Request method. Accepts 'GET', 'POST', 'HEAD', 'PUT', 'DELETE',
* 'TRACE', 'OPTIONS', or 'PATCH'.
* Some transports technically allow others, but should not be
* assumed. Default 'GET'.
* @type float $timeout How long the connection should stay open in seconds. Default 5.
* @type int $redirection Number of allowed redirects. Not supported by all transports
* @type string $httpversion Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
* @type string $user-agent User-agent value sent.
* Default 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ).
* @type bool $reject_unsafe_urls Whether to pass URLs through wp_http_validate_url().
* @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,
* and processing returned to the calling code immediately, the caller
* will know if the request succeeded or failed, but will not receive
* any response from the remote server. Default true.
* @type string|array $headers Array or string of headers to send with the request.
* @type array $cookies List of cookies to send with the request. Default empty array.
* @type string|array $body Body to send with the request. Default null.
* @type bool $compress Whether to compress the $body when sending the request.
* @type bool $decompress Whether to decompress a compressed response. If set to false and
* compressed content is returned in the response anyway, it will
* need to be separately decompressed. Default true.
* @type bool $sslverify Whether to verify SSL for the request. Default true.
* @type string $sslcertificates Absolute path to an SSL certificate .crt file.
* Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
* @type bool $stream Whether to stream to a file. If set to true and no filename was
* given, it will be droped it in the WP temp dir and its name will
* be set using the basename of the URL. Default false.
* @type string $filename Filename of the file to write to when streaming. $stream must be
* set to true. Default null.
* @type int $limit_response_size Size in bytes to limit the response to. Default null.
* @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
* A WP_Error instance upon error.
public function request( $url, $args = array() ) {
* Filters the timeout value for an HTTP request.
* @since 5.1.0 The `$url` parameter was added.
* @param float $timeout_value Time in seconds until a request times out. Default 5.
* @param string $url The request URL.
'timeout' => apply_filters( 'http_request_timeout', 5, $url ),
* Filters the number of redirects allowed during an HTTP request.
* @since 5.1.0 The `$url` parameter was added.
* @param int $redirect_count Number of redirects allowed. Default 5.
* @param string $url The request URL.
'redirection' => apply_filters( 'http_request_redirection_count', 5, $url ),
* Filters the version of the HTTP protocol used in a request.
* @since 5.1.0 The `$url` parameter was added.
* @param string $version Version of HTTP used. Accepts '1.0' and '1.1'. Default '1.0'.
* @param string $url The request URL.
'httpversion' => apply_filters( 'http_request_version', '1.0', $url ),
* Filters the user agent value sent with an HTTP request.
* @since 5.1.0 The `$url` parameter was added.
* @param string $user_agent WordPress user agent string.
* @param string $url The request URL.
'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), $url ),
* Filters whether to pass URLs through wp_http_validate_url() in an HTTP request.
* @since 5.1.0 The `$url` parameter was added.
* @param bool $pass_url Whether to pass URLs through wp_http_validate_url(). Default false.
* @param string $url The request URL.
'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false, $url ),
'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
'limit_response_size' => null,
// Pre-parse for the HEAD checks.
$args = wp_parse_args( $args );
// By default, HEAD requests do not cause redirections.
if ( isset( $args['method'] ) && 'HEAD' === $args['method'] ) {
$defaults['redirection'] = 0;
$parsed_args = wp_parse_args( $args, $defaults );
* Filters the arguments used in an HTTP request.
* @param array $parsed_args An array of HTTP request arguments.
* @param string $url The request URL.
$parsed_args = apply_filters( 'http_request_args', $parsed_args, $url );
// The transports decrement this, store a copy of the original value for loop purposes.
if ( ! isset( $parsed_args['_redirection'] ) ) {
$parsed_args['_redirection'] = $parsed_args['redirection'];
* Filters the preemptive return value of an HTTP request.
* Returning a non-false value from the filter will short-circuit the HTTP request and return
* early with that value. A filter should return one of:
* - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements
* - boolean false to avoid short-circuiting the response
* Returning any other value may result in unexpected behaviour.
* @param false|array|WP_Error $preempt A preemptive return value of an HTTP request. Default false.
* @param array $parsed_args HTTP request arguments.
* @param string $url The request URL.
$pre = apply_filters( 'pre_http_request', false, $parsed_args, $url );
if ( function_exists( 'wp_kses_bad_protocol' ) ) {
if ( $parsed_args['reject_unsafe_urls'] ) {
$url = wp_http_validate_url( $url );
$url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
$arrURL = parse_url( $url );
if ( empty( $url ) || empty( $arrURL['scheme'] ) ) {
$response = new WP_Error( 'http_request_failed', __( 'A valid URL was not provided.' ) );
/** This action is documented in wp-includes/class-http.php */
do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
if ( $this->block_request( $url ) ) {
$response = new WP_Error( 'http_request_not_executed', __( 'User has blocked requests through HTTP.' ) );
/** This action is documented in wp-includes/class-http.php */
do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
// 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 ( $parsed_args['stream'] ) {
if ( empty( $parsed_args['filename'] ) ) {
$parsed_args['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.
$parsed_args['blocking'] = true;
if ( ! wp_is_writable( dirname( $parsed_args['filename'] ) ) ) {
$response = new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
/** This action is documented in wp-includes/class-http.php */
do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
if ( is_null( $parsed_args['headers'] ) ) {
$parsed_args['headers'] = array();
// WP allows passing in headers as a string, weirdly.
if ( ! is_array( $parsed_args['headers'] ) ) {
$processedHeaders = WP_Http::processHeaders( $parsed_args['headers'] );
$parsed_args['headers'] = $processedHeaders['headers'];
$headers = $parsed_args['headers'];
$data = $parsed_args['body'];
$type = $parsed_args['method'];
'timeout' => $parsed_args['timeout'],
'useragent' => $parsed_args['user-agent'],
'blocking' => $parsed_args['blocking'],
'hooks' => new WP_HTTP_Requests_Hooks( $url, $parsed_args ),
// Ensure redirects follow browser behaviour.
$options['hooks']->register( 'requests.before_redirect', array( get_class(), 'browser_redirect_compatibility' ) );
// Validate redirected URLs.
if ( function_exists( 'wp_kses_bad_protocol' ) && $parsed_args['reject_unsafe_urls'] ) {
$options['hooks']->register( 'requests.before_redirect', array( get_class(), 'validate_redirects' ) );
if ( $parsed_args['stream'] ) {
$options['filename'] = $parsed_args['filename'];
if ( empty( $parsed_args['redirection'] ) ) {
$options['follow_redirects'] = false;
$options['redirects'] = $parsed_args['redirection'];
// Use byte limit, if we can.
if ( isset( $parsed_args['limit_response_size'] ) ) {
$options['max_bytes'] = $parsed_args['limit_response_size'];
// If we've got cookies, use and convert them to Requests_Cookie.
if ( ! empty( $parsed_args['cookies'] ) ) {
$options['cookies'] = WP_Http::normalize_cookies( $parsed_args['cookies'] );
// SSL certificate handling.
if ( ! $parsed_args['sslverify'] ) {
$options['verify'] = false;
$options['verifyname'] = false;
$options['verify'] = $parsed_args['sslcertificates'];
// All non-GET/HEAD requests should put the arguments in the form body.
if ( 'HEAD' !== $type && 'GET' !== $type ) {
$options['data_format'] = 'body';
* Filters whether SSL should be verified for non-local requests.
* @since 5.1.0 The `$url` parameter was added.
* @param bool $ssl_verify Whether to verify the SSL connection. Default true.
* @param string $url The request URL.
$options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'], $url );
$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 ( $proxy->use_authentication() ) {
$options['proxy']->use_authentication = true;
$options['proxy']->user = $proxy->username();
$options['proxy']->pass = $proxy->password();
// Avoid issues where mbstring.func_overload is enabled.
mbstring_binary_safe_encoding();
$requests_response = Requests::request( $url, $headers, $data, $type, $options );
// Convert the response into an array.
$http_response = new WP_HTTP_Requests_Response( $requests_response, $parsed_args['filename'] );
$response = $http_response->to_array();
// 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() );
reset_mbstring_encoding();
* Fires after an HTTP API response is received and before the response is returned.
* @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 $parsed_args HTTP request arguments.
* @param string $url The request URL.
do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
if ( is_wp_error( $response ) ) {
if ( ! $parsed_args['blocking'] ) {
* Filters the HTTP API response immediately before the response is returned.
* @param array $response HTTP response.
* @param array $parsed_args HTTP request arguments.
* @param string $url The request URL.
return apply_filters( 'http_response', $response, $parsed_args, $url );
* Normalizes cookies for using in Requests.
* @param array $cookies Array 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(), array( 'host-only' => $value->host_only ) );
} elseif ( is_scalar( $value ) ) {
$cookie_jar[ $name ] = new Requests_Cookie( $name, $value );
* 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.
* @param string $location URL to redirect to.
* @param array $headers Headers for the redirect.
* @param string|array $data Body to send with the request.
* @param array $options Redirect request options.
* @param Requests_Response $original Response object.
public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) {
// Browser compatibility.
if ( 302 === $original->status_code ) {
$options['type'] = Requests::GET;
* Validate redirected URLs.
* @throws Requests_Exception On unsuccessful URL validation.
* @param string $location URL to redirect to.