Completed
Push — master ( a7cd2a...eabd6c )
by Stephen
38:42
created

WP_Http::processResponse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4285
cc 2
eloc 3
nc 2
nop 1
1
<?php
2
/**
3
 * HTTP API: WP_Http class
4
 *
5
 * @package WordPress
6
 * @subpackage HTTP
7
 * @since 2.7.0
8
 */
9
10
/**
11
 * Core class used for managing HTTP transports and making HTTP requests.
12
 *
13
 * This class is used to consistently make outgoing HTTP requests easy for developers
14
 * while still being compatible with the many PHP configurations under which
15
 * WordPress runs.
16
 *
17
 * Debugging includes several actions, which pass different variables for debugging the HTTP API.
18
 *
19
 * @since 2.7.0
20
 */
21
class WP_Http {
22
23
	// Aliases for HTTP response codes.
24
	const HTTP_CONTINUE                   = 100;
25
	const SWITCHING_PROTOCOLS             = 101;
26
	const PROCESSING                      = 102;
27
28
	const OK                              = 200;
29
	const CREATED                         = 201;
30
	const ACCEPTED                        = 202;
31
	const NON_AUTHORITATIVE_INFORMATION   = 203;
32
	const NO_CONTENT                      = 204;
33
	const RESET_CONTENT                   = 205;
34
	const PARTIAL_CONTENT                 = 206;
35
	const MULTI_STATUS                    = 207;
36
	const IM_USED                         = 226;
37
38
	const MULTIPLE_CHOICES                = 300;
39
	const MOVED_PERMANENTLY               = 301;
40
	const FOUND                           = 302;
41
	const SEE_OTHER                       = 303;
42
	const NOT_MODIFIED                    = 304;
43
	const USE_PROXY                       = 305;
44
	const RESERVED                        = 306;
45
	const TEMPORARY_REDIRECT              = 307;
46
	const PERMANENT_REDIRECT              = 308;
47
48
	const BAD_REQUEST                     = 400;
49
	const UNAUTHORIZED                    = 401;
50
	const PAYMENT_REQUIRED                = 402;
51
	const FORBIDDEN                       = 403;
52
	const NOT_FOUND                       = 404;
53
	const METHOD_NOT_ALLOWED              = 405;
54
	const NOT_ACCEPTABLE                  = 406;
55
	const PROXY_AUTHENTICATION_REQUIRED   = 407;
56
	const REQUEST_TIMEOUT                 = 408;
57
	const CONFLICT                        = 409;
58
	const GONE                            = 410;
59
	const LENGTH_REQUIRED                 = 411;
60
	const PRECONDITION_FAILED             = 412;
61
	const REQUEST_ENTITY_TOO_LARGE        = 413;
62
	const REQUEST_URI_TOO_LONG            = 414;
63
	const UNSUPPORTED_MEDIA_TYPE          = 415;
64
	const REQUESTED_RANGE_NOT_SATISFIABLE = 416;
65
	const EXPECTATION_FAILED              = 417;
66
	const IM_A_TEAPOT                     = 418;
67
	const MISDIRECTED_REQUEST             = 421;
68
	const UNPROCESSABLE_ENTITY            = 422;
69
	const LOCKED                          = 423;
70
	const FAILED_DEPENDENCY               = 424;
71
	const UPGRADE_REQUIRED                = 426;
72
	const PRECONDITION_REQUIRED           = 428;
73
	const TOO_MANY_REQUESTS               = 429;
74
	const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
75
	const UNAVAILABLE_FOR_LEGAL_REASONS   = 451;
76
77
	const INTERNAL_SERVER_ERROR           = 500;
78
	const NOT_IMPLEMENTED                 = 501;
79
	const BAD_GATEWAY                     = 502;
80
	const SERVICE_UNAVAILABLE             = 503;
81
	const GATEWAY_TIMEOUT                 = 504;
82
	const HTTP_VERSION_NOT_SUPPORTED      = 505;
83
	const VARIANT_ALSO_NEGOTIATES         = 506;
84
	const INSUFFICIENT_STORAGE            = 507;
85
	const NOT_EXTENDED                    = 510;
86
	const NETWORK_AUTHENTICATION_REQUIRED = 511;
87
88
	/**
89
	 * Send an HTTP request to a URI.
90
	 *
91
	 * Please note: The only URI that are supported in the HTTP Transport implementation
92
	 * are the HTTP and HTTPS protocols.
93
	 *
94
	 * @access public
95
	 * @since 2.7.0
96
	 *
97
	 * @global string $wp_version
98
	 *
99
	 * @param string       $url  The request URL.
100
	 * @param string|array $args {
101
	 *     Optional. Array or string of HTTP request arguments.
102
	 *
103
	 *     @type string       $method              Request method. Accepts 'GET', 'POST', 'HEAD', or 'PUT'.
104
	 *                                             Some transports technically allow others, but should not be
105
	 *                                             assumed. Default 'GET'.
106
	 *     @type int          $timeout             How long the connection should stay open in seconds. Default 5.
107
	 *     @type int          $redirection         Number of allowed redirects. Not supported by all transports
108
	 *                                             Default 5.
109
	 *     @type string       $httpversion         Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
110
	 *                                             Default '1.0'.
111
	 *     @type string       $user-agent          User-agent value sent.
112
	 *                                             Default WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ).
113
	 *     @type bool         $reject_unsafe_urls  Whether to pass URLs through {@see wp_http_validate_url()}.
114
	 *                                             Default false.
115
	 *     @type bool         $blocking            Whether the calling code requires the result of the request.
116
	 *                                             If set to false, the request will be sent to the remote server,
117
	 *                                             and processing returned to the calling code immediately, the caller
118
	 *                                             will know if the request succeeded or failed, but will not receive
119
	 *                                             any response from the remote server. Default true.
120
	 *     @type string|array $headers             Array or string of headers to send with the request.
121
	 *                                             Default empty array.
122
	 *     @type array        $cookies             List of cookies to send with the request. Default empty array.
123
	 *     @type string|array $body                Body to send with the request. Default null.
124
	 *     @type bool         $compress            Whether to compress the $body when sending the request.
125
	 *                                             Default false.
126
	 *     @type bool         $decompress          Whether to decompress a compressed response. If set to false and
127
	 *                                             compressed content is returned in the response anyway, it will
128
	 *                                             need to be separately decompressed. Default true.
129
	 *     @type bool         $sslverify           Whether to verify SSL for the request. Default true.
130
	 *     @type string       sslcertificates      Absolute path to an SSL certificate .crt file.
131
	 *                                             Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
132
	 *     @type bool         $stream              Whether to stream to a file. If set to true and no filename was
133
	 *                                             given, it will be droped it in the WP temp dir and its name will
134
	 *                                             be set using the basename of the URL. Default false.
135
	 *     @type string       $filename            Filename of the file to write to when streaming. $stream must be
136
	 *                                             set to true. Default null.
137
	 *     @type int          $limit_response_size Size in bytes to limit the response to. Default null.
138
	 *
139
	 * }
140
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
141
	 *                        A WP_Error instance upon error.
142
	 */
143
	public function request( $url, $args = array() ) {
144
		global $wp_version;
145
146
		$defaults = array(
147
			'method' => 'GET',
148
			/**
149
			 * Filter the timeout value for an HTTP request.
150
			 *
151
			 * @since 2.7.0
152
			 *
153
			 * @param int $timeout_value Time in seconds until a request times out.
154
			 *                           Default 5.
155
			 */
156
			'timeout' => apply_filters( 'http_request_timeout', 5 ),
157
			/**
158
			 * Filter the number of redirects allowed during an HTTP request.
159
			 *
160
			 * @since 2.7.0
161
			 *
162
			 * @param int $redirect_count Number of redirects allowed. Default 5.
163
			 */
164
			'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
165
			/**
166
			 * Filter the version of the HTTP protocol used in a request.
167
			 *
168
			 * @since 2.7.0
169
			 *
170
			 * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
171
			 *                        Default '1.0'.
172
			 */
173
			'httpversion' => apply_filters( 'http_request_version', '1.0' ),
174
			/**
175
			 * Filter the user agent value sent with an HTTP request.
176
			 *
177
			 * @since 2.7.0
178
			 *
179
			 * @param string $user_agent WordPress user agent string.
180
			 */
181
			'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ),
182
			/**
183
			 * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request.
184
			 *
185
			 * @since 3.6.0
186
			 *
187
			 * @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
188
			 *                       Default false.
189
			 */
190
			'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
191
			'blocking' => true,
192
			'headers' => array(),
193
			'cookies' => array(),
194
			'body' => null,
195
			'compress' => false,
196
			'decompress' => true,
197
			'sslverify' => true,
198
			'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
199
			'stream' => false,
200
			'filename' => null,
201
			'limit_response_size' => null,
202
		);
203
204
		// Pre-parse for the HEAD checks.
205
		$args = wp_parse_args( $args );
206
207
		// By default, Head requests do not cause redirections.
208
		if ( isset($args['method']) && 'HEAD' == $args['method'] )
209
			$defaults['redirection'] = 0;
210
211
		$r = wp_parse_args( $args, $defaults );
212
		/**
213
		 * Filter the arguments used in an HTTP request.
214
		 *
215
		 * @since 2.7.0
216
		 *
217
		 * @param array  $r   An array of HTTP request arguments.
218
		 * @param string $url The request URL.
219
		 */
220
		$r = apply_filters( 'http_request_args', $r, $url );
221
222
		// The transports decrement this, store a copy of the original value for loop purposes.
223
		if ( ! isset( $r['_redirection'] ) )
224
			$r['_redirection'] = $r['redirection'];
225
226
		/**
227
		 * Filter whether to preempt an HTTP request's return value.
228
		 *
229
		 * Returning a non-false value from the filter will short-circuit the HTTP request and return
230
		 * early with that value. A filter should return either:
231
		 *
232
		 *  - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements
233
		 *  - A WP_Error instance
234
		 *  - boolean false (to avoid short-circuiting the response)
235
		 *
236
		 * Returning any other value may result in unexpected behaviour.
237
		 *
238
		 * @since 2.9.0
239
		 *
240
		 * @param false|array|WP_Error $preempt Whether to preempt an HTTP request's return value. Default false.
241
		 * @param array               $r        HTTP request arguments.
242
		 * @param string              $url      The request URL.
243
		 */
244
		$pre = apply_filters( 'pre_http_request', false, $r, $url );
245
246
		if ( false !== $pre )
247
			return $pre;
248
249
		if ( function_exists( 'wp_kses_bad_protocol' ) ) {
250
			if ( $r['reject_unsafe_urls'] )
251
				$url = wp_http_validate_url( $url );
252
			if ( $url ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
253
				$url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
254
			}
255
		}
256
257
		$arrURL = @parse_url( $url );
258
259
		if ( empty( $url ) || empty( $arrURL['scheme'] ) )
260
			return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
261
262
		if ( $this->block_request( $url ) )
263
			return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
264
265
		/*
266
		 * Determine if this is a https call and pass that on to the transport functions
267
		 * so that we can blacklist the transports that do not support ssl verification
268
		 */
269
		$r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
270
271
		// Determine if this request is to OUR install of WordPress.
272
		$homeURL = parse_url( get_bloginfo( 'url' ) );
273
		$r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] );
