WP_Http   F
last analyzed

Complexity

Total Complexity 117

Size/Duplication

Total Lines 1010
Duplicated Lines 2.48 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 25
loc 1010
rs 3.9999
c 0
b 0
f 0
wmc 117
lcom 1
cbo 11

17 Methods

Rating   Name   Duplication   Size   Complexity  
F request() 0 278 31
A normalize_cookies() 0 13 4
A validate_redirects() 0 5 2
B _get_first_available_transport() 0 31 4
B _dispatch_request() 0 30 4
A post() 0 5 1
A get() 0 5 1
A head() 0 5 1
A processResponse() 0 5 2
C processHeaders() 0 61 11
B buildCookieHeader() 0 17 5
B chunkTransferDecode() 0 29 6
C block_request() 25 46 12
A parse_url() 0 4 1
C make_absolute_url() 0 60 14
C handle_redirects() 0 37 12
A is_ip_address() 0 9 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP_Http often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WP_Http, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * HTTP API: WP_Http class
4
 *
5
 * @package WordPress
6
 * @subpackage HTTP
7
 * @since 2.7.0
8
 */
9
10
if ( ! class_exists( 'Requests' ) ) {
11
	require( ABSPATH . WPINC . '/class-requests.php' );
12
13
	Requests::register_autoloader();
14
	Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' );
15
}
16
17
/**
18
 * Core class used for managing HTTP transports and making HTTP requests.
19
 *
20
 * This class is used to consistently make outgoing HTTP requests easy for developers
21
 * while still being compatible with the many PHP configurations under which
22
 * WordPress runs.
23
 *
24
 * Debugging includes several actions, which pass different variables for debugging the HTTP API.
25
 *
26
 * @since 2.7.0
27
 */
