Skip to content
This repository has been archived by the owner on Sep 24, 2018. It is now read-only.

Commit

Permalink
Introduce rest_validate_request_arg()/rest_sanitize_request_arg()
Browse files Browse the repository at this point in the history
Dedicated functions means we can use them for validating / sanitizing
query args too. Removes `WP_REST_Controller::validate_schema_property()`
and `WP_REST_Controller::sanitize_schema_property()`
  • Loading branch information
danielbachhuber committed Jan 30, 2016
1 parent 456472e commit c749796
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 124 deletions.
112 changes: 8 additions & 104 deletions lib/endpoints/class-wp-rest-controller.php
Expand Up @@ -426,8 +426,8 @@ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CRE
}

$endpoint_args[ $field_id ] = array(
'validate_callback' => array( $this, 'validate_schema_property' ),
'sanitize_callback' => array( $this, 'sanitize_schema_property' ),
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'rest_sanitize_request_arg',
);

if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
Expand All @@ -438,6 +438,12 @@ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CRE
$endpoint_args[ $field_id ]['required'] = true;
}

foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
if ( isset( $params[ $schema_prop ] ) ) {
$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
}
}

// Merge in any options provided by the schema property.
if ( isset( $params['arg_options'] ) ) {

Expand All @@ -453,106 +459,4 @@ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CRE
return $endpoint_args;
}

/**
* Validate a parameter value that's based on a property from the item schema.
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $parameter
* @return WP_Error|boolean
*/
public function validate_schema_property( $value, $request, $parameter ) {

/**
* We don't currently validate against empty values, as lots of checks
* can unintentionally fail, as the callback will often handle an empty
* value it's self.
*/
if ( ! $value ) {
return true;
}

$schema = $this->get_item_schema();

if ( ! isset( $schema['properties'][ $parameter ] ) ) {
return true;
}

$property = $schema['properties'][ $parameter ];

if ( ! empty( $property['enum'] ) ) {
if ( ! in_array( $value, $property['enum'] ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not one of %s' ), $parameter, implode( ', ', $property['enum'] ) ) );
}
}

if ( 'integer' === $property['type'] && ! is_numeric( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'integer' ) );
}

if ( 'string' === $property['type'] && ! is_string( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'string' ) );
}

if ( isset( $property['format'] ) ) {
switch ( $property['format'] ) {
case 'date-time' :
if ( ! rest_parse_date( $value ) ) {
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
}
break;

case 'email' :
if ( ! is_email( $value ) ) {
return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
}
break;
}
}

return true;
}

/**
* Sanitize a parameter value that's based on a property from the item schema.
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $parameter
* @return mixed
*/
public function sanitize_schema_property( $value, $request, $parameter ) {

$schema = $this->get_item_schema();

if ( ! isset( $schema['properties'][ $parameter ] ) ) {
return true;
}

$property = $schema['properties'][ $parameter ];

if ( 'integer' === $property['type'] ) {
return (int) $value;
}

if ( isset( $property['format'] ) ) {
switch ( $property['format'] ) {
case 'date-time' :
return sanitize_text_field( $value );

case 'email' :
// as sanitize_email is very lossy, we just want to
// make sure the string is safe.
if ( sanitize_email( $value ) ) {
return sanitize_email( $value );
}
return sanitize_text_field( $value );

case 'uri' :
return esc_url_raw( $value );
}
}

return $value;
}
}
95 changes: 95 additions & 0 deletions plugin.php
Expand Up @@ -288,3 +288,98 @@ function register_api_field( $object_type, $attributes, $args = array() ) {
register_rest_field( $object_type, $attributes, $args );
}
}

if ( ! function_exists( 'rest_validate_request_arg' ) ) {
/**
* Validate a request argument based on details registered to the route.
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $param
* @return WP_Error|boolean
*/
function rest_validate_request_arg( $value, $request, $param ) {

$attributes = $request->get_attributes();
if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
return true;
}
$args = $attributes['args'][ $param ];

if ( ! empty( $args['enum'] ) ) {
if ( ! in_array( $value, $args['enum'] ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not one of %s' ), $param, implode( ', ', $args['enum'] ) ) );
}
}

if ( 'integer' === $args['type'] && ! is_numeric( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $param, 'integer' ) );
}

if ( 'string' === $args['type'] && ! is_string( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $param, 'string' ) );
}

if ( isset( $args['format'] ) ) {
switch ( $args['format'] ) {
case 'date-time' :
if ( ! rest_parse_date( $value ) ) {
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
}
break;

case 'email' :
if ( ! is_email( $value ) ) {
return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
}
break;
}
}