274
		unset( $homeURL );
275
276
		/*
277
		 * If we are streaming to a file but no filename was given drop it in the WP temp dir
278
		 * and pick its name using the basename of the $url.
279
		 */
280
		if ( $r['stream']  && empty( $r['filename'] ) ) {
281
			$r['filename'] = get_temp_dir() . wp_unique_filename( get_temp_dir(), basename( $url ) );
282
		}
283
284
		/*
285
		 * Force some settings if we are streaming to a file and check for existence and perms
286
		 * of destination directory.
287
		 */
288
		if ( $r['stream'] ) {
289
			$r['blocking'] = true;
290
			if ( ! wp_is_writable( dirname( $r['filename'] ) ) )
291
				return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
292
		}
293
294
		if ( is_null( $r['headers'] ) )
295
			$r['headers'] = array();
296
297
		if ( ! is_array( $r['headers'] ) ) {
298
			$processedHeaders = self::processHeaders( $r['headers'], $url );
299
			$r['headers'] = $processedHeaders['headers'];
300
		}
301
302
		if ( isset( $r['headers']['User-Agent'] ) ) {
303
			$r['user-agent'] = $r['headers']['User-Agent'];
304
			unset( $r['headers']['User-Agent'] );
305
		}
306
307
		if ( isset( $r['headers']['user-agent'] ) ) {
308
			$r['user-agent'] = $r['headers']['user-agent'];
309
			unset( $r['headers']['user-agent'] );
310
		}