28
class WP_Http {
29
30
	// Aliases for HTTP response codes.
31
	const HTTP_CONTINUE                   = 100;
32
	const SWITCHING_PROTOCOLS             = 101;
33
	const PROCESSING                      = 102;
34
35
	const OK                              = 200;
36
	const CREATED                         = 201;
37
	const ACCEPTED                        = 202;
38
	const NON_AUTHORITATIVE_INFORMATION   = 203;
39
	const NO_CONTENT                      = 204;
40
	const RESET_CONTENT                   = 205;
41
	const PARTIAL_CONTENT                 = 206;
42
	const MULTI_STATUS                    = 207;
43
	const IM_USED                         = 226;
44
45
	const MULTIPLE_CHOICES                = 300;
46
	const MOVED_PERMANENTLY               = 301;
47
	const FOUND                           = 302;
48
	const SEE_OTHER                       = 303;
49
	const NOT_MODIFIED                    = 304;
50
	const USE_PROXY                       = 305;
51
	const RESERVED                        = 306;
52
	const TEMPORARY_REDIRECT              = 307;
53
	const PERMANENT_REDIRECT              = 308;
54
55
	const BAD_REQUEST                     = 400;
56
	const UNAUTHORIZED                    = 401;
57
	const PAYMENT_REQUIRED                = 402;
58
	const FORBIDDEN                       = 403;
59
	const NOT_FOUND                       = 404;
60
	const METHOD_NOT_ALLOWED              = 405;
61
	const NOT_ACCEPTABLE                  = 406;
62
	const PROXY_AUTHENTICATION_REQUIRED   = 407;
63
	const REQUEST_TIMEOUT                 = 408;
64
	const CONFLICT                        = 409;
65
	const GONE                            = 410;
66
	const LENGTH_REQUIRED                 = 411;
67
	const PRECONDITION_FAILED             = 412;
68
	const REQUEST_ENTITY_TOO_LARGE        = 413;
69
	const REQUEST_URI_TOO_LONG            = 414;
70
	const UNSUPPORTED_MEDIA_TYPE          = 415;
71
	const REQUESTED_RANGE_NOT_SATISFIABLE = 416;
72
	const EXPECTATION_FAILED              = 417;
73
	const IM_A_TEAPOT                     = 418;
74
	const MISDIRECTED_REQUEST             = 421;
75
	const UNPROCESSABLE_ENTITY            = 422;
76
	const LOCKED                          = 423;
77
	const FAILED_DEPENDENCY               = 424;
78
	const UPGRADE_REQUIRED                = 426;
79
	const PRECONDITION_REQUIRED           = 428;
80
	const TOO_MANY_REQUESTS               = 429;
81
	const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
82
	const UNAVAILABLE_FOR_LEGAL_REASONS   = 451;
83
84
	const INTERNAL_SERVER_ERROR           = 500;
85
	const NOT_IMPLEMENTED                 = 501;
86
	const BAD_GATEWAY                     = 502;
87
	const SERVICE_UNAVAILABLE             = 503;
88
	const GATEWAY_TIMEOUT                 = 504;
89
	const HTTP_VERSION_NOT_SUPPORTED      = 505;
90
	const VARIANT_ALSO_NEGOTIATES         = 506;
91
	const INSUFFICIENT_STORAGE            = 507;
92
	const NOT_EXTENDED                    = 510;
93
	const NETWORK_AUTHENTICATION_REQUIRED = 511;
94
95
	/**
96
	 * Send an HTTP request to a URI.
97
	 *
98
	 * Please note: The only URI that are supported in the HTTP Transport implementation
99
	 * are the HTTP and HTTPS protocols.
100
	 *
101
	 * @access public
102
	 * @since 2.7.0
103
	 *
104
	 * @param string       $url  The request URL.
105
	 * @param string|array $args {
106
	 *     Optional. Array or string of HTTP request arguments.
107
	 *
108
	 *     @type string       $method              Request method. Accepts 'GET', 'POST', 'HEAD', or 'PUT'.
109
	 *                                             Some transports technically allow others, but should not be
110
	 *                                             assumed. Default 'GET'.
111
	 *     @type int          $timeout             How long the connection should stay open in seconds. Default 5.
112
	 *     @type int          $redirection         Number of allowed redirects. Not supported by all transports
113
	 *                                             Default 5.
114
	 *     @type string       $httpversion         Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
115
	 *                                             Default '1.0'.
116
	 *     @type string       $user-agent          User-agent value sent.
117
	 *                                             Default WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ).
118
	 *     @type bool         $reject_unsafe_urls  Whether to pass URLs through wp_http_validate_url().
119
	 *                                             Default false.
120
	 *     @type bool         $blocking            Whether the calling code requires the result of the request.
121
	 *                                             If set to false, the request will be sent to the remote server,
122
	 *                                             and processing returned to the calling code immediately, the caller
123
	 *                                             will know if the request succeeded or failed, but will not receive
124
	 *                                             any response from the remote server. Default true.
125
	 *     @type string|array $headers             Array or string of headers to send with the request.
126
	 *                                             Default empty array.
127
	 *     @type array        $cookies             List of cookies to send with the request. Default empty array.
128
	 *     @type string|array $body                Body to send with the request. Default null.
129
	 *     @type bool         $compress            Whether to compress the $body when sending the request.
130
	 *                                             Default false.
131
	 *     @type bool         $decompress          Whether to decompress a compressed response. If set to false and
132
	 *                                             compressed content is returned in the response anyway, it will
133
	 *                                             need to be separately decompressed. Default true.
134
	 *     @type bool         $sslverify           Whether to verify SSL for the request. Default true.
135
	 *     @type string       sslcertificates      Absolute path to an SSL certificate .crt file.
136
	 *                                             Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
137
	 *     @type bool         $stream              Whether to stream to a file. If set to true and no filename was
138
	 *                                             given, it will be droped it in the WP temp dir and its name will
139
	 *                                             be set using the basename of the URL. Default false.
140
	 *     @type string       $filename            Filename of the file to write to when streaming. $stream must be
141
	 *                                             set to true. Default null.
142
	 *     @type int          $limit_response_size Size in bytes to limit the response to. Default null.
143
	 *
144
	 * }
145
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
146
	 *                        A WP_Error instance upon error.
147
	 */
148
	public function request( $url, $args = array() ) {
149
		$defaults = array(
150
			'method' => 'GET',
151
			/**
152
			 * Filters the timeout value for an HTTP request.
153
			 *
154
			 * @since 2.7.0
155
			 *
156
			 * @param int $timeout_value Time in seconds until a request times out.
157
			 *                           Default 5.
158
			 */
159
			'timeout' => apply_filters( 'http_request_timeout', 5 ),
160
			/**
161
			 * Filters the number of redirects allowed during an HTTP request.
162
			 *
163
			 * @since 2.7.0
164
			 *
165
			 * @param int $redirect_count Number of redirects allowed. Default 5.
166
			 */
167
			'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
168
			/**
169
			 * Filters the version of the HTTP protocol used in a request.
170
			 *
171
			 * @since 2.7.0
172
			 *
173
			 * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
174
			 *                        Default '1.0'.
175
			 */
176
			'httpversion' => apply_filters( 'http_request_version', '1.0' ),
177
			/**
178
			 * Filters the user agent value sent with an HTTP request.
179
			 *
180
			 * @since 2.7.0
181
			 *
182
			 * @param string $user_agent WordPress user agent string.
183
			 */
184
			'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) ),
185
			/**
186
			 * Filters whether to pass URLs through wp_http_validate_url() in an HTTP request.
187
			 *
188
			 * @since 3.6.0
189
			 *
190
			 * @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
191
			 *                       Default false.
192
			 */
193
			'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
194
			'blocking' => true,
195
			'headers' => array(),
196
			'cookies' => array(),
197
			'body' => null,
198
			'compress' => false,
199
			'decompress' => true,
200
			'sslverify' => true,
201
			'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
202
			'stream' => false,
203
			'filename' => null,
204
			'limit_response_size' => null,
205
		);
206
207
		// Pre-parse for the HEAD checks.
208
		$args = wp_parse_args( $args );
209
210
		// By default, Head requests do not cause redirections.
211
		if ( isset($args['method']) && 'HEAD' == $args['method'] )
212
			$defaults['redirection'] = 0;
213
214
		$r = wp_parse_args( $args, $defaults );
215
		/**
216
		 * Filters the arguments used in an HTTP request.
217
		 *
218
		 * @since 2.7.0
219
		 *
220
		 * @param array  $r   An array of HTTP request arguments.
221
		 * @param string $url The request URL.
222
		 */
223
		$r = apply_filters( 'http_request_args', $r, $url );
224
225
		// The transports decrement this, store a copy of the original value for loop purposes.
226
		if ( ! isset( $r['_redirection'] ) )
227
			$r['_redirection'] = $r['redirection'];
228
229
		/**
230
		 * Filters whether to preempt an HTTP request's return value.
231
		 *
232
		 * Returning a non-false value from the filter will short-circuit the HTTP request and return
233
		 * early with that value. A filter should return either:
234
		 *
235
		 *  - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements
236
		 *  - A WP_Error instance
237
		 *  - boolean false (to avoid short-circuiting the response)
238
		 *
239
		 * Returning any other value may result in unexpected behaviour.
240
		 *
241
		 * @since 2.9.0
242
		 *
243
		 * @param false|array|WP_Error $preempt Whether to preempt an HTTP request's return value. Default false.
244
		 * @param array               $r        HTTP request arguments.
245
		 * @param string              $url      The request URL.
246
		 */
247
		$pre = apply_filters( 'pre_http_request', false, $r, $url );
248
249
		if ( false !== $pre )
250
			return $pre;
251
252
		if ( function_exists( 'wp_kses_bad_protocol' ) ) {
253
			if ( $r['reject_unsafe_urls'] ) {
254
				$url = wp_http_validate_url( $url );
255
			}
256
			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...
257
				$url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
258
			}
259
		}
260
261
		$arrURL = @parse_url( $url );
262
263
		if ( empty( $url ) || empty( $arrURL['scheme'] ) ) {
264
			return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
265
		}
266
267
		if ( $this->block_request( $url ) ) {
268
			return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
269
		}
270
271
		// If we are streaming to a file but no filename was given drop it in the WP temp dir
272
		// and pick its name using the basename of the $url
273
		if ( $r['stream'] ) {
274
			if ( empty( $r['filename'] ) ) {
275
				$r['filename'] = get_temp_dir() . basename( $url );
276
			}
277
278
			// Force some settings if we are streaming to a file and check for existence and perms of destination directory
279
			$r['blocking'] = true;
280
			if ( ! wp_is_writable( dirname( $r['filename'] ) ) ) {
281
				return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
282
			}
283
		}
284
285
		if ( is_null( $r['headers'] ) ) {
286
			$r['headers'] = array();
287
		}
288
289
		// WP allows passing in headers as a string, weirdly.
290
		if ( ! is_array( $r['headers'] ) ) {
291
			$processedHeaders = WP_Http::processHeaders( $r['headers'] );
292
			$r['headers'] = $processedHeaders['headers'];
293
		}
294
295
		// Setup arguments
296
		$headers = $r['headers'];
297
		$data = $r['body'];
298
		$type = $r['method'];
299
		$options = array(
300
			'timeout' => $r['timeout'],
301
			'useragent' => $r['user-agent'],
302
			'blocking' => $r['blocking'],
303
			'hooks' => new WP_HTTP_Requests_Hooks( $url, $r ),
304
		);
305
306
		// Ensure redirects follow browser behaviour.
307
		$options['hooks']->register( 'requests.before_redirect', array( get_class(), 'browser_redirect_compatibility' ) );
308
309
		// Validate redirected URLs.
310
		if ( function_exists( 'wp_kses_bad_protocol' ) && $r['reject_unsafe_urls'] ) {
311
			$options['hooks']->register( 'requests.before_redirect', array( get_class(), 'validate_redirects' ) );
312
		}
313
314
		if ( $r['stream'] ) {
315
			$options['filename'] = $r['filename'];
316
		}
317
		if ( empty( $r['redirection'] ) ) {
318
			$options['follow_redirects'] = false;
319
		} else {
320
			$options['redirects'] = $r['redirection'];
321
		}
322
323
		// Use byte limit, if we can
324
		if ( isset( $r['limit_response_size'] ) ) {
325
			$options['max_bytes'] = $r['limit_response_size'];
326
		}
327
328
		// If we've got cookies, use and convert them to Requests_Cookie.
329
		if ( ! empty( $r['cookies'] ) ) {
330
			$options['cookies'] = WP_Http::normalize_cookies( $r['cookies'] );
331
		}
332
333
		// SSL certificate handling
334
		if ( ! $r['sslverify'] ) {
335
			$options['verify'] = false;
336
			$options['verifyname'] = false;
337
		} else {
338
			$options['verify'] = $r['sslcertificates'];
339
		}
340
341
		// All non-GET/HEAD requests should put the arguments in the form body.
342
		if ( 'HEAD' !== $type && 'GET' !== $type ) {
343
			$options['data_format'] = 'body';
344
		}
345
346
		/**
347
		 * Filters whether SSL should be verified for non-local requests.
348
		 *
349
		 * @since 2.8.0
350
		 *
351
		 * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
352
		 */
353
		$options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'] );
354
355
		// Check for proxies.
356
		$proxy = new WP_HTTP_Proxy();
357
		if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
358
			$options['proxy'] = new Requests_Proxy_HTTP( $proxy->host() . ':' . $proxy->port() );
359
360
			if ( $proxy->use_authentication() ) {
361
				$options['proxy']->use_authentication = true;
362
				$options['proxy']->user = $proxy->username();
363
				$options['proxy']->pass = $proxy->password();
364
			}
365
		}
