3 * Simple and uniform HTTP request API.
5 * Standardizes the HTTP requests for WordPress. Handles cookies, gzip encoding and decoding, chunk
6 * decoding, if HTTP 1.1 and various other difficult HTTP protocol implementations.
8 * @link http://trac.wordpress.org/ticket/4779 HTTP API Proposal
16 * WordPress HTTP Class for managing HTTP Transports and making HTTP requests.
18 * This class is used to consistently make outgoing HTTP requests easy for developers
19 * while still being compatible with the many PHP configurations under which
22 * Debugging includes several actions, which pass different variables for debugging the HTTP API.
31 * Send a HTTP request to a URI.
33 * The body and headers are part of the arguments. The 'body' argument is for the body and will
34 * accept either a string or an array. The 'headers' argument should be an array, but a string
35 * is acceptable. If the 'body' argument is an array, then it will automatically be escaped
36 * using http_build_query().
38 * The only URI that are supported in the HTTP Transport implementation are the HTTP and HTTPS
41 * The defaults are 'method', 'timeout', 'redirection', 'httpversion', 'blocking' and
44 * Accepted 'method' values are 'GET', 'POST', and 'HEAD', some transports technically allow
45 * others, but should not be assumed. The 'timeout' is used to sent how long the connection
46 * should stay open before failing when no response. 'redirection' is used to track how many
47 * redirects were taken and used to sent the amount for other transports, but not all transports
48 * accept setting that value.
50 * The 'httpversion' option is used to sent the HTTP version and accepted values are '1.0', and
51 * '1.1' and should be a string. The 'user-agent' option is the user-agent and is used to
52 * replace the default user-agent, which is 'WordPress/WP_Version', where WP_Version is the
53 * value from $wp_version.
55 * The 'blocking' parameter can be used to specify if the calling code requires the result of
56 * the HTTP request. If set to false, the request will be sent to the remote server, and
57 * processing returned to the calling code immediately, the caller will know if the request
58 * suceeded or failed, but will not receive any response from the remote server.
63 * @param string $url URI resource.
64 * @param str|array $args Optional. Override the defaults.
65 * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
67 function request( $url, $args = array() ) {
73 * Filter the timeout value for an HTTP request.
77 * @param int $timeout_value Time in seconds until a request times out.
80 'timeout' => apply_filters( 'http_request_timeout', 5 ),
82 * Filter the number of redirects allowed during an HTTP request.
86 * @param int $redirect_count Number of redirects allowed. Default 5.
88 'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
90 * Filter the version of the HTTP protocol used in a request.
94 * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
97 'httpversion' => apply_filters( 'http_request_version', '1.0' ),
99 * Filter the user agent value sent with an HTTP request.
103 * @param string $user_agent WordPress user agent string.
105 'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ),
107 * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request.
111 * @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
114 'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
116 'headers' => array(),
117 'cookies' => array(),
120 'decompress' => true,
122 'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
125 'limit_response_size' => null,
128 // Pre-parse for the HEAD checks.
129 $args = wp_parse_args( $args );
131 // By default, Head requests do not cause redirections.
132 if ( isset($args['method']) && 'HEAD' == $args['method'] )
133 $defaults['redirection'] = 0;
135 $r = wp_parse_args( $args, $defaults );
137 * Filter the arguments used in an HTTP request.
141 * @param array $r An array of HTTP request arguments.
142 * @param string $url The request URI resource.
144 $r = apply_filters( 'http_request_args', $r, $url );
146 // The transports decrement this, store a copy of the original value for loop purposes.
147 if ( ! isset( $r['_redirection'] ) )
148 $r['_redirection'] = $r['redirection'];
151 * Filter whether to preempt an HTTP request's return.
153 * Returning a truthy value to the filter will short-circuit
154 * the HTTP request and return early with that value.
158 * @param bool $preempt Whether to preempt an HTTP request return. Default false.
159 * @param array $r HTTP request arguments.
160 * @param string $url The request URI resource.
162 $pre = apply_filters( 'pre_http_request', false, $r, $url );
163 if ( false !== $pre )
166 if ( function_exists( 'wp_kses_bad_protocol' ) ) {
167 if ( $r['reject_unsafe_urls'] )
168 $url = wp_http_validate_url( $url );
169 $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
172 $arrURL = @parse_url( $url );
174 if ( empty( $url ) || empty( $arrURL['scheme'] ) )
175 return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
177 if ( $this->block_request( $url ) )
178 return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
180 // Determine if this is a https call and pass that on to the transport functions
181 // so that we can blacklist the transports that do not support ssl verification
182 $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
184 // Determine if this request is to OUR install of WordPress
185 $homeURL = parse_url( get_bloginfo( 'url' ) );
186 $r['local'] = $homeURL['host'] == $arrURL['host'] || 'localhost' == $arrURL['host'];
189 // If we are streaming to a file but no filename was given drop it in the WP temp dir
190 // and pick its name using the basename of the $url
191 if ( $r['stream'] && empty( $r['filename'] ) )
192 $r['filename'] = get_temp_dir() . basename( $url );
194 // Force some settings if we are streaming to a file and check for existence and perms of destination directory
195 if ( $r['stream'] ) {
196 $r['blocking'] = true;
197 if ( ! wp_is_writable( dirname( $r['filename'] ) ) )
198 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
201 if ( is_null( $r['headers'] ) )
202 $r['headers'] = array();
204 if ( ! is_array( $r['headers'] ) ) {
205 $processedHeaders = WP_Http::processHeaders( $r['headers'], $url );
206 $r['headers'] = $processedHeaders['headers'];
209 if ( isset( $r['headers']['User-Agent'] ) ) {
210 $r['user-agent'] = $r['headers']['User-Agent'];
211 unset( $r['headers']['User-Agent'] );
214 if ( isset( $r['headers']['user-agent'] ) ) {
215 $r['user-agent'] = $r['headers']['user-agent'];
216 unset( $r['headers']['user-agent'] );
219 if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) {
220 $r['headers']['connection'] = 'close';
223 // Construct Cookie: header if any cookies are set
224 WP_Http::buildCookieHeader( $r );
226 // Avoid issues where mbstring.func_overload is enabled
227 mbstring_binary_safe_encoding();
229 if ( ! isset( $r['headers']['Accept-Encoding'] ) ) {
230 if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) )
231 $r['headers']['Accept-Encoding'] = $encoding;
234 if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) {
235 if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
236 $r['body'] = http_build_query( $r['body'], null, '&' );
238 if ( ! isset( $r['headers']['Content-Type'] ) )
239 $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
242 if ( '' === $r['body'] )
245 if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
246 $r['headers']['Content-Length'] = strlen( $r['body'] );
249 $response = $this->_dispatch_request( $url, $r );
251 reset_mbstring_encoding();
253 if ( is_wp_error( $response ) )
256 // Append cookies that were used in this request to the response
257 if ( ! empty( $r['cookies'] ) ) {
258 $cookies_set = wp_list_pluck( $response['cookies'], 'name' );
259 foreach ( $r['cookies'] as $cookie ) {
260 if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) {
261 $response['cookies'][] = $cookie;
270 * Tests which transports are capable of supporting the request.
275 * @param array $args Request arguments
276 * @param string $url URL to Request
278 * @return string|bool Class name for the first transport that claims to support the request. False if no transport claims to support the request.
280 public function _get_first_available_transport( $args, $url = null ) {
282 * Filter which HTTP transports are available and in what order.
286 * @param array $value Array of HTTP transports to check. Default array contains
287 * 'curl', and 'streams', in that order.
288 * @param array $args HTTP request arguments.
289 * @param string $url The URL to request.
291 $request_order = apply_filters( 'http_api_transports', array( 'curl', 'streams' ), $args, $url );
293 // Loop over each transport on each HTTP request looking for one which will serve this request's needs
294 foreach ( $request_order as $transport ) {
295 $class = 'WP_HTTP_' . $transport;
297 // Check to see if this transport is a possibility, calls the transport statically
298 if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
308 * Dispatches a HTTP request to a supporting transport.
310 * Tests each transport in order to find a transport which matches the request arguments.
311 * Also caches the transport instance to be used later.
313 * The order for requests is cURL, and then PHP Streams.
318 * @param string $url URL to Request
319 * @param array $args Request arguments
320 * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
322 private function _dispatch_request( $url, $args ) {
323 static $transports = array();
325 $class = $this->_get_first_available_transport( $args, $url );
327 return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
329 // Transport claims to support request, instantiate it and give it a whirl.
330 if ( empty( $transports[$class] ) )
331 $transports[$class] = new $class;
333 $response = $transports[$class]->request( $url, $args );
336 * Fires after an HTTP API response is received and before the response is returned.
340 * @param mixed $response HTTP Response or WP_Error object.
341 * @param string $context Context under which the hook is fired.
342 * @param string $class HTTP transport used.
343 * @param array $args HTTP request arguments.
344 * @param string $url The request URL.
346 do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
348 if ( is_wp_error( $response ) )
352 * Filter the HTTP API response immediately before the response is returned.
356 * @param array|obj $response HTTP Response.
357 * @param array $args HTTP request arguments.
358 * @param string $url The request URL.
360 return apply_filters( 'http_response', $response, $args, $url );
364 * Uses the POST HTTP method.
366 * Used for sending data that is expected to be in the body.
371 * @param string $url URI resource.
372 * @param string|array $args Optional. Override the defaults.
373 * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
375 function post($url, $args = array()) {
376 $defaults = array('method' => 'POST');
377 $r = wp_parse_args( $args, $defaults );
378 return $this->request($url, $r);
382 * Uses the GET HTTP method.
384 * Used for sending data that is expected to be in the body.
389 * @param string $url URI resource.
390 * @param str|array $args Optional. Override the defaults.
391 * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
393 function get($url, $args = array()) {
394 $defaults = array('method' => 'GET');
395 $r = wp_parse_args( $args, $defaults );
396 return $this->request($url, $r);
400 * Uses the HEAD HTTP method.
402 * Used for sending data that is expected to be in the body.
407 * @param string $url URI resource.
408 * @param str|array $args Optional. Override the defaults.
409 * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
411 function head($url, $args = array()) {
412 $defaults = array('method' => 'HEAD');
413 $r = wp_parse_args( $args, $defaults );
414 return $this->request($url, $r);
418 * Parses the responses and splits the parts into headers and body.
424 * @param string $strResponse The full response string
425 * @return array Array with 'headers' and 'body' keys.
427 public static function processResponse($strResponse) {
428 $res = explode("\r\n\r\n", $strResponse, 2);
430 return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
434 * Transform header string into an array.
436 * If an array is given then it is assumed to be raw header data with numeric keys with the
437 * headers as the values. No headers must be passed that were already processed.
443 * @param string|array $headers
444 * @param string $url The URL that was requested
445 * @return array Processed string headers. If duplicate headers are encountered,
446 * Then a numbered array is returned as the value of that header-key.
448 public static function processHeaders( $headers, $url = '' ) {
449 // split headers, one per array element
450 if ( is_string($headers) ) {
451 // tolerate line terminator: CRLF = LF (RFC 2616 19.3)
452 $headers = str_replace("\r\n", "\n", $headers);
453 // unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>, <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2)
454 $headers = preg_replace('/\n[ \t]/', ' ', $headers);
455 // create the headers array
456 $headers = explode("\n", $headers);
459 $response = array('code' => 0, 'message' => '');
461 // If a redirection has taken place, The headers for each page request may have been passed.
462 // In this case, determine the final HTTP header and parse from there.
463 for ( $i = count($headers)-1; $i >= 0; $i-- ) {
464 if ( !empty($headers[$i]) && false === strpos($headers[$i], ':') ) {
465 $headers = array_splice($headers, $i);
471 $newheaders = array();
472 foreach ( (array) $headers as $tempheader ) {
473 if ( empty($tempheader) )
476 if ( false === strpos($tempheader, ':') ) {
477 $stack = explode(' ', $tempheader, 3);
479 list( , $response['code'], $response['message']) = $stack;
483 list($key, $value) = explode(':', $tempheader, 2);
485 $key = strtolower( $key );
486 $value = trim( $value );
488 if ( isset( $newheaders[ $key ] ) ) {
489 if ( ! is_array( $newheaders[ $key ] ) )
490 $newheaders[$key] = array( $newheaders[ $key ] );
491 $newheaders[ $key ][] = $value;
493 $newheaders[ $key ] = $value;
495 if ( 'set-cookie' == $key )
496 $cookies[] = new WP_Http_Cookie( $value, $url );
499 return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
503 * Takes the arguments for a ::request() and checks for the cookie array.
505 * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances,
506 * which are each parsed into strings and added to the Cookie: header (within the arguments array).
507 * Edits the array by reference.
513 * @param array $r Full array of args passed into ::request()
515 public static function buildCookieHeader( &$r ) {
516 if ( ! empty($r['cookies']) ) {
517 // Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances
518 foreach ( $r['cookies'] as $name => $value ) {
519 if ( ! is_object( $value ) )
520 $r['cookies'][ $name ] = new WP_HTTP_Cookie( array( 'name' => $name, 'value' => $value ) );
523 $cookies_header = '';
524 foreach ( (array) $r['cookies'] as $cookie ) {
525 $cookies_header .= $cookie->getHeaderValue() . '; ';
528 $cookies_header = substr( $cookies_header, 0, -2 );
529 $r['headers']['cookie'] = $cookies_header;
534 * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
536 * Based off the HTTP http_encoding_dechunk function.
538 * @link http://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
544 * @param string $body Body content
545 * @return string Chunked decoded body on success or raw body on failure.
547 public static function chunkTransferDecode( $body ) {
548 // The body is not chunked encoded or is malformed.
549 if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) )
553 $body_original = $body; // We'll be altering $body, so need a backup in case of error
556 $has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match );
557 if ( ! $has_chunk || empty( $match[1] ) )
558 return $body_original;
560 $length = hexdec( $match[1] );
561 $chunk_length = strlen( $match[0] );
563 // Parse out the chunk of data
564 $parsed_body .= substr( $body, $chunk_length, $length );
566 // Remove the chunk from the raw data
567 $body = substr( $body, $length + $chunk_length );
570 if ( '0' === trim( $body ) )
576 * Block requests through the proxy.
578 * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
579 * prevent plugins from working and core functionality, if you don't include api.wordpress.org.
581 * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php
582 * file and this will only allow localhost and your blog to make requests. The constant
583 * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the
584 * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains
585 * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted.
588 * @link http://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
589 * @link http://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
591 * @param string $uri URI of url.
592 * @return bool True to block, false to allow.
594 function block_request($uri) {
595 // We don't need to block requests, because nothing is blocked.
596 if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL )
599 $check = parse_url($uri);
603 $home = parse_url( get_option('siteurl') );
605 // Don't block requests back to ourselves by default
606 if ( $check['host'] == 'localhost' || $check['host'] == $home['host'] ) {
608 * Filter whether to block local requests through the proxy.
612 * @param bool $block Whether to block local requests through proxy.
615 return apply_filters( 'block_local_requests', false );
618 if ( !defined('WP_ACCESSIBLE_HOSTS') )
621 static $accessible_hosts;
622 static $wildcard_regex = false;
623 if ( null == $accessible_hosts ) {
624 $accessible_hosts = preg_split('|,\s*|', WP_ACCESSIBLE_HOSTS);
626 if ( false !== strpos(WP_ACCESSIBLE_HOSTS, '*') ) {
627 $wildcard_regex = array();
628 foreach ( $accessible_hosts as $host )
629 $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
630 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
634 if ( !empty($wildcard_regex) )
635 return !preg_match($wildcard_regex, $check['host']);
637 return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If it's in the array, then we can't access it.
641 static function make_absolute_url( $maybe_relative_path, $url ) {
643 return $maybe_relative_path;
645 // Check for a scheme
646 if ( false !== strpos( $maybe_relative_path, '://' ) )
647 return $maybe_relative_path;
649 if ( ! $url_parts = @parse_url( $url ) )
650 return $maybe_relative_path;
652 if ( ! $relative_url_parts = @parse_url( $maybe_relative_path ) )
653 return $maybe_relative_path;
655 $absolute_path = $url_parts['scheme'] . '://' . $url_parts['host'];
656 if ( isset( $url_parts['port'] ) )
657 $absolute_path .= ':' . $url_parts['port'];
659 // Start off with the Absolute URL path
660 $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/';
662 // If it's a root-relative path, then great
663 if ( ! empty( $relative_url_parts['path'] ) && '/' == $relative_url_parts['path'][0] ) {
664 $path = $relative_url_parts['path'];
666 // Else it's a relative path
667 } elseif ( ! empty( $relative_url_parts['path'] ) ) {
668 // Strip off any file components from the absolute path
669 $path = substr( $path, 0, strrpos( $path, '/' ) + 1 );
671 // Build the new path
672 $path .= $relative_url_parts['path'];
674 // Strip all /path/../ out of the path
675 while ( strpos( $path, '../' ) > 1 ) {
676 $path = preg_replace( '![^/]+/\.\./!', '', $path );
679 // Strip any final leading ../ from the path
680 $path = preg_replace( '!^/(\.\./)+!', '', $path );
683 // Add the Query string
684 if ( ! empty( $relative_url_parts['query'] ) )
685 $path .= '?' . $relative_url_parts['query'];
687 return $absolute_path . '/' . ltrim( $path, '/' );
691 * Handles HTTP Redirects and follows them if appropriate.
695 * @param string $url The URL which was requested.
696 * @param array $args The Arguements which were used to make the request.
697 * @param array $response The Response of the HTTP request.
698 * @return false|object False if no redirect is present, a WP_HTTP or WP_Error result otherwise.
700 static function handle_redirects( $url, $args, $response ) {
701 // If no redirects are present, or, redirects were not requested, perform no action.
702 if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] )
705 // Only perform redirections on redirection http codes
706 if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 )
709 // Don't redirect if we've run out of redirects
710 if ( $args['redirection']-- <= 0 )
711 return new WP_Error( 'http_request_failed', __('Too many redirects.') );
713 $redirect_location = $response['headers']['location'];
715 // If there were multiple Location headers, use the last header specified
716 if ( is_array( $redirect_location ) )
717 $redirect_location = array_pop( $redirect_location );
719 $redirect_location = WP_HTTP::make_absolute_url( $redirect_location, $url );
721 // POST requests should not POST to a redirected location
722 if ( 'POST' == $args['method'] ) {
723 if ( in_array( $response['response']['code'], array( 302, 303 ) ) )
724 $args['method'] = 'GET';
727 // Include valid cookies in the redirect process
728 if ( ! empty( $response['cookies'] ) ) {
729 foreach ( $response['cookies'] as $cookie ) {
730 if ( $cookie->test( $redirect_location ) )
731 $args['cookies'][] = $cookie;
735 return wp_remote_request( $redirect_location, $args );
739 * Determines if a specified string represents an IP address or not.
741 * This function also detects the type of the IP address, returning either
742 * '4' or '6' to represent a IPv4 and IPv6 address respectively.
743 * This does not verify if the IP is a valid IP, only that it appears to be
746 * @see http://home.deds.nl/~aeron/regex/ for IPv6 regex
751 * @param string $maybe_ip A suspected IP address
752 * @return integer|bool Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure
754 static function is_ip_address( $maybe_ip ) {
755 if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) )
758 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, ' []' ) ) )
767 * HTTP request method uses PHP Streams to retrieve the url.
770 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
772 class WP_Http_Streams {
774 * Send a HTTP request to a URI using PHP Streams.
776 * @see WP_Http::request For default options descriptions.
779 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
782 * @param string $url URI resource.
783 * @param string|array $args Optional. Override the defaults.
784 * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
786 function request($url, $args = array()) {
788 'method' => 'GET', 'timeout' => 5,
789 'redirection' => 5, 'httpversion' => '1.0',
791 'headers' => array(), 'body' => null, 'cookies' => array()
794 $r = wp_parse_args( $args, $defaults );
796 if ( isset($r['headers']['User-Agent']) ) {
797 $r['user-agent'] = $r['headers']['User-Agent'];
798 unset($r['headers']['User-Agent']);
799 } else if ( isset($r['headers']['user-agent']) ) {
800 $r['user-agent'] = $r['headers']['user-agent'];
801 unset($r['headers']['user-agent']);
804 // Construct Cookie: header if any cookies are set
805 WP_Http::buildCookieHeader( $r );
807 $arrURL = parse_url($url);
809 $connect_host = $arrURL['host'];
811 $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' );
812 if ( ! isset( $arrURL['port'] ) ) {
813 if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) {
814 $arrURL['port'] = 443;
815 $secure_transport = true;
817 $arrURL['port'] = 80;
821 if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) {
822 if ( isset( $r['headers']['Host'] ) )
823 $arrURL['host'] = $r['headers']['Host'];
825 $arrURL['host'] = $r['headers']['host'];
826 unset( $r['headers']['Host'], $r['headers']['host'] );
829 // Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect to ::1,
830 // which fails when the server is not set up for it. For compatibility, always connect to the IPv4 address.
831 if ( 'localhost' == strtolower( $connect_host ) )
832 $connect_host = '127.0.0.1';
834 $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host;
836 $is_local = isset( $r['local'] ) && $r['local'];
837 $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify'];
840 * Filter whether SSL should be verified for local requests.
844 * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
846 $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
847 } elseif ( ! $is_local ) {
849 * Filter whether SSL should be verified for non-local requests.
853 * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
855 $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
858 $proxy = new WP_HTTP_Proxy();
860 $context = stream_context_create( array(
862 'verify_peer' => $ssl_verify,
863 //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate()
864 'capture_peer_cert' => $ssl_verify,
865 'SNI_enabled' => true,
866 'cafile' => $r['sslcertificates'],
867 'allow_self_signed' => ! $ssl_verify,
871 $timeout = (int) floor( $r['timeout'] );
872 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
873 $connect_timeout = max( $timeout, 1 );
875 $connection_error = null; // Store error number
876 $connection_error_str = null; // Store error string
879 // In the event that the SSL connection fails, silence the many PHP Warnings
880 if ( $secure_transport )
881 $error_reporting = error_reporting(0);
883 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
884 $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
886 $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
888 if ( $secure_transport )
889 error_reporting( $error_reporting );
892 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
893 $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
895 $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
898 if ( false === $handle ) {
899 // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken
900 if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str )
901 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
903 return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str );
906 // Verify that the SSL certificate is valid for this request
907 if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) {
908 if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) )
909 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
912 stream_set_timeout( $handle, $timeout, $utimeout );
914 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
917 $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
919 if ( empty($requestPath) )
922 $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
924 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
925 $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
927 $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
929 if ( isset($r['user-agent']) )
930 $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
932 if ( is_array($r['headers']) ) {
933 foreach ( (array) $r['headers'] as $header => $headerValue )
934 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
936 $strHeaders .= $r['headers'];
939 if ( $proxy->use_authentication() )
940 $strHeaders .= $proxy->authentication_header() . "\r\n";
942 $strHeaders .= "\r\n";
944 if ( ! is_null($r['body']) )
945 $strHeaders .= $r['body'];
947 fwrite($handle, $strHeaders);
949 if ( ! $r['blocking'] ) {
950 stream_set_blocking( $handle, 0 );
952 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
956 $bodyStarted = false;
957 $keep_reading = true;
959 if ( isset( $r['limit_response_size'] ) )
960 $block_size = min( $block_size, $r['limit_response_size'] );
962 // If streaming to a file setup the file handle
963 if ( $r['stream'] ) {
965 $stream_handle = @fopen( $r['filename'], 'w+' );
967 $stream_handle = fopen( $r['filename'], 'w+' );
968 if ( ! $stream_handle )
969 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
972 while ( ! feof($handle) && $keep_reading ) {
973 $block = fread( $handle, $block_size );
974 if ( ! $bodyStarted ) {
975 $strResponse .= $block;
976 if ( strpos( $strResponse, "\r\n\r\n" ) ) {
977 $process = WP_Http::processResponse( $strResponse );
979 $block = $process['body'];
980 unset( $strResponse );
981 $process['body'] = '';
985 $this_block_size = strlen( $block );
987 if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] )
988 $block = substr( $block, 0, ( $r['limit_response_size'] - $bytes_written ) );
990 $bytes_written_to_file = fwrite( $stream_handle, $block );
992 if ( $bytes_written_to_file != $this_block_size ) {
994 fclose( $stream_handle );
995 return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
998 $bytes_written += $bytes_written_to_file;
1000 $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size'];
1003 fclose( $stream_handle );
1007 while ( ! feof( $handle ) && $keep_reading ) {
1008 $block = fread( $handle, $block_size );
1009 $strResponse .= $block;
1010 if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) {
1011 $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4;
1012 $bodyStarted = true;
1014 $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) );
1017 $process = WP_Http::processResponse( $strResponse );
1018 unset( $strResponse );
1024 $arrHeaders = WP_Http::processHeaders( $process['headers'], $url );
1027 'headers' => $arrHeaders['headers'],
1028 'body' => null, // Not yet processed
1029 'response' => $arrHeaders['response'],
1030 'cookies' => $arrHeaders['cookies'],
1031 'filename' => $r['filename']
1035 if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
1036 return $redirect_response;
1038 // If the body was chunk encoded, then decode it.
1039 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
1040 $process['body'] = WP_Http::chunkTransferDecode($process['body']);
1042 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
1043 $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
1045 if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] )
1046 $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] );
1048 $response['body'] = $process['body'];
1054 * Verifies the received SSL certificate against it's Common Names and subjectAltName fields
1056 * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if
1057 * the certificate is valid for the hostname which was requested.
1058 * This function verifies the requested hostname against certificate's subjectAltName field,
1059 * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used.
1061 * IP Address support is included if the request is being made to an IP address.
1066 * @param stream $stream The PHP Stream which the SSL request is being made over
1067 * @param string $host The hostname being requested
1068 * @return bool If the cerficiate presented in $stream is valid for $host
1070 static function verify_ssl_certificate( $stream, $host ) {
1071 $context_options = stream_context_get_options( $stream );
1073 if ( empty( $context_options['ssl']['peer_certificate'] ) )
1076 $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] );
1080 // If the request is being made to an IP address, we'll validate against IP fields in the cert (if they exist)
1081 $host_type = ( WP_HTTP::is_ip_address( $host ) ? 'ip' : 'dns' );
1083 $certificate_hostnames = array();
1084 if ( ! empty( $cert['extensions']['subjectAltName'] ) ) {
1085 $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] );
1086 foreach ( $match_against as $match ) {
1087 list( $match_type, $match_host ) = explode( ':', $match );
1088 if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS:
1089 $certificate_hostnames[] = strtolower( trim( $match_host ) );
1091 } elseif ( !empty( $cert['subject']['CN'] ) ) {
1092 // Only use the CN when the certificate includes no subjectAltName extension
1093 $certificate_hostnames[] = strtolower( $cert['subject']['CN'] );
1096 // Exact hostname/IP matches
1097 if ( in_array( strtolower( $host ), $certificate_hostnames ) )
1100 // IP's can't be wildcards, Stop processing
1101 if ( 'ip' == $host_type )
1104 // Test to see if the domain is at least 2 deep for wildcard support
1105 if ( substr_count( $host, '.' ) < 2 )
1108 // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com
1109 $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host );
1111 return in_array( strtolower( $wildcard_host ), $certificate_hostnames );
1115 * Whether this class can be used for retrieving a URL.
1120 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
1122 * @return boolean False means this class can not be used, true means it can.
1124 public static function test( $args = array() ) {
1125 if ( ! function_exists( 'stream_socket_client' ) )
1128 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
1131 if ( ! extension_loaded( 'openssl' ) )
1133 if ( ! function_exists( 'openssl_x509_parse' ) )
1138 * Filter whether streams can be used as a transport for retrieving a URL.
1142 * @param bool $use_class Whether the class can be used. Default true.
1143 * @param array $args Request arguments.
1145 return apply_filters( 'use_streams_transport', true, $args );
1150 * Deprecated HTTP Transport method which used fsockopen.
1152 * This class is not used, and is included for backwards compatibility only.
1153 * All code should make use of WP_HTTP directly through it's API.
1155 * @see WP_HTTP::request
1158 * @deprecated 3.7.0 Please use WP_HTTP::request() directly
1160 class WP_HTTP_Fsockopen extends WP_HTTP_Streams {
1161 // For backwards compatibility for users who are using the class directly
1165 * HTTP request method uses Curl extension to retrieve the url.
1167 * Requires the Curl extension to be installed.
1169 * @package WordPress
1173 class WP_Http_Curl {
1176 * Temporary header storage for during requests.
1182 private $headers = '';
1185 * Temporary body storage for during requests.
1194 * The maximum amount of data to receive from the remote server.
1200 private $max_body_length = false;
1203 * The file resource used for streaming to file.
1209 private $stream_handle = false;
1212 * Send a HTTP request to a URI using cURL extension.
1217 * @param string $url
1218 * @param str|array $args Optional. Override the defaults.
1219 * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
1221 function request($url, $args = array()) {
1223 'method' => 'GET', 'timeout' => 5,
1224 'redirection' => 5, 'httpversion' => '1.0',
1226 'headers' => array(), 'body' => null, 'cookies' => array()
1229 $r = wp_parse_args( $args, $defaults );
1231 if ( isset($r['headers']['User-Agent']) ) {
1232 $r['user-agent'] = $r['headers']['User-Agent'];
1233 unset($r['headers']['User-Agent']);
1234 } else if ( isset($r['headers']['user-agent']) ) {
1235 $r['user-agent'] = $r['headers']['user-agent'];
1236 unset($r['headers']['user-agent']);
1239 // Construct Cookie: header if any cookies are set.
1240 WP_Http::buildCookieHeader( $r );
1242 $handle = curl_init();
1244 // cURL offers really easy proxy support.
1245 $proxy = new WP_HTTP_Proxy();
1247 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1249 curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1250 curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
1251 curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
1253 if ( $proxy->use_authentication() ) {
1254 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1255 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1259 $is_local = isset($r['local']) && $r['local'];
1260 $ssl_verify = isset($r['sslverify']) && $r['sslverify'];
1262 /** This filter is documented in wp-includes/class-http.php */
1263 $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
1264 } elseif ( ! $is_local ) {
1265 /** This filter is documented in wp-includes/class-http.php */
1266 $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
1269 // CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since
1270 // a value of 0 will allow an unlimited timeout.
1271 $timeout = (int) ceil( $r['timeout'] );
1272 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
1273 curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
1275 curl_setopt( $handle, CURLOPT_URL, $url);
1276 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
1277 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
1278 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
1279 curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] );
1280 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
1281 // The option doesn't work with safe mode or when open_basedir is set, and there's a
1282 // bug #17490 with redirected POST requests, so handle redirections outside Curl.
1283 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
1284 if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4
1285 curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
1287 switch ( $r['method'] ) {
1289 curl_setopt( $handle, CURLOPT_NOBODY, true );
1292 curl_setopt( $handle, CURLOPT_POST, true );
1293 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1296 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
1297 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1300 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
1301 if ( ! is_null( $r['body'] ) )
1302 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1306 if ( true === $r['blocking'] ) {
1307 curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
1308 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
1311 curl_setopt( $handle, CURLOPT_HEADER, false );
1313 if ( isset( $r['limit_response_size'] ) )
1314 $this->max_body_length = intval( $r['limit_response_size'] );
1316 $this->max_body_length = false;
1318 // If streaming to a file open a file handle, and setup our curl streaming handler
1319 if ( $r['stream'] ) {
1321 $this->stream_handle = @fopen( $r['filename'], 'w+' );
1323 $this->stream_handle = fopen( $r['filename'], 'w+' );
1324 if ( ! $this->stream_handle )
1325 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
1327 $this->stream_handle = false;
1330 if ( !empty( $r['headers'] ) ) {
1331 // cURL expects full header strings in each element
1333 foreach ( $r['headers'] as $name => $value ) {
1334 $headers[] = "{$name}: $value";
1336 curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
1339 if ( $r['httpversion'] == '1.0' )
1340 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
1342 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
1345 * Fires before the cURL request is executed.
1347 * Cookies are not currently handled by the HTTP API. This action allows
1348 * plugins to handle cookies themselves.
1352 * @param resource &$handle The cURL handle returned by curl_init().
1353 * @param array $r The HTTP request arguments.
1354 * @param string $url The destination URL.
1356 do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) );
1358 // We don't need to return the body, so don't. Just execute request and return.
1359 if ( ! $r['blocking'] ) {
1360 curl_exec( $handle );
1362 if ( $curl_error = curl_error( $handle ) ) {
1363 curl_close( $handle );
1364 return new WP_Error( 'http_request_failed', $curl_error );
1366 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
1367 curl_close( $handle );
1368 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
1371 curl_close( $handle );
1372 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
1375 $theResponse = curl_exec( $handle );
1376 $theHeaders = WP_Http::processHeaders( $this->headers, $url );
1377 $theBody = $this->body;
1379 $this->headers = '';
1382 $curl_error = curl_errno( $handle );
1384 // If an error occured, or, no response
1385 if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) {
1386 if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error && $r['stream'] ) {
1387 fclose( $this->stream_handle );
1388 return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
1390 if ( $curl_error = curl_error( $handle ) ) {
1391 curl_close( $handle );
1392 return new WP_Error( 'http_request_failed', $curl_error );
1394 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
1395 curl_close( $handle );
1396 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
1400 $response = array();
1401 $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
1402 $response['message'] = get_status_header_desc($response['code']);
1404 curl_close( $handle );
1407 fclose( $this->stream_handle );
1410 'headers' => $theHeaders['headers'],
1412 'response' => $response,
1413 'cookies' => $theHeaders['cookies'],
1414 'filename' => $r['filename']
1418 if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
1419 return $redirect_response;
1421 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
1422 $theBody = WP_Http_Encoding::decompress( $theBody );
1424 $response['body'] = $theBody;
1430 * Grab the headers of the cURL request
1432 * Each header is sent individually to this callback, so we append to the $header property for temporary storage
1438 private function stream_headers( $handle, $headers ) {
1439 $this->headers .= $headers;
1440 return strlen( $headers );
1444 * Grab the body of the cURL request
1446 * The contents of the document are passed in chunks, so we append to the $body property for temporary storage.
1447 * Returning a length shorter than the length of $data passed in will cause cURL to abort the request as "completed"
1453 private function stream_body( $handle, $data ) {
1454 $data_length = strlen( $data );
1456 if ( $this->max_body_length && ( strlen( $this->body ) + $data_length ) > $this->max_body_length )
1457 $data = substr( $data, 0, ( $this->max_body_length - $data_length ) );
1459 if ( $this->stream_handle ) {
1460 $bytes_written = fwrite( $this->stream_handle, $data );
1462 $this->body .= $data;
1463 $bytes_written = $data_length;
1466 // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR
1467 return $bytes_written;
1471 * Whether this class can be used for retrieving an URL.
1476 * @return boolean False means this class can not be used, true means it can.
1478 public static function test( $args = array() ) {
1479 if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
1482 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
1485 $curl_version = curl_version();
1486 if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) // Does this cURL version support SSL requests?
1491 * Filter whether cURL can be used as a transport for retrieving a URL.
1495 * @param bool $use_class Whether the class can be used. Default true.
1496 * @param array $args An array of request arguments.
1498 return apply_filters( 'use_curl_transport', true, $args );
1503 * Adds Proxy support to the WordPress HTTP API.
1505 * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to
1506 * enable proxy support. There are also a few filters that plugins can hook into for some of the
1509 * Please note that only BASIC authentication is supported by most transports.
1510 * cURL MAY support more methods (such as NTLM authentication) depending on your environment.
1512 * The constants are as follows:
1514 * <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li>
1515 * <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li>
1516 * <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li>
1517 * <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li>
1518 * <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy.
1519 * You do not need to have localhost and the blog host in this list, because they will not be passed
1520 * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org</li>
1523 * An example can be as seen below.
1525 * define('WP_PROXY_HOST', '192.168.84.101');
1526 * define('WP_PROXY_PORT', '8080');
1527 * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');
1530 * @link http://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress.
1531 * @link http://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS
1534 class WP_HTTP_Proxy {
1537 * Whether proxy connection should be used.
1541 * @use WP_PROXY_HOST
1542 * @use WP_PROXY_PORT
1546 function is_enabled() {
1547 return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT');
1551 * Whether authentication should be used.
1555 * @use WP_PROXY_USERNAME
1556 * @use WP_PROXY_PASSWORD
1560 function use_authentication() {
1561 return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD');
1565 * Retrieve the host for the proxy server.
1572 if ( defined('WP_PROXY_HOST') )
1573 return WP_PROXY_HOST;
1579 * Retrieve the port for the proxy server.
1586 if ( defined('WP_PROXY_PORT') )
1587 return WP_PROXY_PORT;
1593 * Retrieve the username for proxy authentication.
1599 function username() {
1600 if ( defined('WP_PROXY_USERNAME') )
1601 return WP_PROXY_USERNAME;
1607 * Retrieve the password for proxy authentication.
1613 function password() {
1614 if ( defined('WP_PROXY_PASSWORD') )
1615 return WP_PROXY_PASSWORD;
1621 * Retrieve authentication string for proxy authentication.
1627 function authentication() {
1628 return $this->username() . ':' . $this->password();
1632 * Retrieve header string for proxy authentication.
1638 function authentication_header() {
1639 return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() );
1643 * Whether URL should be sent through the proxy server.
1645 * We want to keep localhost and the blog URL from being sent through the proxy server, because
1646 * some proxies can not handle this. We also have the constant available for defining other
1647 * hosts that won't be sent through the proxy.
1649 * @uses WP_PROXY_BYPASS_HOSTS
1652 * @param string $uri URI to check.
1653 * @return bool True, to send through the proxy and false if, the proxy should not be used.
1655 function send_through_proxy( $uri ) {
1656 // parse_url() only handles http, https type URLs, and will emit E_WARNING on failure.
1657 // This will be displayed on blogs, which is not reasonable.
1658 $check = @parse_url($uri);
1660 // Malformed URL, can not process, but this could mean ssl, so let through anyway.
1661 if ( $check === false )
1664 $home = parse_url( get_option('siteurl') );
1667 * Filter whether to preempt sending the request through the proxy server.
1669 * Returning false will bypass the proxy; returning true will send
1670 * the request through the proxy. Returning null bypasses the filter.
1674 * @param null $override Whether to override the request result. Default null.
1675 * @param string $uri URL to check.
1676 * @param array $check Associative array result of parsing the URI.
1677 * @param array $home Associative array result of parsing the site URL.
1679 $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home );
1680 if ( ! is_null( $result ) )
1683 if ( $check['host'] == 'localhost' || $check['host'] == $home['host'] )
1686 if ( !defined('WP_PROXY_BYPASS_HOSTS') )
1689 static $bypass_hosts;
1690 static $wildcard_regex = false;
1691 if ( null == $bypass_hosts ) {
1692 $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS);
1694 if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) {
1695 $wildcard_regex = array();
1696 foreach ( $bypass_hosts as $host )
1697 $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
1698 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
1702 if ( !empty($wildcard_regex) )
1703 return !preg_match($wildcard_regex, $check['host']);
1705 return !in_array( $check['host'], $bypass_hosts );
1709 * Internal representation of a single cookie.
1711 * Returned cookies are represented using this class, and when cookies are set, if they are not
1712 * already a WP_Http_Cookie() object, then they are turned into one.
1714 * @todo The WordPress convention is to use underscores instead of camelCase for function and method
1715 * names. Need to switch to use underscores instead for the methods.
1717 * @package WordPress
1721 class WP_Http_Cookie {
1740 * When the cookie expires.
1764 * Sets up this cookie object.
1766 * The parameter $data should be either an associative array containing the indices names below
1767 * or a header string detailing it.
1769 * If it's an array, it should include the following elements:
1772 * <li>Value - should NOT be urlencoded already.</li>
1773 * <li>Expires - (optional) String or int (UNIX timestamp).</li>
1774 * <li>Path (optional)</li>
1775 * <li>Domain (optional)</li>
1776 * <li>Port (optional)</li>
1782 * @param string|array $data Raw cookie data.
1783 * @param string $requested_url The URL which the cookie was set on, used for default 'domain' and 'port' values
1785 function __construct( $data, $requested_url = '' ) {
1786 if ( $requested_url )
1787 $arrURL = @parse_url( $requested_url );
1788 if ( isset( $arrURL['host'] ) )
1789 $this->domain = $arrURL['host'];
1790 $this->path = isset( $arrURL['path'] ) ? $arrURL['path'] : '/';
1791 if ( '/' != substr( $this->path, -1 ) )
1792 $this->path = dirname( $this->path ) . '/';
1794 if ( is_string( $data ) ) {
1795 // Assume it's a header string direct from a previous request
1796 $pairs = explode( ';', $data );
1798 // Special handling for first pair; name=value. Also be careful of "=" in value
1799 $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) );
1800 $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 );
1801 $this->name = $name;
1802 $this->value = urldecode( $value );
1803 array_shift( $pairs ); //Removes name=value from items.
1805 // Set everything else as a property
1806 foreach ( $pairs as $pair ) {
1807 $pair = rtrim($pair);
1808 if ( empty($pair) ) //Handles the cookie ending in ; which results in a empty final pair
1811 list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' );
1812 $key = strtolower( trim( $key ) );
1813 if ( 'expires' == $key )
1814 $val = strtotime( $val );
1818 if ( !isset( $data['name'] ) )
1821 // Set properties based directly on parameters
1822 foreach ( array( 'name', 'value', 'path', 'domain', 'port' ) as $field ) {
1823 if ( isset( $data[ $field ] ) )
1824 $this->$field = $data[ $field ];
1827 if ( isset( $data['expires'] ) )
1828 $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] );
1830 $this->expires = null;
1835 * Confirms that it's OK to send this cookie to the URL checked against.
1837 * Decision is based on RFC 2109/2965, so look there for details on validity.
1842 * @param string $url URL you intend to send this cookie to
1843 * @return boolean true if allowed, false otherwise.
1845 function test( $url ) {
1846 if ( is_null( $this->name ) )
1849 // Expires - if expired then nothing else matters
1850 if ( isset( $this->expires ) && time() > $this->expires )
1853 // Get details on the URL we're thinking about sending to
1854 $url = parse_url( $url );
1855 $url['port'] = isset( $url['port'] ) ? $url['port'] : ( 'https' == $url['scheme'] ? 443 : 80 );
1856 $url['path'] = isset( $url['path'] ) ? $url['path'] : '/';
1858 // Values to use for comparison against the URL
1859 $path = isset( $this->path ) ? $this->path : '/';
1860 $port = isset( $this->port ) ? $this->port : null;
1861 $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] );
1862 if ( false === stripos( $domain, '.' ) )
1863 $domain .= '.local';
1865 // Host - very basic check that the request URL ends with the domain restriction (minus leading dot)
1866 $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain;
1867 if ( substr( $url['host'], -strlen( $domain ) ) != $domain )
1870 // Port - supports "port-lists" in the format: "80,8000,8080"
1871 if ( !empty( $port ) && !in_array( $url['port'], explode( ',', $port) ) )
1874 // Path - request path must start with path restriction
1875 if ( substr( $url['path'], 0, strlen( $path ) ) != $path )
1882 * Convert cookie name and value back to header string.
1887 * @return string Header encoded cookie name and value.
1889 function getHeaderValue() {
1890 if ( ! isset( $this->name ) || ! isset( $this->value ) )
1894 * Filter the header-encoded cookie value.
1898 * @param string $value The cookie value.
1899 * @param string $name The cookie name.
1901 return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name );
1905 * Retrieve cookie header for usage in the rest of the WordPress HTTP API.
1912 function getFullHeader() {
1913 return 'Cookie: ' . $this->getHeaderValue();
1918 * Implementation for deflate and gzip transfer encodings.
1920 * Includes RFC 1950, RFC 1951, and RFC 1952.
1923 * @package WordPress
1926 class WP_Http_Encoding {
1929 * Compress raw string using the deflate format.
1931 * Supports the RFC 1951 standard.
1935 * @param string $raw String to compress.
1936 * @param int $level Optional, default is 9. Compression level, 9 is highest.
1937 * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports.
1938 * @return string|bool False on failure.
1940 public static function compress( $raw, $level = 9, $supports = null ) {
1941 return gzdeflate( $raw, $level );
1945 * Decompression of deflated string.
1947 * Will attempt to decompress using the RFC 1950 standard, and if that fails
1948 * then the RFC 1951 standard deflate will be attempted. Finally, the RFC
1949 * 1952 standard gzip decode will be attempted. If all fail, then the
1950 * original compressed string will be returned.
1954 * @param string $compressed String to decompress.
1955 * @param int $length The optional length of the compressed data.
1956 * @return string|bool False on failure.
1958 public static function decompress( $compressed, $length = null ) {
1960 if ( empty($compressed) )
1963 if ( false !== ( $decompressed = @gzinflate( $compressed ) ) )
1964 return $decompressed;
1966 if ( false !== ( $decompressed = WP_Http_Encoding::compatible_gzinflate( $compressed ) ) )
1967 return $decompressed;
1969 if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) )
1970 return $decompressed;
1972 if ( function_exists('gzdecode') ) {
1973 $decompressed = @gzdecode( $compressed );
1975 if ( false !== $decompressed )
1976 return $decompressed;
1983 * Decompression of deflated string while staying compatible with the majority of servers.
1985 * Certain Servers will return deflated data with headers which PHP's gzinflate()
1986 * function cannot handle out of the box. The following function has been created from
1987 * various snippets on the gzinflate() PHP documentation.
1989 * Warning: Magic numbers within. Due to the potential different formats that the compressed
1990 * data may be returned in, some "magic offsets" are needed to ensure proper decompression
1991 * takes place. For a simple progmatic way to determine the magic offset in use, see:
1992 * http://core.trac.wordpress.org/ticket/18273
1995 * @link http://core.trac.wordpress.org/ticket/18273
1996 * @link http://au2.php.net/manual/en/function.gzinflate.php#70875
1997 * @link http://au2.php.net/manual/en/function.gzinflate.php#77336
1999 * @param string $gzData String to decompress.
2000 * @return string|bool False on failure.
2002 public static function compatible_gzinflate($gzData) {
2004 // Compressed data might contain a full header, if so strip it for gzinflate()
2005 if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
2007 $flg = ord( substr($gzData, 3, 1) );
2010 list($xlen) = unpack('v', substr($gzData, $i, 2) );
2011 $i = $i + 2 + $xlen;
2014 $i = strpos($gzData, "\0", $i) + 1;
2016 $i = strpos($gzData, "\0", $i) + 1;
2020 $decompressed = @gzinflate( substr($gzData, $i, -8) );
2021 if ( false !== $decompressed )
2022 return $decompressed;
2025 // Compressed data from java.util.zip.Deflater amongst others.
2026 $decompressed = @gzinflate( substr($gzData, 2) );
2027 if ( false !== $decompressed )
2028 return $decompressed;
2034 * What encoding types to accept and their priority values.
2038 * @return string Types of encoding to accept.
2040 public static function accept_encoding( $url, $args ) {
2042 $compression_enabled = WP_Http_Encoding::is_available();
2044 if ( ! $args['decompress'] ) // decompression specifically disabled
2045 $compression_enabled = false;
2046 elseif ( $args['stream'] ) // disable when streaming to file
2047 $compression_enabled = false;
2048 elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it
2049 $compression_enabled = false;
2051 if ( $compression_enabled ) {
2052 if ( function_exists( 'gzinflate' ) )
2053 $type[] = 'deflate;q=1.0';
2055 if ( function_exists( 'gzuncompress' ) )
2056 $type[] = 'compress;q=0.5';
2058 if ( function_exists( 'gzdecode' ) )
2059 $type[] = 'gzip;q=0.5';
2063 * Filter the allowed encoding types.
2067 * @param array $type Encoding types allowed. Accepts 'gzinflate',
2068 * 'gzuncompress', 'gzdecode'.
2069 * @param string $url URL of the HTTP request.
2070 * @param array $args HTTP request arguments.
2072 $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args );
2074 return implode(', ', $type);
2078 * What encoding the content used when it was compressed to send in the headers.
2082 * @return string Content-Encoding string to send in the header.
2084 public static function content_encoding() {
2089 * Whether the content be decoded based on the headers.
2093 * @param array|string $headers All of the available headers.
2096 public static function should_decode($headers) {
2097 if ( is_array( $headers ) ) {
2098 if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) )
2100 } else if ( is_string( $headers ) ) {
2101 return ( stripos($headers, 'content-encoding:') !== false );
2108 * Whether decompression and compression are supported by the PHP version.
2110 * Each function is tested instead of checking for the zlib extension, to
2111 * ensure that the functions all exist in the PHP version and aren't
2118 public static function is_available() {
2119 return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') );