return true;
}
}

if ( ! function_exists( 'rest_sanitize_request_arg' ) ) {
/**
* Sanitize a request argument based on details registered to the route.
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $param
* @return WP_Error|boolean
*/
function rest_sanitize_request_arg( $value, $request, $param ) {

$attributes = $request->get_attributes();
if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
return $value;
}
$args = $attributes['args'][ $param ];

if ( 'integer' === $args['type'] ) {
return (int) $value;
}

if ( isset( $args['format'] ) ) {
switch ( $args['format'] ) {
case 'date-time' :
return sanitize_text_field( $value );

case 'email' :
// as sanitize_email is very lossy, we just want to
// make sure the string is safe.
if ( sanitize_email( $value ) ) {
return sanitize_email( $value );
}
return sanitize_text_field( $value );

case 'uri' :
return esc_url_raw( $value );
}
}

return $value;
}

}
55 changes: 35 additions & 20 deletions tests/test-rest-controller.php
Expand Up @@ -2,74 +2,89 @@

class WP_Test_REST_Controller extends WP_Test_REST_TestCase {

public function setUp() {
parent::setUp();
$this->request = new WP_REST_Request( 'GET', '/wp/v2/testroute', array(
'args' => array(
'someinteger' => array(
'type' => 'integer',
),
'somestring' => array(
'type' => 'string',
),
'someenum' => array(
'type' => 'string',
'enum' => array( 'a' ),
),
'somedate' => array(
'type' => 'string',
'format' => 'date-time',
),
'someemail' => array(
'type' => 'string',
'format' => 'email',
),
),
));
}

public function test_validate_schema_type_integer() {

$controller = new WP_REST_Test_Controller();

$this->assertTrue(
$controller->validate_schema_property( '123', new WP_REST_Request, 'someinteger' )
rest_validate_request_arg( '123', $this->request, 'someinteger' )
);

$this->assertErrorResponse(
'rest_invalid_param',
$controller->validate_schema_property( 'abc', new WP_REST_Request, 'someinteger' )
rest_validate_request_arg( 'abc', $this->request, 'someinteger' )
);
}

public function test_validate_schema_type_string() {

$controller = new WP_REST_Test_Controller();

$this->assertTrue(
$controller->validate_schema_property( '123', new WP_REST_Request, 'somestring' )
rest_validate_request_arg( '123', $this->request, 'somestring' )
);

$this->assertErrorResponse(
'rest_invalid_param',
$controller->validate_schema_property( array( 'foo' => 'bar' ), new WP_REST_Request, 'somestring' )
rest_validate_request_arg( array( 'foo' => 'bar' ), $this->request, 'somestring' )
);
}

public function test_validate_schema_enum() {

$controller = new WP_REST_Test_Controller();

$this->assertTrue(
$controller->validate_schema_property( 'a', new WP_REST_Request, 'someenum' )
rest_validate_request_arg( 'a', $this->request, 'someenum' )
);

$this->assertErrorResponse(
'rest_invalid_param',
$controller->validate_schema_property( 'd', new WP_REST_Request, 'someenum' )
rest_validate_request_arg( 'd', $this->request, 'someenum' )
);
}

public function test_validate_schema_format_email() {

$controller = new WP_REST_Test_Controller();

$this->assertTrue(
$controller->validate_schema_property( 'joe@foo.bar', new WP_REST_Request, 'someemail' )
rest_validate_request_arg( 'joe@foo.bar', $this->request, 'someemail' )
);

$this->assertErrorResponse(
'rest_invalid_email',
$controller->validate_schema_property( 'd', new WP_REST_Request, 'someemail' )
rest_validate_request_arg( 'd', $this->request, 'someemail' )
);
}

public function test_validate_schema_format_date_time() {

$controller = new WP_REST_Test_Controller();

$this->assertTrue(
$controller->validate_schema_property( '2010-01-01T12:00:00', new WP_REST_Request, 'somedate' )
rest_validate_request_arg( '2010-01-01T12:00:00', $this->request, 'somedate' )
);

$this->assertErrorResponse(
'rest_invalid_date',
$controller->validate_schema_property( '2010-18-18T12:00:00', new WP_REST_Request, 'somedate' )
rest_validate_request_arg( '2010-18-18T12:00:00', $this->request, 'somedate' )
);
}

Expand Down

1 comment on commit c749796

@tzkmx
Copy link

@tzkmx tzkmx commented on c749796 Aug 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we use this sanitization/validation functions in our fields added to the resources by register_rest_field, or at creation of new endpoints?

Please sign in to comment.