366
367
		// Avoid issues where mbstring.func_overload is enabled
368
		mbstring_binary_safe_encoding();
369
370
		try {
371
			$requests_response = Requests::request( $url, $headers, $data, $type, $options );
372
373
			// Convert the response into an array
374
			$http_response = new WP_HTTP_Requests_Response( $requests_response, $r['filename'] );
375
			$response = $http_response->to_array();
376
377
			// Add the original object to the array.
378
			$response['http_response'] = $http_response;
379
		}
380
		catch ( Requests_Exception $e ) {
381
			$response = new WP_Error( 'http_request_failed', $e->getMessage() );
382
		}
383
384
		reset_mbstring_encoding();
385
386
		/**
387
		 * Fires after an HTTP API response is received and before the response is returned.
388
		 *
389
		 * @since 2.8.0
390
		 *
391
		 * @param array|WP_Error $response HTTP response or WP_Error object.
392
		 * @param string         $context  Context under which the hook is fired.
393
		 * @param string         $class    HTTP transport used.
394
		 * @param array          $args     HTTP request arguments.
395
		 * @param string         $url      The request URL.
396
		 */
397
		do_action( 'http_api_debug', $response, 'response', 'Requests', $r, $url );
398
		if ( is_wp_error( $response ) ) {
399
			return $response;
400
		}
