← All changes
|
includes/rest-api/controller/version2/class-mainwp-rest-monitors-controller.php
+63
-3
6.1.8
→
6.2.2
View file →
| @@ -883,8 +883,16 @@ | ||
| 883 | 883 | */ |
| 884 | 884 | public function update_global_monitoring_settings( $request ) { |
| 885 | 885 | // Get request body. |
| 886 | 886 | $body = $request->get_json_params(); |
| 887 | + | |
| 888 | + // A JSON scalar or list body carries none of the settings keys: array_intersect_key() below | |
| 889 | + // fatals on the scalar, and the list would intersect down to nothing and answer as a | |
| 890 | + // successful no-op, so both are treated as no body at all. | |
| 891 | + if ( ! is_array( $body ) || ( array() !== $body && array_keys( $body ) === range( 0, count( $body ) - 1 ) ) ) { | |
| 892 | + $body = array(); | |
| 893 | + } | |
| 894 | + | |
| 887 | 895 | if ( empty( $body ) ) { |
| 888 | 896 | return new WP_Error( |
| 889 | 897 | 'empty_body', |
| 890 | 898 | __( 'Request body is empty.', 'mainwp' ), |
| @@ -900,10 +908,27 @@ | ||
| 900 | 908 | __( 'Invalid content type. Expected application/json.', 'mainwp' ), |
| 901 | 909 | ); |
| 902 | 910 | } |
| 903 | 911 | |
| 912 | + // Reject a key the route does not declare. get_sanitized_settings_params() drops it, so a | |
| 913 | + // misspelled setting would otherwise reach the processor as an empty settings array and | |
| 914 | + // answer as a successful no-op. The individual route reports the same mistake through its | |
| 915 | + // schema, so it gets the same error code here. | |
| 916 | + $unknown_keys = array_diff( array_keys( $body ), array_keys( $this->get_global_monitor_settings_allowed_fields() ) ); | |
| 917 | + if ( ! empty( $unknown_keys ) ) { | |
| 918 | + return new WP_Error( | |
| 919 | + 'rest_additional_properties_forbidden', | |
| 920 | + sprintf( | |
| 921 | + /* translators: %s: comma separated list of request body keys the route does not accept. */ | |
| 922 | + __( 'Unknown settings: %s.', 'mainwp' ), | |
| 923 | + implode( ', ', $unknown_keys ) | |
| 924 | + ), | |
| 925 | + array( 'status' => 400 ) | |
| 926 | + ); | |
| 927 | + } | |
| 928 | + | |
| 904 | 929 | // Process global monitoring settings update. |
| 905 | - $result = $this->process_global_monitoring_settings_update( $body ); | |
| 930 | + $result = $this->process_global_monitoring_settings_update( $this->get_sanitized_settings_params( $request, $body ) ); | |
| 906 | 931 | if ( is_wp_error( $result ) ) { |
| 907 | 932 | return $result; |
| 908 | 933 | } |
| 909 | 934 | |
| @@ -938,12 +963,20 @@ | ||
| 938 | 963 | |
| 939 | 964 | // Get request body. |
| 940 | 965 | $body = $request->get_json_params(); |
| 941 | 966 | |
| 967 | + // A JSON scalar or list body carries none of the settings keys: array_intersect_key() below | |
| 968 | + // fatals on the scalar, and the list would intersect down to nothing and answer as a | |
| 969 | + // successful no-op, so both are treated as no body at all. | |
| 970 | + if ( ! is_array( $body ) || ( array() !== $body && array_keys( $body ) === range( 0, count( $body ) - 1 ) ) ) { | |
| 971 | + $body = array(); | |
| 972 | + } | |
| 973 | + | |
| 942 | 974 | if ( empty( $body ) ) { |
| 943 | 975 | return new WP_Error( |
| 944 | 976 | 'empty_body', |
| 945 | 977 | __( 'Request body is empty.', 'mainwp' ), |
| 978 | + array( 'status' => 400 ) | |
| 946 | 979 | ); |
| 947 | 980 | } |
| 948 | 981 | |
| 949 | 982 | // Check content type. |
| @@ -954,17 +987,22 @@ | ||
| 954 | 987 | __( 'Invalid content type. Expected application/json.', 'mainwp' ), |
| 955 | 988 | ); |
| 956 | 989 | } |
| 957 | 990 | |
| 958 | - // Validate request body against schema. | |
| 991 | + // Validate request body against schema. The schema has to see the raw body: the sanitized | |
| 992 | + // params keep only the keys the route registered, so a misspelled key would be filtered out | |
| 993 | + // before additionalProperties could reject it. | |
| 959 | 994 | $schema = $this->get_monitor_settings_schema(); // Get default schema. |
| 960 | 995 | $valid = rest_validate_value_from_schema( $body, $schema, 'body' ); |
| 961 | 996 | if ( is_wp_error( $valid ) ) { |
| 997 | + // A schema error carries no status of its own and REST falls back to 500 for one that | |
| 998 | + // has none, which is the wrong answer for a body the caller got wrong. | |
| 999 | + $valid->add_data( array( 'status' => 400 ) ); | |
| 962 | 1000 | return $valid; |
| 963 | 1001 | } |
| 964 | 1002 | |
| 965 | 1003 | // Sanitize request body. |
| 966 | - $data = rest_sanitize_value_from_schema( $body, $schema ); | |
| 1004 | + $data = rest_sanitize_value_from_schema( $this->get_sanitized_settings_params( $request, $body ), $schema ); | |
| 967 | 1005 | |
| 968 | 1006 | // Process individual monitor settings update. |
| 969 | 1007 | $result = $this->process_individual_monitor_settings_update( $monitor, $data ); |
| 970 | 1008 | |
| @@ -1054,8 +1092,30 @@ | ||
| 1054 | 1092 | 'uptime_status' => $check_result ? 'up' : 'down', |
| 1055 | 1093 | ), |
| 1056 | 1094 | ) |
| 1057 | 1095 | ); |
| 1096 | + } | |
| 1097 | + | |
| 1098 | + /** | |
| 1099 | + * Get the settings the request carries, as the registered callbacks sanitized them. | |
| 1100 | + * | |
| 1101 | + * @param WP_REST_Request $request Full details about the request. | |
| 1102 | + * @param array $body Raw request body. | |
| 1103 | + * | |
| 1104 | + * @return array Sanitized settings, keyed by the keys the body carried. | |
| 1105 | + */ | |
| 1106 | + private function get_sanitized_settings_params( $request, $body ) { | |
| 1107 | + // The registered sanitize_callbacks produce the request params, so the persisted value is | |
| 1108 | + // read from there rather than from the raw body; WordPress currently writes the sanitized | |
| 1109 | + // value back into the JSON body too, but that is internal to sanitize_params(), not a | |
| 1110 | + // contract. get_params() also carries auth, query and route params, so keep only the | |
| 1111 | + // settings keys the route declares and only the ones this body actually sent. It merges in | |
| 1112 | + // the reverse of get_parameter_order(), so a JSON body value beats a query parameter of the | |
| 1113 | + // same name rather than the other way round. | |
| 1114 | + $allowed = $this->get_global_monitor_settings_allowed_fields(); | |
| 1115 | + $params = array_intersect_key( $request->get_params(), $allowed ); | |
| 1116 | + | |
| 1117 | + return array_intersect_key( $params, $body ); | |
| 1058 | 1118 | } |
| 1059 | 1119 | |
| 1060 | 1120 | /** |
| 1061 | 1121 | * Process global monitoring settings update with all business logic. |