1<?php
2/**
3 * Core HTTP Request API
4 *
5 * Standardizes the HTTP requests for WordPress. Handles cookies, gzip encoding and decoding, chunk
6 * decoding, if HTTP 1.1 and various other difficult HTTP protocol implementations.
7 *
8 * @package WordPress
9 * @subpackage HTTP
10 */
11
12/**
13 * Returns the initialized WP_Http Object
14 *
15 * @since 2.7.0
16 * @access private
17 *
18 * @return WP_Http HTTP Transport object.
19 */
20function _wp_http_get_object() {
21 static $http = null;
22
23 if ( is_null( $http ) ) {
24 $http = new WP_Http();
25 }
26 return $http;
27}
28
29/**
30 * Retrieves the raw response from a safe HTTP request.
31 *
32 * This function is ideal when the HTTP request is being made to an arbitrary
33 * URL. The URL, and every URL it redirects to, are validated with wp_http_validate_url()
34 * to avoid Server Side Request Forgery attacks (SSRF).
35 *
36 * The only supported protocols are `http` and `https`.
37 *
38 * @since 3.6.0
39 *
40 * @see wp_remote_request() For more information on the response array format.
41 * @see WP_Http::request() For default arguments information.
42 * @see wp_http_validate_url() For more information about how the URL is validated.
43 *
44 * @link https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
45 *
46 * @param string $url URL to retrieve.
47 * @param array $args Optional. Request arguments. Default empty array.
48 * See WP_Http::request() for information on accepted arguments.
49 * @return array|WP_Error The response or WP_Error on failure.
50 * See WP_Http::request() for information on return value.
51 */
52function wp_safe_remote_request( $url, $args = array() ) {
53 $args['reject_unsafe_urls'] = true;
54 $http = _wp_http_get_object();
55 return $http->request( $url, $args );
56}
57
58/**
59 * Retrieves the raw response from a safe HTTP request using the GET method.
60 *
61 * This function is ideal when the HTTP request is being made to an arbitrary
62 * URL. The URL, and every URL it redirects to, are validated with wp_http_validate_url()
63 * to avoid Server Side Request Forgery attacks (SSRF).
64 *
65 * The only supported protocols are `http` and `https`.
66 *
67 * @since 3.6.0
68 *
69 * @see wp_remote_request() For more information on the response array format.
70 * @see WP_Http::request() For default arguments information.
71 * @see wp_http_validate_url() For more information about how the URL is validated.
72 *
73 * @link https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
74 *
75 * @param string $url URL to retrieve.
76 * @param array $args Optional. Request arguments. Default empty array.
77 * See WP_Http::request() for information on accepted arguments.
78 * @return array|WP_Error The response or WP_Error on failure.
79 * See WP_Http::request() for information on return value.
80 */
81function wp_safe_remote_get( $url, $args = array() ) {
82 $args['reject_unsafe_urls'] = true;
83 $http = _wp_http_get_object();
84 return $http->get( $url, $args );
85}
86
87/**
88 * Retrieves the raw response from a safe HTTP request using the POST method.
89 *
90 * This function is ideal when the HTTP request is being made to an arbitrary
91 * URL. The URL, and every URL it redirects to, are validated with wp_http_validate_url()
92 * to avoid Server Side Request Forgery attacks (SSRF).
93 *
94 * The only supported protocols are `http` and `https`.
95 *
96 * @since 3.6.0
97 *
98 * @see wp_remote_request() For more information on the response array format.
99 * @see WP_Http::request() For default arguments information.
100 * @see wp_http_validate_url() For more information about how the URL is validated.
101 *
102 * @link https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
103 *
104 * @param string $url URL to retrieve.
105 * @param array $args Optional. Request arguments. Default empty array.
106 * See WP_Http::request() for information on accepted arguments.
107 * @return array|WP_Error The response or WP_Error on failure.
108 * See WP_Http::request() for information on return value.
109 */
110function wp_safe_remote_post( $url, $args = array() ) {
111 $args['reject_unsafe_urls'] = true;
112 $http = _wp_http_get_object();
113 return $http->post( $url, $args );
114}
115
116/**
117 * Retrieves the raw response from a safe HTTP request using the HEAD method.
118 *
119 * This function is ideal when the HTTP request is being made to an arbitrary
120 * URL. The URL, and every URL it redirects to, are validated with wp_http_validate_url()
121 * to avoid Server Side Request Forgery attacks (SSRF).
122 *
123 * The only supported protocols are `http` and `https`.
124 *
125 * @since 3.6.0
126 *
127 * @see wp_remote_request() For more information on the response array format.
128 * @see WP_Http::request() For default arguments information.
129 * @see wp_http_validate_url() For more information about how the URL is validated.
130 *
131 * @link https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
132 *
133 * @param string $url URL to retrieve.
134 * @param array $args Optional. Request arguments. Default empty array.
135 * See WP_Http::request() for information on accepted arguments.
136 * @return array|WP_Error The response or WP_Error on failure.
137 * See WP_Http::request() for information on return value.
138 */
139function wp_safe_remote_head( $url, $args = array() ) {
140 $args['reject_unsafe_urls'] = true;
141 $http = _wp_http_get_object();
142 return $http->head( $url, $args );
143}
144
145/**
146 * Performs an HTTP request and returns its response.
147 *
148 * There are other API functions available which abstract away the HTTP method:
149 *
150 * - Default 'GET' for wp_remote_get()
151 * - Default 'POST' for wp_remote_post()
152 * - Default 'HEAD' for wp_remote_head()
153 *
154 * Important: If the URL is user-controlled, use `wp_safe_remote_request()` instead.
155 *
156 * @since 2.7.0
157 *
158 * @see WP_Http::request() For information on default arguments.
159 *
160 * @param string $url URL to retrieve.
161 * @param array $args Optional. Request arguments. Default empty array.
162 * See WP_Http::request() for information on accepted arguments.
163 * @return array|WP_Error The response array or a WP_Error on failure.
164 * See WP_Http::request() for information on return value.
165 */
166function wp_remote_request( $url, $args = array() ) {
167 $http = _wp_http_get_object();
168 return $http->request( $url, $args );
169}
170
171/**
172 * Performs an HTTP request using the GET method and returns its response.
173 *
174 * Important: If the URL is user-controlled, use `wp_safe_remote_get()` instead.
175 *
176 * @since 2.7.0
177 *
178 * @see wp_remote_request() For more information on the response array format.
179 * @see WP_Http::request() For default arguments information.
180 *
181 * @param string $url URL to retrieve.
182 * @param array $args Optional. Request arguments. Default empty array.
183 * See WP_Http::request() for information on accepted arguments.
184 * @return array|WP_Error The response or WP_Error on failure.
185 * See WP_Http::request() for information on return value.
186 */
187function wp_remote_get( $url, $args = array() ) {
188 $http = _wp_http_get_object();
189 return $http->get( $url, $args );
190}
191
192/**
193 * Performs an HTTP request using the POST method and returns its response.
194 *
195 * Important: If the URL is user-controlled, use `wp_safe_remote_post()` instead.
196 *
197 * @since 2.7.0
198 *
199 * @see wp_remote_request() For more information on the response array format.
200 * @see WP_Http::request() For default arguments information.
201 *
202 * @param string $url URL to retrieve.
203 * @param array $args Optional. Request arguments. Default empty array.
204 * See WP_Http::request() for information on accepted arguments.
205 * @return array|WP_Error The response or WP_Error on failure.
206 * See WP_Http::request() for information on return value.
207 */
208function wp_remote_post( $url, $args = array() ) {
209 $http = _wp_http_get_object();
210 return $http->post( $url, $args );
211}
212
213/**
214 * Performs an HTTP request using the HEAD method and returns its response.
215 *
216 * Important: If the URL is user-controlled, use `wp_safe_remote_head()` instead.
217 *
218 * @since 2.7.0
219 *
220 * @see wp_remote_request() For more information on the response array format.
221 * @see WP_Http::request() For default arguments information.
222 *
223 * @param string $url URL to retrieve.
224 * @param array $args Optional. Request arguments. Default empty array.
225 * See WP_Http::request() for information on accepted arguments.
226 * @return array|WP_Error The response or WP_Error on failure.
227 * See WP_Http::request() for information on return value.
228 */
229function wp_remote_head( $url, $args = array() ) {
230 $http = _wp_http_get_object();
231 return $http->head( $url, $args );
232}
233
234/**
235 * Retrieves only the headers from the raw response.
236 *
237 * @since 2.7.0
238 * @since 4.6.0 Return value changed from an array to an WpOrg\Requests\Utility\CaseInsensitiveDictionary instance.
239 *
240 * @see \WpOrg\Requests\Utility\CaseInsensitiveDictionary
241 *
242 * @param array|WP_Error $response HTTP response.
243 * @return \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array The headers of the response, or empty array
244 * if incorrect parameter given.
245 */
246function wp_remote_retrieve_headers( $response ) {
247 if ( is_wp_error( $response ) || ! isset( $response['headers'] ) ) {
248 return array();
249 }
250
251 return $response['headers'];
252}
253
254/**
255 * Retrieves a single header by name from the raw response.
256 *
257 * @since 2.7.0
258 *
259 * @param array|WP_Error $response HTTP response.
260 * @param string $header Header name to retrieve value from.
261 * @return array|string The header(s) value(s). Array if multiple headers with the same name are retrieved.
262 * Empty string if incorrect parameter given, or if the header doesn't exist.
263 */
264function wp_remote_retrieve_header( $response, $header ) {
265 if ( is_wp_error( $response ) || ! isset( $response['headers'] ) ) {
266 return '';
267 }
268
269 if ( isset( $response['headers'][ $header ] ) ) {
270 return $response['headers'][ $header ];
271 }
272
273 return '';
274}
275
276/**
277 * Retrieves only the response code from the raw response.
278 *
279 * Will return an empty string if incorrect parameter value is given.
280 *
281 * @since 2.7.0
282 *
283 * @param array|WP_Error $response HTTP response.
284 * @return int|string The response code as an integer. Empty string if incorrect parameter given.
285 */
286function wp_remote_retrieve_response_code( $response ) {
287 if ( is_wp_error( $response ) || ! isset( $response['response'] ) || ! is_array( $response['response'] ) ) {
288 return '';
289 }
290
291 return $response['response']['code'];
292}
293
294/**
295 * Retrieves only the response message from the raw response.
296 *
297 * Will return an empty string if incorrect parameter value is given.
298 *
299 * @since 2.7.0
300 *
301 * @param array|WP_Error $response HTTP response.
302 * @return string The response message. Empty string if incorrect parameter given.
303 */
304function wp_remote_retrieve_response_message( $response ) {
305 if ( is_wp_error( $response ) || ! isset( $response['response'] ) || ! is_array( $response['response'] ) ) {
306 return '';
307 }
308
309 return $response['response']['message'];
310}
311
312/**
313 * Retrieves only the body from the raw response.
314 *
315 * @since 2.7.0
316 *
317 * @param array|WP_Error $response HTTP response.
318 * @return string The body of the response. Empty string if no body or incorrect parameter given.
319 */
320function wp_remote_retrieve_body( $response ) {
321 if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
322 return '';
323 }
324
325 return $response['body'];
326}
327
328/**
329 * Retrieves only the cookies from the raw response.
330 *
331 * @since 4.4.0
332 *
333 * @param array|WP_Error $response HTTP response.
334 * @return WP_Http_Cookie[] An array of `WP_Http_Cookie` objects from the response.
335 * Empty array if there are none, or the response is a WP_Error.
336 */
337function wp_remote_retrieve_cookies( $response ) {
338 if ( is_wp_error( $response ) || empty( $response['cookies'] ) ) {
339 return array();
340 }
341
342 return $response['cookies'];
343}
344
345/**
346 * Retrieves a single cookie by name from the raw response.
347 *
348 * @since 4.4.0
349 *
350 * @param array|WP_Error $response HTTP response.
351 * @param string $name The name of the cookie to retrieve.
352 * @return WP_Http_Cookie|string The `WP_Http_Cookie` object, or empty string
353 * if the cookie is not present in the response.
354 */
355function wp_remote_retrieve_cookie( $response, $name ) {
356 $cookies = wp_remote_retrieve_cookies( $response );
357
358 if ( empty( $cookies ) ) {
359 return '';
360 }
361
362 foreach ( $cookies as $cookie ) {
363 if ( $cookie->name === $name ) {
364 return $cookie;
365 }
366 }
367
368 return '';
369}
370
371/**
372 * Retrieves a single cookie's value by name from the raw response.
373 *
374 * @since 4.4.0
375 *
376 * @param array|WP_Error $response HTTP response.
377 * @param string $name The name of the cookie to retrieve.
378 * @return string The value of the cookie, or empty string
379 * if the cookie is not present in the response.
380 */
381function wp_remote_retrieve_cookie_value( $response, $name ) {
382 $cookie = wp_remote_retrieve_cookie( $response, $name );
383
384 if ( ! ( $cookie instanceof WP_Http_Cookie ) ) {
385 return '';
386 }
387
388 return $cookie->value;
389}
390
391/**
392 * Determines if there is an HTTP Transport that can process this request.
393 *
394 * @since 3.2.0
395 *
396 * @param array $capabilities Array of capabilities to test or a wp_remote_request() $args array.
397 * @param string $url Optional. If given, will check if the URL requires SSL and adds
398 * that requirement to the capabilities array.
399 *
400 * @return bool
401 */
402function wp_http_supports( $capabilities = array(), $url = null ) {
403 $capabilities = wp_parse_args( $capabilities );
404
405 $count = count( $capabilities );
406
407 // If we have a numeric $capabilities array, spoof a wp_remote_request() associative $args array.
408 if ( $count && count( array_filter( array_keys( $capabilities ), 'is_numeric' ) ) === $count ) {
409 $capabilities = array_combine( array_values( $capabilities ), array_fill( 0, $count, true ) );
410 }
411
412 if ( $url && ! isset( $capabilities['ssl'] ) ) {
413 $scheme = parse_url( $url, PHP_URL_SCHEME );
414 if ( 'https' === $scheme || 'ssl' === $scheme ) {
415 $capabilities['ssl'] = true;
416 }
417 }
418
419 return WpOrg\Requests\Requests::has_capabilities( $capabilities );
420}
421
422/**
423 * Gets the HTTP Origin of the current request.
424 *
425 * @since 3.4.0
426 *
427 * @return string URL of the origin. Empty string if no origin.
428 */
429function get_http_origin() {
430 $origin = '';
431 if ( ! empty( $_SERVER['HTTP_ORIGIN'] ) ) {
432 $origin = $_SERVER['HTTP_ORIGIN'];
433 }
434
435 /**
436 * Changes the origin of an HTTP request.
437 *
438 * @since 3.4.0
439 *
440 * @param string $origin The HTTP origin for the request.
441 */
442 return apply_filters( 'http_origin', $origin );
443}
444
445/**
446 * Retrieves list of allowed HTTP origins.
447 *
448 * @since 3.4.0
449 *
450 * @return string[] Array of origin URLs.
451 */
452function get_allowed_http_origins() {
453 $admin_origin = parse_url( admin_url() );
454 $home_origin = parse_url( home_url() );
455
456 // @todo Preserve port?
457 $allowed_origins = array_unique(
458 array(
459 'http://' . $admin_origin['host'],
460 'https://' . $admin_origin['host'],
461 'http://' . $home_origin['host'],
462 'https://' . $home_origin['host'],
463 )
464 );
465
466 /**
467 * Changes the origin types allowed for HTTP requests.
468 *
469 * @since 3.4.0
470 *
471 * @param string[] $allowed_origins Array of allowed HTTP origins.
472 */
473 return apply_filters( 'allowed_http_origins', $allowed_origins );
474}
475
476/**
477 * Determines if the HTTP origin is an authorized one.
478 *
479 * @since 3.4.0
480 *
481 * @param string|null $origin Origin URL. If not provided, the value of get_http_origin() is used.
482 * @return string Origin URL if allowed, empty string if not.
483 */
484function is_allowed_http_origin( $origin = null ) {
485 $origin_arg = $origin;
486
487 if ( null === $origin ) {
488 $origin = get_http_origin();
489 }
490
491 if ( $origin && ! in_array( $origin, get_allowed_http_origins(), true ) ) {
492 $origin = '';
493 }
494
495 /**
496 * Changes the allowed HTTP origin result.
497 *
498 * @since 3.4.0
499 *
500 * @param string $origin Origin URL if allowed, empty string if not.
501 * @param string $origin_arg Original origin string passed into is_allowed_http_origin function.
502 */
503 return apply_filters( 'allowed_http_origin', $origin, $origin_arg );
504}
505
506/**
507 * Sends Access-Control-Allow-Origin and related headers if the current request
508 * is from an allowed origin.
509 *
510 * If the request is an OPTIONS request, the script exits with either access
511 * control headers sent, or a 403 response if the origin is not allowed. For
512 * other request methods, you will receive a return value.
513 *
514 * @since 3.4.0
515 *
516 * @return string|false Returns the origin URL if headers are sent. Returns false
517 * if headers are not sent.
518 */
519function send_origin_headers() {
520 $origin = get_http_origin();
521
522 if ( is_allowed_http_origin( $origin ) ) {
523 header( 'Access-Control-Allow-Origin: ' . $origin );
524 header( 'Access-Control-Allow-Credentials: true' );
525 if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) {
526 exit;
527 }
528 return $origin;
529 }
530
531 if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) {
532 status_header( 403 );
533 exit;
534 }
535
536 return false;
537}
538
539/**
540 * Validates a URL as safe for use in the HTTP API.
541 *
542 * The only supported protocols are `http` and `https`.
543 *
544 * Examples of URLs that are considered unsafe:
545 *
546 * - `ftp://example.com/caniload.php` - Invalid protocol - only http and https are allowed.
547 * - `http:///example.com/caniload.php` - Malformed URL.
548 * - `http://user:pass@example.com/caniload.php` - Login information.
549 * - `http://example.invalid/caniload.php` - Invalid hostname, as the IP cannot be looked up in DNS.
550 *
551 * Examples of URLs that are considered unsafe by default but can be allowed with filters:
552 *
553 * - `http://192.168.0.1/caniload.php` - IP address from LAN network.
554 * This can be changed with the {@see 'http_request_host_is_external'} filter.
555 * - `http://198.143.164.252:81/caniload.php` - By default, only ports 80, 443, and 8080 are allowed.
556 * This can be changed with the {@see 'http_allowed_safe_ports'} filter.
557 *
558 * @since 3.5.2
559 *
560 * @param string $url Request URL.
561 * @return string|false Returns false if the URL is not safe, or the original URL if it is safe.
562 */
563function wp_http_validate_url( $url ) {
564 if ( ! is_string( $url ) || '' === $url || is_numeric( $url ) ) {
565 return false;
566 }
567
568 $original_url = $url;
569 $url = wp_kses_bad_protocol( $url, array( 'http', 'https' ) );
570 if ( ! $url || strtolower( $url ) !== strtolower( $original_url ) ) {
571 return false;
572 }
573
574 $parsed_url = parse_url( $url );
575 if ( ! $parsed_url || empty( $parsed_url['host'] ) ) {
576 return false;
577 }
578
579 if ( isset( $parsed_url['user'] ) || isset( $parsed_url['pass'] ) ) {
580 return false;
581 }
582
583 if ( false !== strpbrk( $parsed_url['host'], ':#?[]' ) ) {
584 return false;
585 }
586
587 $parsed_home = parse_url( get_option( 'home' ) );
588 $same_host = isset( $parsed_home['host'] ) && strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] );
589 $host = trim( $parsed_url['host'], '.' );
590
591 if ( ! $same_host ) {
592 if ( preg_match( '#^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$#', $host ) ) {
593 $ip = $host;
594 } else {
595 $ip = gethostbyname( $host );
596 if ( $ip === $host ) { // Error condition for gethostbyname().
597 return false;
598 }
599 }
600 if ( $ip ) {
601 $parts = array_map( 'intval', explode( '.', $ip ) );
602 if ( 127 === $parts[0] || 10 === $parts[0] || 0 === $parts[0]
603 || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] )
604 || ( 192 === $parts[0] && 168 === $parts[1] )
605 ) {
606 // If host appears local, reject unless specifically allowed.
607 /**
608 * Checks if HTTP request is external or not.
609 *
610 * Allows to change and allow external requests for the HTTP request.
611 *
612 * @since 3.6.0
613 *
614 * @param bool $external Whether HTTP request is external or not.
615 * @param string $host Host name of the requested URL.
616 * @param string $url Requested URL.
617 */
618 if ( ! apply_filters( 'http_request_host_is_external', false, $host, $url ) ) {
619 return false;
620 }
621 }
622 }
623 }
624
625 if ( empty( $parsed_url['port'] ) ) {
626 return $url;
627 }
628
629 $port = $parsed_url['port'];
630
631 /**
632 * Controls the list of ports considered safe in HTTP API.
633 *
634 * Allows to change and allow external requests for the HTTP request.
635 *
636 * @since 5.9.0
637 *
638 * @param int[] $allowed_ports Array of integers for valid ports. Default allowed ports
639 * are 80, 443, and 8080.
640 * @param string $host Host name of the requested URL.
641 * @param string $url Requested URL.
642 */
643 $allowed_ports = apply_filters( 'http_allowed_safe_ports', array( 80, 443, 8080 ), $host, $url );
644 if ( is_array( $allowed_ports ) && in_array( $port, $allowed_ports, true ) ) {
645 return $url;
646 }
647
648 if ( $parsed_home && $same_host && isset( $parsed_home['port'] ) && $parsed_home['port'] === $port ) {
649 return $url;
650 }
651
652 return false;
653}
654
655/**
656 * Marks allowed redirect hosts safe for HTTP requests as well.
657 *
658 * Attached to the {@see 'http_request_host_is_external'} filter.
659 *
660 * @since 3.6.0
661 *
662 * @param bool $is_external
663 * @param string $host
664 * @return bool
665 */
666function allowed_http_request_hosts( $is_external, $host ) {
667 if ( ! $is_external && wp_validate_redirect( 'http://' . $host ) ) {
668 $is_external = true;
669 }
670 return $is_external;
671}
672
673/**
674 * Adds any domain in a multisite installation for safe HTTP requests to the
675 * allowed list.
676 *
677 * Attached to the {@see 'http_request_host_is_external'} filter.
678 *
679 * @since 3.6.0
680 *
681 * @global wpdb $wpdb WordPress database abstraction object.
682 *
683 * @param bool $is_external
684 * @param string $host
685 * @return bool
686 */
687function ms_allowed_http_request_hosts( $is_external, $host ) {
688 global $wpdb;
689 static $queried = array();
690 if ( $is_external ) {
691 return $is_external;
692 }
693 if ( get_network()->domain === $host ) {
694 return true;
695 }
696 if ( isset( $queried[ $host ] ) ) {
697 return $queried[ $host ];
698 }
699 $queried[ $host ] = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT domain FROM $wpdb->blogs WHERE domain = %s LIMIT 1", $host ) );
700 return $queried[ $host ];
701}
702
703/**
704 * A wrapper for PHP's parse_url() function that handles consistency in the return values
705 * across PHP versions.
706 *
707 * Across various PHP versions, schemeless URLs containing a ":" in the query
708 * are being handled inconsistently. This function works around those differences.
709 *
710 * @since 4.4.0
711 * @since 4.7.0 The `$component` parameter was added for parity with PHP's `parse_url()`.
712 *
713 * @link https://www.php.net/manual/en/function.parse-url.php
714 *
715 * @param string $url The URL to parse.
716 * @param int $component The specific component to retrieve. Use one of the PHP
717 * predefined constants to specify which one.
718 * Defaults to -1 (= return all parts as an array).
719 * @return mixed False on parse failure; Array of URL components on success;
720 * When a specific component has been requested: null if the component
721 * doesn't exist in the given URL; a string or - in the case of
722 * PHP_URL_PORT - integer when it does. See parse_url()'s return values.
723 */
724function wp_parse_url( $url, $component = -1 ) {
725 $to_unset = array();
726 $url = (string) $url;
727
728 if ( str_starts_with( $url, '//' ) ) {
729 $to_unset[] = 'scheme';
730 $url = 'placeholder:' . $url;
731 } elseif ( str_starts_with( $url, '/' ) ) {
732 $to_unset[] = 'scheme';
733 $to_unset[] = 'host';
734 $url = 'placeholder://placeholder' . $url;
735 }
736
737 $parts = parse_url( $url );
738
739 if ( false === $parts ) {
740 // Parsing failure.
741 return $parts;
742 }
743
744 // Remove the placeholder values.
745 foreach ( $to_unset as $key ) {
746 unset( $parts[ $key ] );
747 }
748
749 return _get_component_from_parsed_url_array( $parts, $component );
750}
751
752/**
753 * Retrieves a specific component from a parsed URL array.
754 *
755 * @internal
756 *
757 * @since 4.7.0
758 * @access private
759 *
760 * @link https://www.php.net/manual/en/function.parse-url.php
761 *
762 * @param array|false $url_parts The parsed URL. Can be false if the URL failed to parse.
763 * @param int $component The specific component to retrieve. Use one of the PHP
764 * predefined constants to specify which one.
765 * Defaults to -1 (= return all parts as an array).
766 * @return mixed False on parse failure; Array of URL components on success;
767 * When a specific component has been requested: null if the component
768 * doesn't exist in the given URL; a string or - in the case of
769 * PHP_URL_PORT - integer when it does. See parse_url()'s return values.
770 */
771function _get_component_from_parsed_url_array( $url_parts, $component = -1 ) {
772 if ( -1 === $component ) {
773 return $url_parts;
774 }
775
776 $key = _wp_translate_php_url_constant_to_key( $component );
777 if ( false !== $key && is_array( $url_parts ) && isset( $url_parts[ $key ] ) ) {
778 return $url_parts[ $key ];
779 } else {
780 return null;
781 }
782}
783
784/**
785 * Translates a PHP_URL_* constant to the named array keys PHP uses.
786 *
787 * @internal
788 *
789 * @since 4.7.0
790 * @access private
791 *
792 * @link https://www.php.net/manual/en/url.constants.php
793 *
794 * @param int $constant PHP_URL_* constant.
795 * @return string|false The named key or false.
796 */
797function _wp_translate_php_url_constant_to_key( $constant ) {
798 $translation = array(
799 PHP_URL_SCHEME => 'scheme',
800 PHP_URL_HOST => 'host',
801 PHP_URL_PORT => 'port',
802 PHP_URL_USER => 'user',
803 PHP_URL_PASS => 'pass',
804 PHP_URL_PATH => 'path',
805 PHP_URL_QUERY => 'query',
806 PHP_URL_FRAGMENT => 'fragment',
807 );
808
809 if ( isset( $translation[ $constant ] ) ) {
810 return $translation[ $constant ];
811 } else {
812 return false;
813 }
814}
815