401
402
		if ( ! $r['blocking'] ) {
403
			return array(
404
				'headers' => array(),
405
				'body' => '',
406
				'response' => array(
407
					'code' => false,
408
					'message' => false,
409
				),
410
				'cookies' => array(),
411
				'http_response' => null,
412
			);
413
		}
414
415
		/**
416
		 * Filters the HTTP API response immediately before the response is returned.
417
		 *
418
		 * @since 2.9.0
419
		 *
420
		 * @param array  $response HTTP response.
421
		 * @param array  $r        HTTP request arguments.
422
		 * @param string $url      The request URL.
423
		 */
424
		return apply_filters( 'http_response', $response, $r, $url );
425
	}
426
427
	/**
428
	 * Normalizes cookies for using in Requests.
429
	 *
430
	 * @since 4.6.0
431
	 * @access public
432
	 * @static
433
	 *
434
	 * @param array $cookies List of cookies to send with the request.
435
	 * @return Requests_Cookie_Jar Cookie holder object.
436
	 */
437
	public static function normalize_cookies( $cookies ) {
438
		$cookie_jar = new Requests_Cookie_Jar();
439
440
		foreach ( $cookies as $name => $value ) {
441
			if ( $value instanceof WP_Http_Cookie ) {
442
				$cookie_jar[ $value->name ] = new Requests_Cookie( $value->name, $value->value, $value->get_attributes() );
443
			} elseif ( is_scalar( $value ) ) {
444
				$cookie_jar[ $name ] = new Requests_Cookie( $name, $value );
445
			}
446
		}
447
448
		return $cookie_jar;
449
	}
450
451
	/**
452
	 * Match redirect behaviour to browser handling.
453
	 *
454
	 * Changes 302 redirects from POST to GET to match browser handling. Per
455
	 * RFC 7231, user agents can deviate from the strict reading of the
456
	 * specification for compatibility purposes.
457
	 *
458
	 * @since 4.6.0
459
	 * @access public
460
	 * @static
461
	 *
462
	 * @param string            $location URL to redirect to.
463
	 * @param array             $headers  Headers for the redirect.
464
	 * @param array             $options  Redirect request options.
465
	 * @param Requests_Response $original Response object.
466
	 */