311
312
		if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) {
313
			$r['headers']['connection'] = 'close';
314
		}
315
316
		// Construct Cookie: header if any cookies are set.
317
		self::buildCookieHeader( $r );
318
319
		// Avoid issues where mbstring.func_overload is enabled.
320
		mbstring_binary_safe_encoding();
321
322
		if ( ! isset( $r['headers']['Accept-Encoding'] ) ) {
323
			if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) )
324
				$r['headers']['Accept-Encoding'] = $encoding;
325
		}
326
327
		if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) {
328
			if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
329
				$r['body'] = http_build_query( $r['body'], null, '&' );
330
331
				if ( ! isset( $r['headers']['Content-Type'] ) )
332
					$r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
333
			}
334
335
			if ( '' === $r['body'] )
336
				$r['body'] = null;
337
338
			if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
339
				$r['headers']['Content-Length'] = strlen( $r['body'] );
340
		}
341
342
		$response = $this->_dispatch_request( $url, $r );
343
344
		reset_mbstring_encoding();
345
346
		if ( is_wp_error( $response ) )
347
			return $response;
348
349
		// Append cookies that were used in this request to the response
350
		if ( ! empty( $r['cookies'] ) ) {
351
			$cookies_set = wp_list_pluck( $response['cookies'], 'name' );
352
			foreach ( $r['cookies'] as $cookie ) {
353
				if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) {
354
					$response['cookies'][] = $cookie;
355
				}
356
			}
