Changeset 64730
- Timestamp:
- 10/26/2026 03:56:24 PM (41 hours ago)
- Location:
- trunk
- Files:
-
- 1 added
- 2 edited
-
src/wp-includes/rest-api.php (modified) (3 diffs)
-
tests/phpunit/includes/spy-rest-server.php (modified) (1 diff)
-
tests/phpunit/tests/rest-api/rest-cors-headers.php (added)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/rest-api.php
r64689 r64730 250 250 251 251 // Default serving. 252 add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );252 add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers', 10, 4 ); 253 253 add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 ); 254 254 add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 ); … … 809 809 * 810 810 * @since 4.4.0 811 * 812 * @param mixed $value Response data. 811 * @since 7.2.0 Added the `$result`, `$request`, and `$server` parameters and the `rest_allowed_cors_methods` filter. 812 * 813 * @param mixed $value Response data. 814 * @param WP_HTTP_Response|null $result Optional. Result to send to the client. Default null. 815 * @param WP_REST_Request|null $request Optional. The request in context. Default null. 816 * @param WP_REST_Server|null $server Optional. The server instance. Default null. 813 817 * @return mixed Response data. 814 818 */ 815 function rest_send_cors_headers( $value ) {819 function rest_send_cors_headers( $value, $result = null, $request = null, $server = null ) { 816 820 $origin = get_http_origin(); 817 821 … … 821 825 $origin = sanitize_url( $origin ); 822 826 } 823 header( 'Access-Control-Allow-Origin: ' . $origin ); 824 header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); 825 header( 'Access-Control-Allow-Credentials: true' ); 826 header( 'Vary: Origin', false ); 827 828 /* 829 * Send through the server when serving a REST request. A direct call without one 830 * sends the headers itself, so that it does not set up the REST server. 831 */ 832 if ( ! $server instanceof WP_REST_Server ) { 833 $server = null; 834 } 835 $send_header = static function ( string $key, string $header_value ) use ( $server ): void { 836 if ( $server ) { 837 $server->send_header( $key, $header_value ); 838 } elseif ( ! headers_sent() ) { 839 header( "{$key}: {$header_value}" ); 840 } 841 }; 842 843 $send_header( 'Access-Control-Allow-Origin', $origin ); 844 845 /* 846 * The response's own headers have already been sent, and header() replaces them, 847 * so seed the list from any value the response set rather than discarding it. 848 */ 849 $response_headers = ( $result instanceof WP_HTTP_Response ) ? $result->get_headers() : array(); 850 $response_headers = array_change_key_case( $response_headers ); 851 852 /** 853 * Parses a list of methods, keeping only syntactically valid method tokens (RFC 9110, section 9.1). 854 * 855 * @phpstan-var Closure(mixed): list<non-empty-string> $parse_methods 856 */ 857 $parse_methods = static function ( $methods ): array { 858 if ( is_array( $methods ) ) { 859 // Join the entries so that each one is split like a string value, as in array( 'GET, POST' ). 860 $methods = implode( ',', array_filter( $methods, 'is_string' ) ); 861 } elseif ( ! is_string( $methods ) ) { 862 return array(); 863 } 864 $methods = preg_grep( '/^[!#$%&\'*+.^_`|~0-9A-Za-z-]+$/D', array_unique( wp_parse_list( $methods ) ) ); 865 return is_array( $methods ) ? array_values( $methods ) : array(); 866 }; 867 868 // Fall back to the default list when the response names no valid methods. 869 $allow_methods = $parse_methods( $response_headers['access-control-allow-methods'] ?? null ); 870 if ( empty( $allow_methods ) ) { 871 $allow_methods = array( 'OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ); 872 } 873 874 /** 875 * Filters the list of HTTP methods advertised to REST API CORS requests. 876 * 877 * The allowed methods are passed to the browser to specify which methods 878 * may be used in a cross-origin request to the REST API. The list is a 879 * fixed default and is not derived from the registered routes, so a site 880 * serving routes that accept other methods can advertise them here. 881 * 882 * If the response already set its own `Access-Control-Allow-Methods` header 883 * naming valid methods, `$allow_methods` is those methods instead of the default list. 884 * Entries that are not valid method tokens are dropped, and an empty list 885 * omits the header. A value that is neither an array nor a string is ignored. 886 * 887 * @since 7.2.0 888 * 889 * @param string[] $allow_methods The list of HTTP methods to allow. 890 * @param WP_REST_Request|null $request The request in context, if available. 891 */ 892 $filtered_methods = apply_filters( 'rest_allowed_cors_methods', $allow_methods, $request instanceof WP_REST_Request ? $request : null ); 893 894 // Keep the unfiltered list for a callback that does not return one, rather than omitting the header. 895 if ( is_array( $filtered_methods ) || is_string( $filtered_methods ) ) { 896 $allow_methods = $parse_methods( $filtered_methods ); 897 } 898 899 if ( ! empty( $allow_methods ) ) { 900 $send_header( 'Access-Control-Allow-Methods', implode( ', ', $allow_methods ) ); 901 } elseif ( $server ) { 902 // Remove a value the response already sent. 903 $server->remove_header( 'Access-Control-Allow-Methods' ); 904 } elseif ( ! headers_sent() ) { 905 header_remove( 'Access-Control-Allow-Methods' ); 906 } 907 908 $send_header( 'Access-Control-Allow-Credentials', 'true' ); 909 910 // WP_REST_Server::send_header() replaces a header, and Vary must be appended to. 911 if ( ! headers_sent() ) { 912 header( 'Vary: Origin', false ); 913 } 827 914 } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) { 828 915 header( 'Vary: Origin', false ); -
trunk/tests/phpunit/includes/spy-rest-server.php
r60185 r64730 3 3 class Spy_REST_Server extends WP_REST_Server { 4 4 5 /** @var array<string, string> */ 5 6 public $sent_headers = array(); 6 7 public $sent_body = '';
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)