467
	public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) {
0 ignored issues
show
Unused Code introduced by
The parameter $location is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $headers is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
468
		// Browser compat
469
		if ( $original->status_code === 302 ) {
470
			$options['type'] = Requests::GET;
471
		}
472
	}
473
474
	/**
475
	 * Validate redirected URLs.
476
	 *
477
	 * @since 4.7.5
478
	 *
479
	 * @throws Requests_Exception On unsuccessful URL validation
480
	 * @param string $location URL to redirect to.
481
	 */
482
	public static function validate_redirects( $location ) {
483
		if ( ! wp_http_validate_url( $location ) ) {
484
			throw new Requests_Exception( __('A valid URL was not provided.'), 'wp_http.redirect_failed_validation' );
485
		}
486
	}
487
488
	/**
489
	 * Tests which transports are capable of supporting the request.
490
	 *
491
	 * @since 3.2.0
492
	 * @access public
493
	 *
494
	 * @param array $args Request arguments
495
	 * @param string $url URL to Request
496
	 *
497
	 * @return string|false Class name for the first transport that claims to support the request. False if no transport claims to support the request.
498
	 */
499
	public function _get_first_available_transport( $args, $url = null ) {
500
		$transports = array( 'curl', 'streams' );
501
502
		/**
503
		 * Filters which HTTP transports are available and in what order.
504
		 *
505
		 * @since 3.7.0
506
		 *
507
		 * @param array  $transports Array of HTTP transports to check. Default array contains
508
		 *                           'curl', and 'streams', in that order.
509
		 * @param array  $args       HTTP request arguments.
510
		 * @param string $url        The URL to request.
511
		 */
512
		$request_order = apply_filters( 'http_api_transports', $transports, $args, $url );
513
514
		// Loop over each transport on each HTTP request looking for one which will serve this request's needs.
515
		foreach ( $request_order as $transport ) {
516
			if ( in_array( $transport, $transports ) ) {
517
				$transport = ucfirst( $transport );
518
			}
519
			$class = 'WP_Http_' . $transport;
520
521
			// Check to see if this transport is a possibility, calls the transport statically.
522
			if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
523
				continue;
524
525
			return $class;
526
		}
527
528
		return false;
529
	}
530
531
	/**
532
	 * Dispatches a HTTP request to a supporting transport.
533
	 *
534
	 * Tests each transport in order to find a transport which matches the request arguments.
535
	 * Also caches the transport instance to be used later.
536
	 *
537
	 * The order for requests is cURL, and then PHP Streams.
538
	 *
539
	 * @since 3.2.0
540
	 *
541
	 * @static
542
	 * @access private
543
	 *
544
	 * @param string $url URL to Request
545
	 * @param array $args Request arguments
546
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
547
	 */
548
	private function _dispatch_request( $url, $args ) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
549
		static $transports = array();
550
551
		$class = $this->_get_first_available_transport( $args, $url );
552
		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...
553
			return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
554
555
		// Transport claims to support request, instantiate it and give it a whirl.
556
		if ( empty( $transports[$class] ) )
557
			$transports[$class] = new $class;
558
559
		$response = $transports[$class]->request( $url, $args );
560
561
		/** This action is documented in wp-includes/class-http.php */
562
		do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
563
564
		if ( is_wp_error( $response ) )
565
			return $response;
566
567
		/**
568
		 * Filters the HTTP API response immediately before the response is returned.
569
		 *
570
		 * @since 2.9.0
571
		 *
572
		 * @param array  $response HTTP response.
573
		 * @param array  $args     HTTP request arguments.
574
		 * @param string $url      The request URL.
575
		 */
576
		return apply_filters( 'http_response', $response, $args, $url );
577
	}
578
579
	/**
580
	 * Uses the POST HTTP method.
581
	 *
582
	 * Used for sending data that is expected to be in the body.
583
	 *
584
	 * @access public
585
	 * @since 2.7.0
586
	 *
587
	 * @param string       $url  The request URL.
588
	 * @param string|array $args Optional. Override the defaults.
589
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
590
	 */
591
	public function post($url, $args = array()) {
592
		$defaults = array('method' => 'POST');
593
		$r = wp_parse_args( $args, $defaults );
594
		return $this->request($url, $r);
595
	}
596
597
	/**
598
	 * Uses the GET HTTP method.
599
	 *
600
	 * Used for sending data that is expected to be in the body.
601
	 *
602
	 * @access public
603
	 * @since 2.7.0
604
	 *
605
	 * @param string $url The request URL.
606
	 * @param string|array $args Optional. Override the defaults.
607
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
608
	 */
609
	public function get($url, $args = array()) {
610
		$defaults = array('method' => 'GET');
611
		$r = wp_parse_args( $args, $defaults );
612
		return $this->request($url, $r);
613
	}
614
615
	/**
616
	 * Uses the HEAD HTTP method.
617
	 *
618
	 * Used for sending data that is expected to be in the body.
619
	 *
620
	 * @access public
621
	 * @since 2.7.0
622
	 *
623
	 * @param string $url The request URL.
624
	 * @param string|array $args Optional. Override the defaults.
625
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
626
	 */
