WordPress 4.3
[autoinstalls/wordpress.git] / wp-includes / http.php
1 <?php
2 /**
3  * Simple and uniform HTTP request API.
4  *
5  * Will eventually replace and standardize the WordPress HTTP requests made.
6  *
7  * @link https://core.trac.wordpress.org/ticket/4779 HTTP API Proposal
8  *
9  * @package WordPress
10  * @subpackage HTTP
11  * @since 2.7.0
12  */
13
14 /**
15  * Returns the initialized WP_Http Object
16  *
17  * @since 2.7.0
18  * @access private
19  *
20  * @staticvar WP_Http $http
21  *
22  * @return WP_Http HTTP Transport object.
23  */
24 function _wp_http_get_object() {
25         static $http = null;
26
27         if ( is_null( $http ) ) {
28                 $http = new WP_Http();
29         }
30         return $http;
31 }
32
33 /**
34  * Retrieve the raw response from a safe HTTP request.
35  *
36  * This function is ideal when the HTTP request is being made to an arbitrary
37  * URL. The URL is validated to avoid redirection and request forgery attacks.
38  *
39  * @since 3.6.0
40  *
41  * @see wp_remote_request() For more information on the response array format.
42  * @see WP_Http::request() For default arguments information.
43  *
44  * @param string $url  Site URL to retrieve.
45  * @param array  $args Optional. Request arguments. Default empty array.
46  * @return WP_Error|array The response or WP_Error on failure.
47  */
48 function wp_safe_remote_request( $url, $args = array() ) {
49         $args['reject_unsafe_urls'] = true;
50         $http = _wp_http_get_object();
51         return $http->request( $url, $args );
52 }
53
54 /**
55  * Retrieve the raw response from a safe HTTP request using the GET method.
56  *
57  * This function is ideal when the HTTP request is being made to an arbitrary
58  * URL. The URL is validated to avoid redirection and request forgery attacks.
59  *
60  * @since 3.6.0
61  *
62  * @see wp_remote_request() For more information on the response array format.
63  * @see WP_Http::request() For default arguments information.
64  *
65  * @param string $url  Site URL to retrieve.
66  * @param array  $args Optional. Request arguments. Default empty array.
67  * @return WP_Error|array The response or WP_Error on failure.
68  */
69 function wp_safe_remote_get( $url, $args = array() ) {
70         $args['reject_unsafe_urls'] = true;
71         $http = _wp_http_get_object();
72         return $http->get( $url, $args );
73 }
74
75 /**
76  * Retrieve the raw response from a safe HTTP request using the POST method.
77  *
78  * This function is ideal when the HTTP request is being made to an arbitrary
79  * URL. The URL is validated to avoid redirection and request forgery attacks.
80  *
81  * @since 3.6.0
82  *
83  * @see wp_remote_request() For more information on the response array format.
84  * @see WP_Http::request() For default arguments information.
85  *
86  * @param string $url  Site URL to retrieve.
87  * @param array  $args Optional. Request arguments. Default empty array.
88  * @return WP_Error|array The response or WP_Error on failure.
89  */
90 function wp_safe_remote_post( $url, $args = array() ) {
91         $args['reject_unsafe_urls'] = true;
92         $http = _wp_http_get_object();
93         return $http->post( $url, $args );
94 }
95
96 /**
97  * Retrieve the raw response from a safe HTTP request using the HEAD method.
98  *
99  * This function is ideal when the HTTP request is being made to an arbitrary
100  * URL. The URL is validated to avoid redirection and request forgery attacks.
101  *
102  * @since 3.6.0
103  *
104  * @see wp_remote_request() For more information on the response array format.
105  * @see WP_Http::request() For default arguments information.
106  *
107  * @param string $url Site URL to retrieve.
108  * @param array $args Optional. Request arguments. Default empty array.
109  * @return WP_Error|array The response or WP_Error on failure.
110  */
111 function wp_safe_remote_head( $url, $args = array() ) {
112         $args['reject_unsafe_urls'] = true;
113         $http = _wp_http_get_object();
114         return $http->head( $url, $args );
115 }
116
117 /**
118  * Retrieve the raw response from the HTTP request.
119  *
120  * The array structure is a little complex:
121  *
122  *     $res = array(
123  *         'headers'  => array(),
124  *         'response' => array(
125  *             'code'    => int,
126  *             'message' => string
127  *         )
128  *     );
129  *
130  * All of the headers in $res['headers'] are with the name as the key and the
131  * value as the value. So to get the User-Agent, you would do the following.
132  *
133  *     $user_agent = $res['headers']['user-agent'];
134  *
135  * The body is the raw response content and can be retrieved from $res['body'].
136  *
137  * This function is called first to make the request and there are other API
138  * functions to abstract out the above convoluted setup.
139  *
140  * Request method defaults for helper functions:
141  *  - Default 'GET'  for wp_remote_get()
142  *  - Default 'POST' for wp_remote_post()
143  *  - Default 'HEAD' for wp_remote_head()
144  *
145  * @since 2.7.0
146  *
147  * @see WP_Http::request() For additional information on default arguments.
148  *
149  * @param string $url  Site URL to retrieve.
150  * @param array  $args Optional. Request arguments. Default empty array.
151  * @return WP_Error|array The response or WP_Error on failure.
152  */
153 function wp_remote_request($url, $args = array()) {
154         $http = _wp_http_get_object();
155         return $http->request( $url, $args );
156 }
157
158 /**
159  * Retrieve the raw response from the HTTP request using the GET method.
160  *
161  * @since 2.7.0
162  *
163  * @see wp_remote_request() For more information on the response array format.
164  * @see WP_Http::request() For default arguments information.
165  *
166  * @param string $url  Site URL to retrieve.
167  * @param array  $args Optional. Request arguments. Default empty array.
168  * @return WP_Error|array The response or WP_Error on failure.
169  */
170 function wp_remote_get($url, $args = array()) {
171         $http = _wp_http_get_object();
172         return $http->get( $url, $args );
173 }
174
175 /**
176  * Retrieve the raw response from the HTTP request using the POST method.
177  *
178  * @since 2.7.0
179  *
180  * @see wp_remote_request() For more information on the response array format.
181  * @see WP_Http::request() For default arguments information.
182  *
183  * @param string $url  Site URL to retrieve.
184  * @param array  $args Optional. Request arguments. Default empty array.
185  * @return WP_Error|array The response or WP_Error on failure.
186  */
187 function wp_remote_post($url, $args = array()) {
188         $http = _wp_http_get_object();
189         return $http->post( $url, $args );
190 }
191
192 /**
193  * Retrieve the raw response from the HTTP request using the HEAD method.
194  *
195  * @since 2.7.0
196  *
197  * @see wp_remote_request() For more information on the response array format.
198  * @see WP_Http::request() For default arguments information.
199  *
200  * @param string $url  Site URL to retrieve.
201  * @param array  $args Optional. Request arguments. Default empty array.
202  * @return WP_Error|array The response or WP_Error on failure.
203  */
204 function wp_remote_head($url, $args = array()) {
205         $http = _wp_http_get_object();
206         return $http->head( $url, $args );
207 }
208
209 /**
210  * Retrieve only the headers from the raw response.
211  *
212  * @since 2.7.0
213  *
214  * @param array $response HTTP response.
215  * @return array The headers of the response. Empty array if incorrect parameter given.
216  */
217 function wp_remote_retrieve_headers( $response ) {
218         if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers']))
219                 return array();
220
221         return $response['headers'];
222 }
223
224 /**
225  * Retrieve a single header by name from the raw response.
226  *
227  * @since 2.7.0
228  *
229  * @param array  $response
230  * @param string $header Header name to retrieve value from.
231  * @return string The header value. Empty string on if incorrect parameter given, or if the header doesn't exist.
232  */
233 function wp_remote_retrieve_header( $response, $header ) {
234         if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers']))
235                 return '';
236
237         if ( array_key_exists($header, $response['headers']) )
238                 return $response['headers'][$header];
239
240         return '';
241 }
242
243 /**
244  * Retrieve only the response code from the raw response.
245  *
246  * Will return an empty array if incorrect parameter value is given.
247  *
248  * @since 2.7.0
249  *
250  * @param array $response HTTP response.
251  * @return int|string The response code as an integer. Empty string on incorrect parameter given.
252  */
253 function wp_remote_retrieve_response_code( $response ) {
254         if ( is_wp_error($response) || ! isset($response['response']) || ! is_array($response['response']))
255                 return '';
256
257         return $response['response']['code'];
258 }
259
260 /**
261  * Retrieve only the response message from the raw response.
262  *
263  * Will return an empty array if incorrect parameter value is given.
264  *
265  * @since 2.7.0
266  *
267  * @param array $response HTTP response.
268  * @return string The response message. Empty string on incorrect parameter given.
269  */
270 function wp_remote_retrieve_response_message( $response ) {
271         if ( is_wp_error($response) || ! isset($response['response']) || ! is_array($response['response']))
272                 return '';
273
274         return $response['response']['message'];
275 }
276
277 /**
278  * Retrieve only the body from the raw response.
279  *
280  * @since 2.7.0
281  *
282  * @param array $response HTTP response.
283  * @return string The body of the response. Empty string if no body or incorrect parameter given.
284  */
285 function wp_remote_retrieve_body( $response ) {
286         if ( is_wp_error($response) || ! isset($response['body']) )
287                 return '';
288
289         return $response['body'];
290 }
291
292 /**
293  * Determines if there is an HTTP Transport that can process this request.
294  *
295  * @since 3.2.0
296  *
297  * @param array  $capabilities Array of capabilities to test or a wp_remote_request() $args array.
298  * @param string $url          Optional. If given, will check if the URL requires SSL and adds
299  *                             that requirement to the capabilities array.
300  *
301  * @return bool
302  */
303 function wp_http_supports( $capabilities = array(), $url = null ) {
304         $http = _wp_http_get_object();
305
306         $capabilities = wp_parse_args( $capabilities );
307
308         $count = count( $capabilities );
309
310         // If we have a numeric $capabilities array, spoof a wp_remote_request() associative $args array
311         if ( $count && count( array_filter( array_keys( $capabilities ), 'is_numeric' ) ) == $count ) {
312                 $capabilities = array_combine( array_values( $capabilities ), array_fill( 0, $count, true ) );
313         }
314
315         if ( $url && !isset( $capabilities['ssl'] ) ) {
316                 $scheme = parse_url( $url, PHP_URL_SCHEME );
317                 if ( 'https' == $scheme || 'ssl' == $scheme ) {
318                         $capabilities['ssl'] = true;
319                 }
320         }
321
322         return (bool) $http->_get_first_available_transport( $capabilities );
323 }
324
325 /**
326  * Get the HTTP Origin of the current request.
327  *
328  * @since 3.4.0
329  *
330  * @return string URL of the origin. Empty string if no origin.
331  */
332 function get_http_origin() {
333         $origin = '';
334         if ( ! empty ( $_SERVER[ 'HTTP_ORIGIN' ] ) )
335                 $origin = $_SERVER[ 'HTTP_ORIGIN' ];
336
337         /**
338          * Change the origin of an HTTP request.
339          *
340          * @since 3.4.0
341          *
342          * @param string $origin The original origin for the request.
343          */
344         return apply_filters( 'http_origin', $origin );
345 }
346
347 /**
348  * Retrieve list of allowed HTTP origins.
349  *
350  * @since 3.4.0
351  *
352  * @return array Array of origin URLs.
353  */
354 function get_allowed_http_origins() {
355         $admin_origin = parse_url( admin_url() );
356         $home_origin = parse_url( home_url() );
357
358         // @todo preserve port?
359         $allowed_origins = array_unique( array(
360                 'http://' . $admin_origin[ 'host' ],
361                 'https://' . $admin_origin[ 'host' ],
362                 'http://' . $home_origin[ 'host' ],
363                 'https://' . $home_origin[ 'host' ],
364         ) );
365
366         /**
367          * Change the origin types allowed for HTTP requests.
368          *
369          * @since 3.4.0
370          *
371          * @param array $allowed_origins {
372          *     Default allowed HTTP origins.
373          *     @type string Non-secure URL for admin origin.
374          *     @type string Secure URL for admin origin.
375          *     @type string Non-secure URL for home origin.
376          *     @type string Secure URL for home origin.
377          * }
378          */
379         return apply_filters( 'allowed_http_origins' , $allowed_origins );
380 }
381
382 /**
383  * Determines if the HTTP origin is an authorized one.
384  *
385  * @since 3.4.0
386  *
387  * @param null|string $origin Origin URL. If not provided, the value of get_http_origin() is used.
388  * @return string True if the origin is allowed. False otherwise.
389  */
390 function is_allowed_http_origin( $origin = null ) {
391         $origin_arg = $origin;
392
393         if ( null === $origin )
394                 $origin = get_http_origin();
395
396         if ( $origin && ! in_array( $origin, get_allowed_http_origins() ) )
397                 $origin = '';
398
399         /**
400          * Change the allowed HTTP origin result.
401          *
402          * @since 3.4.0
403          *
404          * @param string $origin     Result of check for allowed origin.
405          * @param string $origin_arg Original origin string passed into is_allowed_http_origin function.
406          */
407         return apply_filters( 'allowed_http_origin', $origin, $origin_arg );
408 }
409
410 /**
411  * Send Access-Control-Allow-Origin and related headers if the current request
412  * is from an allowed origin.
413  *
414  * If the request is an OPTIONS request, the script exits with either access
415  * control headers sent, or a 403 response if the origin is not allowed. For
416  * other request methods, you will receive a return value.
417  *
418  * @since 3.4.0
419  *
420  * @return string|false Returns the origin URL if headers are sent. Returns false
421  *                      if headers are not sent.
422  */
423 function send_origin_headers() {
424         $origin = get_http_origin();
425
426         if ( is_allowed_http_origin( $origin ) ) {
427                 @header( 'Access-Control-Allow-Origin: ' .  $origin );
428                 @header( 'Access-Control-Allow-Credentials: true' );
429                 if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] )
430                         exit;
431                 return $origin;
432         }
433
434         if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) {
435                 status_header( 403 );
436                 exit;
437         }
438
439         return false;
440 }
441
442 /**
443  * Validate a URL for safe use in the HTTP API.
444  *
445  * @since 3.5.2
446  *
447  * @param string $url
448  * @return false|string URL or false on failure.
449  */
450 function wp_http_validate_url( $url ) {
451         $original_url = $url;
452         $url = wp_kses_bad_protocol( $url, array( 'http', 'https' ) );
453         if ( ! $url || strtolower( $url ) !== strtolower( $original_url ) )
454                 return false;
455
456         $parsed_url = @parse_url( $url );
457         if ( ! $parsed_url || empty( $parsed_url['host'] ) )
458                 return false;
459
460         if ( isset( $parsed_url['user'] ) || isset( $parsed_url['pass'] ) )
461                 return false;
462
463         if ( false !== strpbrk( $parsed_url['host'], ':#?[]' ) )
464                 return false;
465
466         $parsed_home = @parse_url( get_option( 'home' ) );
467
468         $same_host = strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] );
469
470         if ( ! $same_host ) {
471                 $host = trim( $parsed_url['host'], '.' );
472                 if ( preg_match( '#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $host ) ) {
473                         $ip = $host;
474                 } else {
475                         $ip = gethostbyname( $host );
476                         if ( $ip === $host ) // Error condition for gethostbyname()
477                                 $ip = false;
478                 }
479                 if ( $ip ) {
480                         $parts = array_map( 'intval', explode( '.', $ip ) );
481                         if ( 127 === $parts[0] || 10 === $parts[0]
482                                 || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] )
483                                 || ( 192 === $parts[0] && 168 === $parts[1] )
484                         ) {
485                                 // If host appears local, reject unless specifically allowed.
486                                 /**
487                                  * Check if HTTP request is external or not.
488                                  *
489                                  * Allows to change and allow external requests for the HTTP request.
490                                  *
491                                  * @since 3.6.0
492                                  *
493                                  * @param bool   false Whether HTTP request is external or not.
494                                  * @param string $host IP of the requested host.
495                                  * @param string $url  URL of the requested host.
496                                  */
497                                 if ( ! apply_filters( 'http_request_host_is_external', false, $host, $url ) )
498                                         return false;
499                         }
500                 }
501         }
502
503         if ( empty( $parsed_url['port'] ) )
504                 return $url;
505
506         $port = $parsed_url['port'];
507         if ( 80 === $port || 443 === $port || 8080 === $port )
508                 return $url;
509
510         if ( $parsed_home && $same_host && isset( $parsed_home['port'] ) && $parsed_home['port'] === $port )
511                 return $url;
512
513         return false;
514 }
515
516 /**
517  * Whitelists allowed redirect hosts for safe HTTP requests as well.
518  *
519  * Attached to the http_request_host_is_external filter.
520  *
521  * @since 3.6.0
522  *
523  * @param bool   $is_external
524  * @param string $host
525  * @return bool
526  */
527 function allowed_http_request_hosts( $is_external, $host ) {
528         if ( ! $is_external && wp_validate_redirect( 'http://' . $host ) )
529                 $is_external = true;
530         return $is_external;
531 }
532
533 /**
534  * Whitelists any domain in a multisite installation for safe HTTP requests.
535  *
536  * Attached to the http_request_host_is_external filter.
537  *
538  * @since 3.6.0
539  *
540  * @global wpdb $wpdb
541  * @staticvar array $queried
542  *
543  * @param bool   $is_external
544  * @param string $host
545  * @return bool
546  */
547 function ms_allowed_http_request_hosts( $is_external, $host ) {
548         global $wpdb;
549         static $queried = array();
550         if ( $is_external )
551                 return $is_external;
552         if ( $host === get_current_site()->domain )
553                 return true;
554         if ( isset( $queried[ $host ] ) )
555                 return $queried[ $host ];
556         $queried[ $host ] = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT domain FROM $wpdb->blogs WHERE domain = %s LIMIT 1", $host ) );
557         return $queried[ $host ];
558 }