Make WordPress Core

Opened 17 months ago

Closed 2 weeks ago

Last modified 2 weeks ago

#62287 closed defect (bug) (fixed)

WP_REST_Posts_Controller::prepare_items_query() - Warning $prepared_args can be null

Reported by: apermo's profile apermo Owned by: westonruter's profile westonruter
Milestone: 7.0 Priority: normal
Severity: normal Version: 4.7
Component: REST API Keywords: has-patch
Focuses: Cc:

Description

In a project I maintain, we are using sentry, and I've received a warning.

Warning: foreach() argument must be of type array|object, null given

For:
/wp/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php in WP_REST_Posts_Controller::prepare_items_query at line 1124

This happens for GET request on /wp-json/wp/vs/posts with the query parameters order, orderby and per_page

I could not yet figure out, why null was given, but I propose to add a is_array() condition around the loop. This will work as intended, without possibly throwing errors as a type hint in the function header woul.d

I will prepare a PR on github with a proposed solution, that will ensure, this warning will not show up.

Change History (10)

This ticket was mentioned in PR #7625 on WordPress/wordpress-develop by @apermo.


17 months ago
#1

  • Keywords has-patch added

As discribed in the track ticket, it is possible that prepare_items_query() can receive a null value as $prepared_args which results in a warning. This fixes this issue.

Trac ticket: https://core.trac.wordpress.org/ticket/62287

#2 @apermo
3 weeks ago

  • Version set to 4.7
  • Posts controller (class-wp-rest-posts-controller.php): The vulnerable foreach ($prepared_args as ...) pattern was introduced in commit 4f685410b2 ("REST API: Remove get_allowed_query_vars() now filter is gone.") — first released in WordPress 4.7.0.
  • Revisions controller (class-wp-rest-revisions-controller.php): Introduced in commit b513113c49 ("REST API: Support pagination, order, search and other common query parameters for revisions.") — first released in WordPress 5.0.0.


#3 @dmsnell
3 weeks ago

thanks for sharing this report @apermo — I can only guess that some custom code implementing the rest_{$this->post_type}_query is returning NULL, which then gets sent into prepare_items_query()

because of that, and because it’s protected/private, I feel like it could be more reasonable to fix the call sites, defaulting NULL values to array() before sending them to the method. why? this is coherent with the existing types, which state that the argument must be an array, and while it’s optional, that does not permit sending a NULL value — only in sending no values.

open to thoughts here. if this were more exposed to plugin and theme authors as part of a public interface I would probably favor adding more guards internally, but in doing so we create a inconsistency in the code between the types and the expectations.

@dmsnell commented on PR #7625:


3 weeks ago
#4

In order to see the tests pass I have merged trunk, which includes a fix for broken end-to-end tests.

@westonruter commented on PR #7625:


3 weeks ago
#5

@dmsnell I'm glad to see this. I think your addition is the key one (which I iterated on a bit more in e54f258ea1a23c5be7e89220e8c752a0776bb9e6). The return value of apply_filters() cannot be trusted. If that along addresses the issue, it would seem fine to revert the change to the prepare_items_query methods, which I also amended in 35606cfd2db5ca90e3369007e3a7e7c6abf89f3e to account for other values being passed in, for example an empty string.

With that in place, I'd ideally want to add the type hints to the methods and eliminate the default values, since the params are “always” provided:

  • src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
    index 720f122aed..83467201ba 100644
    a b class WP_REST_Posts_Controller extends WP_REST_Controller { 
    12081208         *
    12091209         * @since 4.7.0
    12101210         *
    1211          * @param array           $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
    1212          * @param WP_REST_Request $request       Optional. Full details about the request.
     1211         * @param array           $prepared_args Prepared WP_Query arguments. Default empty array.
     1212         * @param WP_REST_Request $request       Full details about the request.
    12131213         * @return array Items query arguments.
    12141214         */
    1215         protected function prepare_items_query( $prepared_args = array(), $request = null ) {
     1215        protected function prepare_items_query( array $prepared_args, WP_REST_Request $request ): array {
    12161216                $query_args = array();
    1217                 if ( is_array( ! $prepared_args ) ) {
    1218                         $prepared_args = array();
    1219                 }
    12201217
    12211218                foreach ( $prepared_args as $key => $value ) {
    12221219                        /**
  • src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php
    index cc483edacf..20a050a6e8 100644
    a b class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    546546         *
    547547         * @since 5.0.0
    548548         *
    549          * @param array           $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
    550          * @param WP_REST_Request $request       Optional. Full details about the request.
     549         * @param array           $prepared_args Prepared WP_Query arguments. Default empty array.
     550         * @param WP_REST_Request $request       Full details about the request.
    551551         * @return array Items query arguments.
    552552         */
    553         protected function prepare_items_query( $prepared_args = array(), $request = null ) {
     553        protected function prepare_items_query( array $prepared_args, WP_REST_Request $request ): array {
    554554                $query_args = array();
    555                 if ( is_array( ! $prepared_args ) ) {
    556                         $prepared_args = array();
    557                 }
    558555
    559556                foreach ( $prepared_args as $key => $value ) {
    560557                        /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */

However, this does not account for subclasses which may be calling this protected method. So if we did that, we should do so early in a release cycle to ensure plugins which extend these classes are passing in the proper values as we've done in this PR with type checking the filter return values. But since a subclass may be also using a filter for the $prepared_args param being passed in to prepare_items_query() then it seems safest to add the type checking inside method as well since we didn't add the type hint in the first place back in 5.0.

#6 @westonruter
3 weeks ago

  • Milestone changed from Awaiting Review to 7.0

#7 @westonruter
3 weeks ago

  • Owner set to westonruter
  • Status changed from new to reviewing

#8 @westonruter
2 weeks ago

  • Resolution set to fixed
  • Status changed from reviewing to closed

In 61773:

REST API: Guard against passing null as $prepared_args to WP_REST_Posts_Controller::prepare_items_query().

The variable may get set to null by a filter or subclass.

Developed in https://github.com/WordPress/wordpress-develop/pull/7625

Follow-up to r38832.

Props apermo, dmsnell, westonruter.
Fixes #62287.

@dmsnell commented on PR #7625:


2 weeks ago
#9

Thanks @westonruter — I had a commit prepared but had been stalled by some flakey PHP Unit Tests and was trying to wait until they passed. Appreciate the teamwork.

@westonruter commented on PR #7625:


2 weeks ago
#10

@dmsnell oh! Sorry if I stepped on your toes. 🤝

Note: See TracTickets for help on using tickets.