357
		}
358
359
		return $response;
360
	}
361
362
	/**
363
	 * Tests which transports are capable of supporting the request.
364
	 *
365
	 * @since 3.2.0
366
	 * @access public
367
	 *
368
	 * @param array $args Request arguments
369
	 * @param string $url URL to Request
370
	 *
371
	 * @return string|false Class name for the first transport that claims to support the request. False if no transport claims to support the request.
372
	 */
373
	public function _get_first_available_transport( $args, $url = null ) {
374
		$transports = array( 'curl', 'streams' );
375
376
		/**
377
		 * Filter which HTTP transports are available and in what order.
378
		 *
379
		 * @since 3.7.0
380
		 *
381
		 * @param array  $transports Array of HTTP transports to check. Default array contains
382
		 *                           'curl', and 'streams', in that order.
383
		 * @param array  $args       HTTP request arguments.
384
		 * @param string $url        The URL to request.
385
		 */
386
		$request_order = apply_filters( 'http_api_transports', $transports, $args, $url );
387
388
		// Loop over each transport on each HTTP request looking for one which will serve this request's needs.
389
		foreach ( $request_order as $transport ) {
390
			if ( in_array( $transport, $transports ) ) {
391
				$transport = ucfirst( $transport );
392
			}
393
			$class = 'WP_Http_' . $transport;
394
395
			// Check to see if this transport is a possibility, calls the transport statically.
396
			if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
397
				continue;
398
399
			return $class;
400
		}
401
402
		return false;
403
	}
404
405
	/**
406
	 * Dispatches a HTTP request to a supporting transport.
407
	 *
408
	 * Tests each transport in order to find a transport which matches the request arguments.
409
	 * Also caches the transport instance to be used later.
410
	 *
411
	 * The order for requests is cURL, and then PHP Streams.
412
	 *
413
	 * @since 3.2.0
414
	 *
415
	 * @static
416
	 * @access private
417
	 *
418
	 * @param string $url URL to Request
419
	 * @param array $args Request arguments
420
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
421
	 */