627
	public function head($url, $args = array()) {
628
		$defaults = array('method' => 'HEAD');
629
		$r = wp_parse_args( $args, $defaults );
630
		return $this->request($url, $r);
631
	}
632
633
	/**
634
	 * Parses the responses and splits the parts into headers and body.
635
	 *
636
	 * @access public
637
	 * @static
638
	 * @since 2.7.0
639
	 *
640
	 * @param string $strResponse The full response string
641
	 * @return array Array with 'headers' and 'body' keys.
642
	 */
643
	public static function processResponse($strResponse) {
644
		$res = explode("\r\n\r\n", $strResponse, 2);
645
646
		return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
647
	}
648
649
	/**
650
	 * Transform header string into an array.
651
	 *
652
	 * If an array is given then it is assumed to be raw header data with numeric keys with the
653
	 * headers as the values. No headers must be passed that were already processed.
654
	 *
655
	 * @access public
656
	 * @static
657
	 * @since 2.7.0
658
	 *
659
	 * @param string|array $headers
660
	 * @param string $url The URL that was requested
661
	 * @return array Processed string headers. If duplicate headers are encountered,
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
662
	 * 					Then a numbered array is returned as the value of that header-key.
663
	 */
664
	public static function processHeaders( $headers, $url = '' ) {
665
		// Split headers, one per array element.
666
		if ( is_string($headers) ) {
667
			// Tolerate line terminator: CRLF = LF (RFC 2616 19.3).
668
			$headers = str_replace("\r\n", "\n", $headers);
669
			/*
670
			 * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>,
671
			 * <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2).
672
			 */
673
			$headers = preg_replace('/\n[ \t]/', ' ', $headers);
674
			// Create the headers array.
675
			$headers = explode("\n", $headers);
676
		}
677
678
		$response = array('code' => 0, 'message' => '');
679
680
		/*
681
		 * If a redirection has taken place, The headers for each page request may have been passed.
682
		 * In this case, determine the final HTTP header and parse from there.
683
		 */
684
		for ( $i = count($headers)-1; $i >= 0; $i-- ) {
685
			if ( !empty($headers[$i]) && false === strpos($headers[$i], ':') ) {
686
				$headers = array_splice($headers, $i);
687
				break;
688
			}
689
		}
690
691
		$cookies = array();
692
		$newheaders = array();
693
		foreach ( (array) $headers as $tempheader ) {
694
			if ( empty($tempheader) )
695
				continue;
696
697
			if ( false === strpos($tempheader, ':') ) {
698
				$stack = explode(' ', $tempheader, 3);
699
				$stack[] = '';
700
				list( , $response['code'], $response['message']) = $stack;
701
				continue;
702
			}
703
704
			list($key, $value) = explode(':', $tempheader, 2);
705
706
			$key = strtolower( $key );
707
			$value = trim( $value );
708
709
			if ( isset( $newheaders[ $key ] ) ) {
710
				if ( ! is_array( $newheaders[ $key ] ) )
711
					$newheaders[$key] = array( $newheaders[ $key ] );
712
				$newheaders[ $key ][] = $value;
713
			} else {
714
				$newheaders[ $key ] = $value;
715
			}
716
			if ( 'set-cookie' == $key )
717
				$cookies[] = new WP_Http_Cookie( $value, $url );
718
		}
719
720
		// Cast the Response Code to an int
721
		$response['code'] = intval( $response['code'] );
722
723
		return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
724
	}
725
726
	/**
727
	 * Takes the arguments for a ::request() and checks for the cookie array.
728
	 *
729
	 * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances,
730
	 * which are each parsed into strings and added to the Cookie: header (within the arguments array).
731
	 * Edits the array by reference.
732
	 *
733
	 * @access public
734
	 * @version 2.8.0
735
	 * @static
736
	 *
737
	 * @param array $r Full array of args passed into ::request()
738
	 */
739
	public static function buildCookieHeader( &$r ) {
740
		if ( ! empty($r['cookies']) ) {
741
			// Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances.
742
			foreach ( $r['cookies'] as $name => $value ) {
743
				if ( ! is_object( $value ) )
744
					$r['cookies'][ $name ] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) );
745
			}
746
747
			$cookies_header = '';
748
			foreach ( (array) $r['cookies'] as $cookie ) {
749
				$cookies_header .= $cookie->getHeaderValue() . '; ';
750
			}
751
752
			$cookies_header = substr( $cookies_header, 0, -2 );
753
			$r['headers']['cookie'] = $cookies_header;
754
		}
755
	}
