From e1692a451aa94d147b3cb4c4fcce2006cccab075 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Tue, 26 Nov 2024 14:35:38 +0100 Subject: [PATCH 1/6] Don't prepare response for a single taxonomy. --- src/wp-includes/rest-api/class-wp-rest-request.php | 12 ++++++++++++ .../class-wp-rest-taxonomies-controller.php | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/src/wp-includes/rest-api/class-wp-rest-request.php b/src/wp-includes/rest-api/class-wp-rest-request.php index 6ed6ce667432c..3257194e547d2 100644 --- a/src/wp-includes/rest-api/class-wp-rest-request.php +++ b/src/wp-includes/rest-api/class-wp-rest-request.php @@ -161,6 +161,18 @@ public function get_headers() { return $this->headers; } + /** + * Determines if the request is the given method. + * + * @since 6.8.0 + * + * @param string $method HTTP method. + * @return bool Whether the request is of the given method. + */ + public function is_method( $method ) { + return $this->get_method() === strtoupper( $method ); + } + /** * Canonicalizes the header name. * diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php index 0b15f3494c4b5..22f1f0e86f6d8 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php @@ -191,6 +191,10 @@ public function get_item( $request ) { ); } + if ( $request->is_method( 'head' ) ) { + return new WP_REST_Response(); + } + $data = $this->prepare_item_for_response( $tax_obj, $request ); return rest_ensure_response( $data ); From 8bec4d1274330b19454973b919e526eb487fa6ab Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Wed, 27 Nov 2024 22:07:48 +0100 Subject: [PATCH 2/6] Short-circuit the get_items method. --- .../endpoints/class-wp-rest-taxonomies-controller.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php index 22f1f0e86f6d8..d62de0c3e8290 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php @@ -114,6 +114,11 @@ public function get_items_permissions_check( $request ) { */ public function get_items( $request ) { + if ( $request->is_method( 'head' ) ) { + // Return early as this method doesn't add any headers. + return new WP_REST_Response(); + } + // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); From 362cdb19ddf383509a2b4db477307ac302fc5162 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Sun, 1 Dec 2024 12:11:27 +0100 Subject: [PATCH 3/6] Add more unit tests. --- .../rest-api/rest-taxonomies-controller.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php index 86ad0daf351b9..cbc6616c5d6c3 100644 --- a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php +++ b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php @@ -109,6 +109,36 @@ public function test_get_item() { $this->check_taxonomy_object_response( 'view', $response ); } + /** + * @ticket 56481 + */ + public function test_get_item_with_head_request_should_not_prepare_taxonomy_data() { + $request = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies/category' ); + $hook_name = 'rest_prepare_taxonomy'; + $filter = new MockAction(); + $callback = array( $filter, 'filter' ); + add_filter( $hook_name, $callback ); + $response = rest_get_server()->dispatch( $request ); + remove_filter( $hook_name, $callback ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + + $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); + } + + /** + * Data provider intended to provide HTTP method names for testing GET and HEAD requests. + * + * @return array + */ + public static function data_readable_http_methods() { + return array( + 'GET request' => array( 'GET' ), + 'HEAD request' => array( 'HEAD' ), + ); + } + public function test_get_item_edit_context() { $editor_id = self::factory()->user->create( array( 'role' => 'editor' ) ); wp_set_current_user( $editor_id ); From 8378c83739878d552c524f1b886f4b7db0aa5932 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Sun, 1 Dec 2024 23:52:41 +0100 Subject: [PATCH 4/6] Add more unit tests. --- .../rest-api/rest-taxonomies-controller.php | 122 +++++++++++++----- 1 file changed, 93 insertions(+), 29 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php index cbc6616c5d6c3..3370177188816 100644 --- a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php +++ b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php @@ -60,6 +60,24 @@ public function test_get_items() { $this->assertSame( 'tags', $data['post_tag']['rest_base'] ); } + /** + * @ticket 56481 + */ + public function test_get_items_with_head_request_should_not_prepare_taxonomy_data() { + $request = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies' ); + $hook_name = 'rest_prepare_taxonomy'; + $filter = new MockAction(); + $callback = array( $filter, 'filter' ); + add_filter( $hook_name, $callback ); + $response = rest_get_server()->dispatch( $request ); + remove_filter( $hook_name, $callback ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + + $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); + $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); + } + public function test_get_items_context_edit() { wp_set_current_user( self::$contributor_id ); $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); @@ -79,26 +97,60 @@ public function test_get_items_context_edit() { $this->assertSame( 'tags', $data['post_tag']['rest_base'] ); } - public function test_get_items_invalid_permission_for_context() { + + /** + * @dataProvider data_readable_http_methods + * @ticket 56481 + * + * @param string $method HTTP method to use. + */ + public function test_get_items_invalid_permission_for_context( $method ) { wp_set_current_user( 0 ); - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); + $request = new WP_REST_Request( $method, '/wp/v2/taxonomies' ); $request->set_param( 'context', 'edit' ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_cannot_view', $response, 401 ); } - public function test_get_taxonomies_for_type() { - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); + /** + * Data provider intended to provide HTTP method names for testing GET and HEAD requests. + * + * @return array + */ + public static function data_readable_http_methods() { + return array( + 'GET request' => array( 'GET' ), + 'HEAD request' => array( 'HEAD' ), + ); + } + + /** + * @dataProvider data_readable_http_methods + * @ticket 56481 + * + * @param string $method HTTP method to use. + */ + public function test_get_taxonomies_for_type( $method ) { + $request = new WP_REST_Request( $method, '/wp/v2/taxonomies' ); $request->set_param( 'type', 'post' ); $response = rest_get_server()->dispatch( $request ); $this->check_taxonomies_for_type_response( 'post', $response ); } - public function test_get_taxonomies_for_invalid_type() { - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); + /** + * @dataProvider data_readable_http_methods + * @ticket 56481 + * + * @param string $method HTTP method to use. + */ + public function test_get_taxonomies_for_invalid_type( $method ) { + $request = new WP_REST_Request( $method, '/wp/v2/taxonomies' ); $request->set_param( 'type', 'wingding' ); $response = rest_get_server()->dispatch( $request ); $this->assertSame( 200, $response->get_status() ); + if ( 'HEAD' === $method ) { + return; + } $data = $response->get_data(); $this->assertSame( '{}', json_encode( $data ) ); } @@ -113,10 +165,10 @@ public function test_get_item() { * @ticket 56481 */ public function test_get_item_with_head_request_should_not_prepare_taxonomy_data() { - $request = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies/category' ); + $request = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies/category' ); $hook_name = 'rest_prepare_taxonomy'; - $filter = new MockAction(); - $callback = array( $filter, 'filter' ); + $filter = new MockAction(); + $callback = array( $filter, 'filter' ); add_filter( $hook_name, $callback ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); @@ -127,18 +179,6 @@ public function test_get_item_with_head_request_should_not_prepare_taxonomy_data $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); } - /** - * Data provider intended to provide HTTP method names for testing GET and HEAD requests. - * - * @return array - */ - public static function data_readable_http_methods() { - return array( - 'GET request' => array( 'GET' ), - 'HEAD request' => array( 'HEAD' ), - ); - } - public function test_get_item_edit_context() { $editor_id = self::factory()->user->create( array( 'role' => 'editor' ) ); wp_set_current_user( $editor_id ); @@ -148,33 +188,57 @@ public function test_get_item_edit_context() { $this->check_taxonomy_object_response( 'edit', $response ); } - public function test_get_item_invalid_permission_for_context() { + /** + * @dataProvider data_readable_http_methods + * @ticket 56481 + * + * @param string $method HTTP method to use. + */ + public function test_get_item_invalid_permission_for_context( $method ) { wp_set_current_user( 0 ); - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' ); + $request = new WP_REST_Request( $method, '/wp/v2/taxonomies/category' ); $request->set_param( 'context', 'edit' ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_forbidden_context', $response, 401 ); } - public function test_get_invalid_taxonomy() { - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/invalid' ); + /** + * @dataProvider data_readable_http_methods + * @ticket 56481 + * + * @param string $method HTTP method to use. + */ + public function test_get_invalid_taxonomy( $method ) { + $request = new WP_REST_Request( $method, '/wp/v2/taxonomies/invalid' ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_taxonomy_invalid', $response, 404 ); } - public function test_get_non_public_taxonomy_not_authenticated() { + /** + * @dataProvider data_readable_http_methods + * @ticket 56481 + * + * @param string $method HTTP method to use. + */ + public function test_get_non_public_taxonomy_not_authenticated( $method ) { register_taxonomy( 'api-private', 'post', array( 'public' => false ) ); - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' ); + $request = new WP_REST_Request( $method, '/wp/v2/taxonomies/api-private' ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); } - public function test_get_non_public_taxonomy_no_permission() { + /** + * @dataProvider data_readable_http_methods + * @ticket 56481 + * + * @param string $method HTTP method to use. + */ + public function test_get_non_public_taxonomy_no_permission( $method ) { wp_set_current_user( self::$contributor_id ); register_taxonomy( 'api-private', 'post', array( 'public' => false ) ); - $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' ); + $request = new WP_REST_Request( $method, '/wp/v2/taxonomies/api-private' ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_forbidden', $response, 403 ); } From 4c12f48e3ee98509937cd2bf749ecf0e93647494 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Mon, 2 Dec 2024 00:20:51 +0100 Subject: [PATCH 5/6] Fix the tests. --- .../tests/rest-api/rest-taxonomies-controller.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php index 3370177188816..8c3ec6bbe8fa2 100644 --- a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php +++ b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php @@ -124,14 +124,8 @@ public static function data_readable_http_methods() { ); } - /** - * @dataProvider data_readable_http_methods - * @ticket 56481 - * - * @param string $method HTTP method to use. - */ - public function test_get_taxonomies_for_type( $method ) { - $request = new WP_REST_Request( $method, '/wp/v2/taxonomies' ); + public function test_get_taxonomies_for_type() { + $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' ); $request->set_param( 'type', 'post' ); $response = rest_get_server()->dispatch( $request ); $this->check_taxonomies_for_type_response( 'post', $response ); From f119b289d9b6c9281ab05c0691959b8cb9c431ab Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Mon, 2 Dec 2024 12:36:09 +0100 Subject: [PATCH 6/6] Fix code style. --- tests/phpunit/tests/rest-api/rest-taxonomies-controller.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php index 8c3ec6bbe8fa2..a4da2c4608a2b 100644 --- a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php +++ b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php @@ -71,9 +71,7 @@ public function test_get_items_with_head_request_should_not_prepare_taxonomy_dat add_filter( $hook_name, $callback ); $response = rest_get_server()->dispatch( $request ); remove_filter( $hook_name, $callback ); - $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); - $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' ); $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' ); }