422
	private function _dispatch_request( $url, $args ) {
423
		static $transports = array();
424
425
		$class = $this->_get_first_available_transport( $args, $url );
426
		if ( !$class )
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
427
			return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
428
429
		// Transport claims to support request, instantiate it and give it a whirl.
430
		if ( empty( $transports[$class] ) )
431
			$transports[$class] = new $class;
432
433
		$response = $transports[$class]->request( $url, $args );
434
435
		/**
436
		 * Fires after an HTTP API response is received and before the response is returned.
437
		 *
438
		 * @since 2.8.0
439
		 *
440
		 * @param array|WP_Error $response HTTP response or WP_Error object.
441
		 * @param string         $context  Context under which the hook is fired.
442
		 * @param string         $class    HTTP transport used.
443
		 * @param array          $args     HTTP request arguments.
444
		 * @param string         $url      The request URL.
445
		 */
446
		do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
447
448
		if ( is_wp_error( $response ) )
449
			return $response;
450
451
		/**
452
		 * Filter the HTTP API response immediately before the response is returned.
453
		 *
454
		 * @since 2.9.0
455
		 *
456
		 * @param array  $response HTTP response.
457
		 * @param array  $args     HTTP request arguments.
458
		 * @param string $url      The request URL.
459
		 */
460
		return apply_filters( 'http_response', $response, $args, $url );
461
	}
462
463
	/**
464
	 * Uses the POST HTTP method.
465
	 *
466
	 * Used for sending data that is expected to be in the body.
467
	 *
468
	 * @access public
469
	 * @since 2.7.0
470
	 *
471
	 * @param string       $url  The request URL.
472
	 * @param string|array $args Optional. Override the defaults.
473
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
474
	 */
475
	public function post($url, $args = array()) {
476
		$defaults = array('method' => 'POST');
477
		$r = wp_parse_args( $args, $defaults );
478
		return $this->request($url, $r);
479
	}
480
481
	/**
482
	 * Uses the GET HTTP method.
483
	 *
484
	 * Used for sending data that is expected to be in the body.
485
	 *
486
	 * @access public
487
	 * @since 2.7.0
488
	 *
489
	 * @param string $url The request URL.
490
	 * @param string|array $args Optional. Override the defaults.
491
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
492
	 */
493
	public function get($url, $args = array()) {
494
		$defaults = array('method' => 'GET');
495
		$r = wp_parse_args( $args, $defaults );
496
		return $this->request($url, $r);
497
	}
498
499
	/**
500
	 * Uses the HEAD HTTP method.
501
	 *
502
	 * Used for sending data that is expected to be in the body.
503
	 *
504
	 * @access public
505
	 * @since 2.7.0
506
	 *
507
	 * @param string $url The request URL.
508
	 * @param string|array $args Optional. Override the defaults.
509
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
510
	 */
511
	public function head($url, $args = array()) {
512
		$defaults = array('method' => 'HEAD');
513
		$r = wp_parse_args( $args, $defaults );
514
		return $this->request($url, $r);
515
	}
516
517
	/**
518
	 * Parses the responses and splits the parts into headers and body.
519
	 *
520
	 * @access public
521
	 * @static
522
	 * @since 2.7.0
523
	 *
524
	 * @param string $strResponse The full response string
525
	 * @return array Array with 'headers' and 'body' keys.
526
	 */
527
	public static function processResponse($strResponse) {
528
		$res = explode("\r\n\r\n", $strResponse, 2);
529
530
		return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
531
	}
532
533
	/**
534
	 * Transform header string into an array.
535
	 *
536
	 * If an array is given then it is assumed to be raw header data with numeric keys with the
537
	 * headers as the values. No headers must be passed that were already processed.
538
	 *
539
	 * @access public
540
	 * @static
541
	 * @since 2.7.0
542
	 *
543
	 * @param string|array $headers
544
	 * @param string $url The URL that was requested
545
	 * @return array Processed string headers. If duplicate headers are encountered,
546
	 * 					Then a numbered array is returned as the value of that header-key.
547
	 */
