From a2ffb2990881a7006d9f42e7b6200c95a84c06a0 Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Tue, 17 Sep 2024 10:04:18 -0700 Subject: [PATCH] Add validation and sanitization. --- .../rest-api/class-wp-rest-server.php | 78 +++++++++++++------ tests/phpunit/tests/rest-api/rest-server.php | 29 +++++++ 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index f92bfc674feb0..87e6e351fd56d 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -628,8 +628,6 @@ public static function get_response_links( $response ) { return array(); } - $server = rest_get_server(); - // Convert links to part of the data. $data = array(); foreach ( $links as $rel => $items ) { @@ -644,37 +642,67 @@ public static function get_response_links( $response ) { continue; } - // Prefer targetHints that were specifically designated by the developer. - if ( isset( $attributes['targetHints']['allow'] ) ) { - $data[ $rel ][] = $attributes; - continue; + $target_hints = self::get_target_hints_for_link( $item['href'] ); + if ( $target_hints ) { + $attributes['targetHints'] = $target_hints; } - $request = WP_REST_Request::from_url( $item['href'] ); - if ( ! $request ) { - $data[ $rel ][] = $attributes; - continue; - } + $data[ $rel ][] = $attributes; + } + } - $match = $server->match_request_to_handler( $request ); - if ( ! is_wp_error( $match ) ) { - $response = new WP_REST_Response(); - $response->set_matched_route( $match[0] ); - $response->set_matched_handler( $match[1] ); - $headers = rest_send_allow_header( $response, $server, $request )->get_headers(); + return $data; + } - foreach ( $headers as $name => $value ) { - $name = WP_REST_Request::canonicalize_header_name( $name ); + /** + * Gets the target links for a REST API Link. + * + * @since 6.7.0 + * + * @param array $link + * + * @return array|null + */ + protected static function get_target_hints_for_link( $link ) { + // Prefer targetHints that were specifically designated by the developer. + if ( isset( $link['targetHints']['allow'] ) ) { + return null; + } - $attributes['targetHints'][ $name ] = array_map( 'trim', explode( ',', $value ) ); - } - } + $request = WP_REST_Request::from_url( $link['href'] ); + if ( ! $request ) { + return null; + } - $data[ $rel ][] = $attributes; - } + $server = rest_get_server(); + $match = $server->match_request_to_handler( $request ); + + if ( is_wp_error( $match ) ) { + return null; } - return $data; + if ( is_wp_error( $request->has_valid_params() ) ) { + return null; + } + + if ( is_wp_error( $request->sanitize_params() ) ) { + return null; + } + + $target_hints = array(); + + $response = new WP_REST_Response(); + $response->set_matched_route( $match[0] ); + $response->set_matched_handler( $match[1] ); + $headers = rest_send_allow_header( $response, $server, $request )->get_headers(); + + foreach ( $headers as $name => $value ) { + $name = WP_REST_Request::canonicalize_header_name( $name ); + + $target_hints[ $name ] = array_map( 'trim', explode( ',', $value ) ); + } + + return $target_hints; } /** diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php index 421bf2e3528e4..1aa19b4815a41 100644 --- a/tests/phpunit/tests/rest-api/rest-server.php +++ b/tests/phpunit/tests/rest-api/rest-server.php @@ -2441,6 +2441,35 @@ public function test_rest_allowed_cors_headers_filter_receives_request_object() $this->assertSame( '/test-allowed-cors-headers', $mock_hook->get_events()[0]['args'][1]->get_route() ); } + public function test_validates_request_when_building_target_hints() { + register_rest_route( + 'test-ns/v1', + '/test/(?P\d+)', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => static function () { + return new \WP_REST_Response(); + }, + 'permission_callback' => '__return_true', + 'args' => array( + 'id' => array( + 'type' => 'integer', + ), + ), + ), + ) + ); + + $response = new WP_REST_Response(); + $response->add_link( 'self', rest_url( 'test-ns/v1/test/garbage' ) ); + + $links = rest_get_server()::get_response_links( $response ); + + $this->assertArrayHasKey( 'self', $links['self'] ); + $this->assertArrayNotHasKey( 'targetHints', $links['self'][0] ); + } + public function test_populates_target_hints_for_administrator() { wp_set_current_user( self::$admin_id ); $response = rest_do_request( '/wp/v2/posts' );