From 1a65652cbce3b3d91d4f647d4c06589dcf800de6 Mon Sep 17 00:00:00 2001 From: TimothyBlynJacobs Date: Sun, 24 Jan 2021 16:52:00 +0000 Subject: [PATCH] REST API: Support type coercion when validating the `enum` JSON Schema keyword. Previously, the `enum` keyword was validated by perform a strict equality check. For `string` types this is generally ok, but it prevented using alternative types like `number` when rich type support isn't available. Now the same level of type coercion/sanitization is applied when validating `enum` as all other validation checks. This means that a value of `"1"` will be accepted for an `enum` of `[ 0, 1 ]`. Additionally, `object` types now properly ignore key order when checking for equality. Props yakimun. Fixes #51911. Built from https://develop.svn.wordpress.org/trunk@50010 git-svn-id: http://core.svn.wordpress.org/trunk@49711 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/rest-api.php | 81 ++++++++++++++++++++++++++++++++++++---- wp-includes/version.php | 2 +- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php index 1dd54b27d6..946c3cdf33 100644 --- a/wp-includes/rest-api.php +++ b/wp-includes/rest-api.php @@ -1874,6 +1874,73 @@ function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first return $matching_schemas[0]['schema_object']; } +/** + * Checks the equality of two values, following JSON Schema semantics. + * + * Property order is ignored for objects. + * + * Values must have been previously sanitized/coerced to their native types. + * + * @since 5.7.0 + * + * @param mixed $value1 The first value to check. + * @param mixed $value2 The second value to check. + * @return bool True if the values are equal or false otherwise. + */ +function rest_are_values_equal( $value1, $value2 ) { + if ( is_array( $value1 ) && is_array( $value2 ) ) { + if ( count( $value1 ) !== count( $value2 ) ) { + return false; + } + + foreach ( $value1 as $index => $value ) { + if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) { + return false; + } + } + + return true; + } + + return $value1 === $value2; +} + +/** + * Validates that the given value is a member of the JSON Schema "enum". + * + * @since 5.7.0 + * + * @param mixed $value The value to validate. + * @param array $args The schema array to use. + * @param string $param The parameter name, used in error messages. + * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise. + */ +function rest_validate_enum( $value, $args, $param ) { + $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param ); + if ( is_wp_error( $sanitized_value ) ) { + return $sanitized_value; + } + + foreach ( $args['enum'] as $enum_value ) { + if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) { + return true; + } + } + + $encoded_enum_values = array(); + foreach ( $args['enum'] as $enum_value ) { + $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value ); + } + + if ( count( $encoded_enum_values ) === 1 ) { + /* translators: 1: Parameter, 2: Valid values. */ + return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) ); + } + + /* translators: 1: Parameter, 2: List of valid values. */ + return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) ); +} + /** * Get all valid JSON schema properties. * @@ -2153,13 +2220,6 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) { return true; } - if ( ! empty( $args['enum'] ) ) { - if ( ! in_array( $value, $args['enum'], true ) ) { - /* translators: 1: Parameter, 2: List of valid values. */ - return new WP_Error( 'rest_not_in_enum', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); - } - } - if ( in_array( $args['type'], array( 'integer', 'number' ), true ) ) { if ( ! is_numeric( $value ) ) { return new WP_Error( @@ -2234,6 +2294,13 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) { } } + if ( ! empty( $args['enum'] ) ) { + $enum_contains_value = rest_validate_enum( $value, $args, $param ); + if ( is_wp_error( $enum_contains_value ) ) { + return $enum_contains_value; + } + } + // The "format" keyword should only be applied to strings. However, for backward compatibility, // we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value. if ( isset( $args['format'] ) diff --git a/wp-includes/version.php b/wp-includes/version.php index bb8ced8e29..b447134fca 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -13,7 +13,7 @@ * * @global string $wp_version */ -$wp_version = '5.7-alpha-50009'; +$wp_version = '5.7-alpha-50010'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.