548
	public static function processHeaders( $headers, $url = '' ) {
549
		// Split headers, one per array element.
550
		if ( is_string($headers) ) {
551
			// Tolerate line terminator: CRLF = LF (RFC 2616 19.3).
552
			$headers = str_replace("\r\n", "\n", $headers);
553
			/*
554
			 * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>,
555
			 * <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2).
556
			 */
557
			$headers = preg_replace('/\n[ \t]/', ' ', $headers);
558
			// Create the headers array.
559
			$headers = explode("\n", $headers);
560
		}
561
562
		$response = array('code' => 0, 'message' => '');
563
564
		/*
565
		 * If a redirection has taken place, The headers for each page request may have been passed.
566
		 * In this case, determine the final HTTP header and parse from there.
567
		 */
568
		for ( $i = count($headers)-1; $i >= 0; $i-- ) {
569
			if ( !empty($headers[$i]) && false === strpos($headers[$i], ':') ) {
570
				$headers = array_splice($headers, $i);
571
				break;
572
			}
573
		}
574
575
		$cookies = array();
576
		$newheaders = array();
577
		foreach ( (array) $headers as $tempheader ) {
578
			if ( empty($tempheader) )
579
				continue;
580
581
			if ( false === strpos($tempheader, ':') ) {
582
				$stack = explode(' ', $tempheader, 3);
583
				$stack[] = '';
584
				list( , $response['code'], $response['message']) = $stack;
585
				continue;
586
			}
587
588
			list($key, $value) = explode(':', $tempheader, 2);
589
590
			$key = strtolower( $key );
591
			$value = trim( $value );
592
593
			if ( isset( $newheaders[ $key ] ) ) {
594
				if ( ! is_array( $newheaders[ $key ] ) )
595
					$newheaders[$key] = array( $newheaders[ $key ] );
596
				$newheaders[ $key ][] = $value;
597
			} else {
598
				$newheaders[ $key ] = $value;
599
			}
600
			if ( 'set-cookie' == $key )
601
				$cookies[] = new WP_Http_Cookie( $value, $url );
602
		}
603
604
		// Cast the Response Code to an int
605
		$response['code'] = intval( $response['code'] );
606
607
		return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
608
	}
609
610
	/**
611
	 * Takes the arguments for a ::request() and checks for the cookie array.
612
	 *
613
	 * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances,
614
	 * which are each parsed into strings and added to the Cookie: header (within the arguments array).
615
	 * Edits the array by reference.
616
	 *
617
	 * @access public
618
	 * @version 2.8.0
619
	 * @static
620
	 *
621
	 * @param array $r Full array of args passed into ::request()
622
	 */
623
	public static function buildCookieHeader( &$r ) {
624
		if ( ! empty($r['cookies']) ) {
625
			// Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances.
626
			foreach ( $r['cookies'] as $name => $value ) {
627
				if ( ! is_object( $value ) )
628
					$r['cookies'][ $name ] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) );
629
			}
630
631
			$cookies_header = '';
632
			foreach ( (array) $r['cookies'] as $cookie ) {
633
				$cookies_header .= $cookie->getHeaderValue() . '; ';
634
			}
635
636
			$cookies_header = substr( $cookies_header, 0, -2 );
637
			$r['headers']['cookie'] = $cookies_header;
638
		}
639
	}
640
641
	/**
642
	 * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
643
	 *
644
	 * Based off the HTTP http_encoding_dechunk function.
645
	 *
646
	 * @link http://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
647
	 *
648
	 * @access public
649
	 * @since 2.7.0
650
	 * @static
651
	 *
652
	 * @param string $body Body content
653
	 * @return string Chunked decoded body on success or raw body on failure.
654
	 */
655
	public static function chunkTransferDecode( $body ) {
656
		// The body is not chunked encoded or is malformed.
657
		if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) )
658
			return $body;
659
660
		$parsed_body = '';
661
662
		// We'll be altering $body, so need a backup in case of error.
663
		$body_original = $body;
664
665
		while ( true ) {
666
			$has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match );
667
			if ( ! $has_chunk || empty( $match[1] ) )
668
				return $body_original;
669
670
			$length = hexdec( $match[1] );
671
			$chunk_length = strlen( $match[0] );
672
673
			// Parse out the chunk of data.
674
			$parsed_body .= substr( $body, $chunk_length, $length );
675
676
			// Remove the chunk from the raw data.
677
			$body = substr( $body, $length + $chunk_length );
678
679
			// End of the document.
680
			if ( '0' === trim( $body ) )
681
				return $parsed_body;
682
		}
683
	}