756
757
	/**
758
	 * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
759
	 *
760
	 * Based off the HTTP http_encoding_dechunk function.
761
	 *
762
	 * @link https://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
763
	 *
764
	 * @access public
765
	 * @since 2.7.0
766
	 * @static
767
	 *
768
	 * @param string $body Body content
769
	 * @return string Chunked decoded body on success or raw body on failure.
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
770
	 */
771
	public static function chunkTransferDecode( $body ) {
772
		// The body is not chunked encoded or is malformed.
773
		if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) )
774
			return $body;
775
776
		$parsed_body = '';
777
778
		// We'll be altering $body, so need a backup in case of error.
779
		$body_original = $body;
780
781
		while ( true ) {
782
			$has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match );
783
			if ( ! $has_chunk || empty( $match[1] ) )
784
				return $body_original;
785
786
			$length = hexdec( $match[1] );
787
			$chunk_length = strlen( $match[0] );
788
789
			// Parse out the chunk of data.
790
			$parsed_body .= substr( $body, $chunk_length, $length );
791
792
			// Remove the chunk from the raw data.
793
			$body = substr( $body, $length + $chunk_length );
794
795
			// End of the document.
796
			if ( '0' === trim( $body ) )
797
				return $parsed_body;
798
		}
799
	}
800
801
	/**
802
	 * Block requests through the proxy.
803
	 *
804
	 * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
805
	 * prevent plugins from working and core functionality, if you don't include api.wordpress.org.
806
	 *
807
	 * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php
808
	 * file and this will only allow localhost and your site to make requests. The constant
809
	 * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the
810
	 * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains
811
	 * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted.
812
	 *
813
	 * @since 2.8.0
814
	 * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
815
	 * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
816
	 *
817
	 * @staticvar array|null $accessible_hosts
818
	 * @staticvar array      $wildcard_regex
819
	 *
820
	 * @param string $uri URI of url.
821
	 * @return bool True to block, false to allow.
822
	 */
823
	public function block_request($uri) {
824
		// We don't need to block requests, because nothing is blocked.
825
		if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL )
826
			return false;
827
828
		$check = parse_url($uri);
829
		if ( ! $check )
830
			return true;
831
832
		$home = parse_url( get_option('siteurl') );
833
834
		// Don't block requests back to ourselves by default.
835 View Code Duplication
		if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) {
836
			/**
837
			 * Filters whether to block local requests through the proxy.
838
			 *
839
			 * @since 2.8.0
840
			 *
841
			 * @param bool $block Whether to block local requests through proxy.
842
			 *                    Default false.
843
			 */
844
			return apply_filters( 'block_local_requests', false );
845
		}
846
847
		if ( !defined('WP_ACCESSIBLE_HOSTS') )
848
			return true;
849
850
		static $accessible_hosts = null;
851
		static $wildcard_regex = array();
852 View Code Duplication
		if ( null === $accessible_hosts ) {
853
			$accessible_hosts = preg_split('|,\s*|', WP_ACCESSIBLE_HOSTS);
854
855
			if ( false !== strpos(WP_ACCESSIBLE_HOSTS, '*') ) {
856
				$wildcard_regex = array();
857
				foreach ( $accessible_hosts as $host )
858
					$wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
859
				$wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
860
			}
861
		}
862
863 View Code Duplication
		if ( !empty($wildcard_regex) )
864
			return !preg_match($wildcard_regex, $check['host']);
865
		else
866
			return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If it's in the array, then we can't access it.
867
868
	}
869
870
	/**
871
	 * Used as a wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7.
872
	 *
873
	 * @access protected
874
	 * @deprecated 4.4.0 Use wp_parse_url()
875
	 * @see wp_parse_url()
876
	 *
877
	 * @param string $url The URL to parse.
878
	 * @return bool|array False on failure; Array of URL components on success;
879
	 *                    See parse_url()'s return values.
880
	 */
881
	protected static function parse_url( $url ) {
882
		_deprecated_function( __METHOD__, '4.4.0', 'wp_parse_url()' );
883
		return wp_parse_url( $url );
884
	}
885
886
	/**
887
	 * Converts a relative URL to an absolute URL relative to a given URL.
888
	 *
889
	 * If an Absolute URL is provided, no processing of that URL is done.
890
	 *
891
	 * @since 3.4.0
892
	 *
893
	 * @static
894
	 * @access public
895
	 *
896
	 * @param string $maybe_relative_path The URL which might be relative
897
	 * @param string $url                 The URL which $maybe_relative_path is relative to
898
	 * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned.
899
	 */