684
685
	/**
686
	 * Block requests through the proxy.
687
	 *
688
	 * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
689
	 * prevent plugins from working and core functionality, if you don't include api.wordpress.org.
690
	 *
691
	 * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php
692
	 * file and this will only allow localhost and your site to make requests. The constant
693
	 * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the
694
	 * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains
695
	 * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted.
696
	 *
697
	 * @since 2.8.0
698
	 * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
699
	 * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
700
	 *
701
	 * @staticvar array|null $accessible_hosts
702
	 * @staticvar array      $wildcard_regex
703
	 *
704
	 * @param string $uri URI of url.
705
	 * @return bool True to block, false to allow.
706
	 */
707
	public function block_request($uri) {
708
		// We don't need to block requests, because nothing is blocked.
709
		if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL )
710
			return false;
711
712
		$check = parse_url($uri);
713
		if ( ! $check )
714
			return true;
715
716
		$home = parse_url( get_option('siteurl') );
717
718
		// Don't block requests back to ourselves by default.
719 View Code Duplication
		if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) {
720
			/**
721
			 * Filter whether to block local requests through the proxy.
722
			 *
723
			 * @since 2.8.0
724
			 *
725
			 * @param bool $block Whether to block local requests through proxy.
726
			 *                    Default false.
727
			 */
728
			return apply_filters( 'block_local_requests', false );
729
		}
730
731
		if ( !defined('WP_ACCESSIBLE_HOSTS') )
732
			return true;
733
734
		static $accessible_hosts = null;
735
		static $wildcard_regex = array();
736 View Code Duplication
		if ( null === $accessible_hosts ) {
737
			$accessible_hosts = preg_split('|,\s*|', WP_ACCESSIBLE_HOSTS);
738
739
			if ( false !== strpos(WP_ACCESSIBLE_HOSTS, '*') ) {
740
				$wildcard_regex = array();
741
				foreach ( $accessible_hosts as $host )
742
					$wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
743
				$wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
744
			}
745
		}
746
747 View Code Duplication
		if ( !empty($wildcard_regex) )
748
			return !preg_match($wildcard_regex, $check['host']);
749
		else
750
			return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If it's in the array, then we can't access it.
751
752
	}
753
754
	/**
755
	 * Used as a wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7.
756
	 *
757
	 * @access protected
758
	 * @deprecated 4.4.0 Use wp_parse_url()
759
	 * @see wp_parse_url()
760
	 *
761
	 * @param string $url The URL to parse.
762
	 * @return bool|array False on failure; Array of URL components on success;
763
	 *                    See parse_url()'s return values.
764
	 */
765
	protected static function parse_url( $url ) {
766
		_deprecated_function( __METHOD__, '4.4.0', 'wp_parse_url()' );
767
		return wp_parse_url( $url );
768
	}
769
770
	/**
771
	 * Converts a relative URL to an absolute URL relative to a given URL.
772
	 *
773
	 * If an Absolute URL is provided, no processing of that URL is done.
774
	 *
775
	 * @since 3.4.0
776
	 *
777
	 * @static
778
	 * @access public
779
	 *
780
	 * @param string $maybe_relative_path The URL which might be relative
781
	 * @param string $url                 The URL which $maybe_relative_path is relative to
782
	 * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned.
783
	 */
784
	public static function make_absolute_url( $maybe_relative_path, $url ) {
785
		if ( empty( $url ) )
786
			return $maybe_relative_path;
787
788
		if ( ! $url_parts = wp_parse_url( $url ) ) {
789
			return $maybe_relative_path;
790
		}
791
792
		if ( ! $relative_url_parts = wp_parse_url( $maybe_relative_path ) ) {
793
			return $maybe_relative_path;
794
		}
795
796
		// Check for a scheme on the 'relative' url
797
		if ( ! empty( $relative_url_parts['scheme'] ) ) {
798
			return $maybe_relative_path;
799
		}
800
801
		$absolute_path = $url_parts['scheme'] . '://';
802
803
		// Schemeless URL's will make it this far, so we check for a host in the relative url and convert it to a protocol-url
804
		if ( isset( $relative_url_parts['host'] ) ) {
805
			$absolute_path .= $relative_url_parts['host'];
806
			if ( isset( $relative_url_parts['port'] ) )
807
				$absolute_path .= ':' . $relative_url_parts['port'];
808
		} else {
809
			$absolute_path .= $url_parts['host'];
810
			if ( isset( $url_parts['port'] ) )
811
				$absolute_path .= ':' . $url_parts['port'];
812
		}
813
814
		// Start off with the Absolute URL path.
815
		$path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/';
816
817
		// If it's a root-relative path, then great.
818
		if ( ! empty( $relative_url_parts['path'] ) && '/' == $relative_url_parts['path'][0] ) {
819
			$path = $relative_url_parts['path'];
820
821
		// Else it's a relative path.
822
		} elseif ( ! empty( $relative_url_parts['path'] ) ) {
823
			// Strip off any file components from the absolute path.
824
			$path = substr( $path, 0, strrpos( $path, '/' ) + 1 );
825
826
			// Build the new path.
827
			$path .= $relative_url_parts['path'];
828
829
			// Strip all /path/../ out of the path.
830
			while ( strpos( $path, '../' ) > 1 ) {
831
				$path = preg_replace( '![^/]+/\.\./!', '', $path );
832
			}
833
834
			// Strip any final leading ../ from the path.
835
			$path = preg_replace( '!^/(\.\./)+!', '', $path );
836
		}
837
838
		// Add the Query string.
839
		if ( ! empty( $relative_url_parts['query'] ) )
840
			$path .= '?' . $relative_url_parts['query'];
841
842
		return $absolute_path . '/' . ltrim( $path, '/' );
843
	}
844
845
	/**
846
	 * Handles HTTP Redirects and follows them if appropriate.
847
	 *
848
	 * @since 3.7.0
849
	 *
850
	 * @static
851
	 *
852
	 * @param string $url The URL which was requested.
853
	 * @param array $args The Arguments which were used to make the request.
854
	 * @param array $response The Response of the HTTP request.
855
	 * @return false|object False if no redirect is present, a WP_HTTP or WP_Error result otherwise.
856
	 */
857
	public static function handle_redirects( $url, $args, $response ) {
858
		// If no redirects are present, or, redirects were not requested, perform no action.
859
		if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] )
860
			return false;
861
862
		// Only perform redirections on redirection http codes.
863
		if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 )
864
			return false;
865
866
		// Don't redirect if we've run out of redirects.
867
		if ( $args['redirection']-- <= 0 )
868
			return new WP_Error( 'http_request_failed', __('Too many redirects.') );
869
870
		$redirect_location = $response['headers']['location'];
871
872
		// If there were multiple Location headers, use the last header specified.
873
		if ( is_array( $redirect_location ) )
874
			$redirect_location = array_pop( $redirect_location );
875
876
		$redirect_location = WP_Http::make_absolute_url( $redirect_location, $url );
877
878
		// POST requests should not POST to a redirected location.
879
		if ( 'POST' == $args['method'] ) {
880
			if ( in_array( $response['response']['code'], array( 302, 303 ) ) )
881
				$args['method'] = 'GET';
882
		}
883
884
		// Include valid cookies in the redirect process.
885
		if ( ! empty( $response['cookies'] ) ) {
886
			foreach ( $response['cookies'] as $cookie ) {
887
				if ( $cookie->test( $redirect_location ) )
888
					$args['cookies'][] = $cookie;
889
			}
890
		}
891
892
		return wp_remote_request( $redirect_location, $args );
893
	}
894
895
	/**
896
	 * Determines if a specified string represents an IP address or not.
897
	 *
898
	 * This function also detects the type of the IP address, returning either
899
	 * '4' or '6' to represent a IPv4 and IPv6 address respectively.
900
	 * This does not verify if the IP is a valid IP, only that it appears to be
901
	 * an IP address.
902
	 *
903
	 * @link http://home.deds.nl/~aeron/regex/ for IPv6 regex
904
	 *
905
	 * @since 3.7.0
906
	 * @static
907
	 *
908
	 * @param string $maybe_ip A suspected IP address
909
	 * @return integer|bool Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure
910
	 */
911
	public static function is_ip_address( $maybe_ip ) {
912
		if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) )
913
			return 4;
914
915
		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, ' []' ) ) )
916
			return 6;
917
918
		return false;
919
	}
920
921
}
922