900
	public static function make_absolute_url( $maybe_relative_path, $url ) {
901
		if ( empty( $url ) )
902
			return $maybe_relative_path;
903
904
		if ( ! $url_parts = wp_parse_url( $url ) ) {
905
			return $maybe_relative_path;
906
		}
907
908
		if ( ! $relative_url_parts = wp_parse_url( $maybe_relative_path ) ) {
909
			return $maybe_relative_path;
910
		}
911
912
		// Check for a scheme on the 'relative' url
913
		if ( ! empty( $relative_url_parts['scheme'] ) ) {
914
			return $maybe_relative_path;
915
		}
916
917
		$absolute_path = $url_parts['scheme'] . '://';
918
919
		// 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
920
		if ( isset( $relative_url_parts['host'] ) ) {
921
			$absolute_path .= $relative_url_parts['host'];
922
			if ( isset( $relative_url_parts['port'] ) )
923
				$absolute_path .= ':' . $relative_url_parts['port'];
924
		} else {
925
			$absolute_path .= $url_parts['host'];
926
			if ( isset( $url_parts['port'] ) )
927
				$absolute_path .= ':' . $url_parts['port'];
928
		}
929
930
		// Start off with the Absolute URL path.
931
		$path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/';
932
933
		// If it's a root-relative path, then great.
934
		if ( ! empty( $relative_url_parts['path'] ) && '/' == $relative_url_parts['path'][0] ) {
935
			$path = $relative_url_parts['path'];
936
937
		// Else it's a relative path.
938
		} elseif ( ! empty( $relative_url_parts['path'] ) ) {
939
			// Strip off any file components from the absolute path.
940
			$path = substr( $path, 0, strrpos( $path, '/' ) + 1 );
941
942
			// Build the new path.
943
			$path .= $relative_url_parts['path'];
944
945
			// Strip all /path/../ out of the path.
946
			while ( strpos( $path, '../' ) > 1 ) {
947
				$path = preg_replace( '![^/]+/\.\./!', '', $path );
948
			}
949
950
			// Strip any final leading ../ from the path.
951
			$path = preg_replace( '!^/(\.\./)+!', '', $path );
952
		}
953
954
		// Add the Query string.
955
		if ( ! empty( $relative_url_parts['query'] ) )
956
			$path .= '?' . $relative_url_parts['query'];
957
958
		return $absolute_path . '/' . ltrim( $path, '/' );
959
	}
960
961
	/**
962
	 * Handles HTTP Redirects and follows them if appropriate.
963
	 *
964
	 * @since 3.7.0
965
	 *
966
	 * @static
967
	 *
968
	 * @param string $url The URL which was requested.
969
	 * @param array $args The Arguments which were used to make the request.
970
	 * @param array $response The Response of the HTTP request.
971
	 * @return false|object False if no redirect is present, a WP_HTTP or WP_Error result otherwise.
0 ignored issues
show
Documentation introduced by
Should the return type not be false|WP_Error|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
972
	 */
973
	public static function handle_redirects( $url, $args, $response ) {
974
		// If no redirects are present, or, redirects were not requested, perform no action.
975
		if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] )
976
			return false;
977
978
		// Only perform redirections on redirection http codes.
979
		if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 )
980
			return false;
981
982
		// Don't redirect if we've run out of redirects.
983
		if ( $args['redirection']-- <= 0 )
984
			return new WP_Error( 'http_request_failed', __('Too many redirects.') );
985
986
		$redirect_location = $response['headers']['location'];
987
988
		// If there were multiple Location headers, use the last header specified.
989
		if ( is_array( $redirect_location ) )
990
			$redirect_location = array_pop( $redirect_location );
991
992
		$redirect_location = WP_Http::make_absolute_url( $redirect_location, $url );
993
994
		// POST requests should not POST to a redirected location.
995
		if ( 'POST' == $args['method'] ) {
996
			if ( in_array( $response['response']['code'], array( 302, 303 ) ) )
997
				$args['method'] = 'GET';
998
		}
999
1000
		// Include valid cookies in the redirect process.
1001
		if ( ! empty( $response['cookies'] ) ) {
1002
			foreach ( $response['cookies'] as $cookie ) {
1003
				if ( $cookie->test( $redirect_location ) )
1004
					$args['cookies'][] = $cookie;
1005
			}
1006
		}
1007
1008
		return wp_remote_request( $redirect_location, $args );
1009
	}
1010
1011
	/**
1012
	 * Determines if a specified string represents an IP address or not.
1013
	 *
1014
	 * This function also detects the type of the IP address, returning either
1015
	 * '4' or '6' to represent a IPv4 and IPv6 address respectively.
1016
	 * This does not verify if the IP is a valid IP, only that it appears to be
1017
	 * an IP address.
1018
	 *
1019
	 * @link http://home.deds.nl/~aeron/regex/ for IPv6 regex
1020
	 *
1021
	 * @since 3.7.0
1022
	 * @static
1023
	 *
1024
	 * @param string $maybe_ip A suspected IP address
1025
	 * @return integer|bool Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1026
	 */
1027
	public static function is_ip_address( $maybe_ip ) {
1028
		if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) )
1029
			return 4;
1030
1031
		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, ' []' ) ) )
1032
			return 6;
1033
1034
		return false;
1035
	}
1036
1037
}
1038