From fb8d5a6a491cb7b292cd4c7788b3bfe46fca3f1c Mon Sep 17 00:00:00 2001 From: Alejandro Mostajo Date: Sun, 15 Dec 2019 09:43:08 -0600 Subject: [PATCH] Sanitization fixes. Adds `$wpdb->esc_like` sanitization inside builder. Added wildcard sanitization options inside builder and fixed sanitization algorithm. Unit testing added. --- src/QueryBuilder.php | 57 +++++- tests/cases/QueryBuilderStatementsTest.php | 5 +- tests/cases/SanitizationTest.php | 207 +++++++++++++++++++++ tests/framework/wp.php | 5 +- tests/framework/wpdb.php | 20 +- 5 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 tests/cases/SanitizationTest.php diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 2e31247..ac2da0b 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -542,19 +542,70 @@ private function _query_offset( &$query ) private function sanitize_value( $callback, $value ) { if ( $callback === true ) - $callback = is_float( $value ) + $callback = ( is_numeric( $value ) && strpos( $value, '.' ) !== false ) ? 'floatval' - : ( is_integer( $value ) + : ( is_numeric( $value ) ? 'absint' : ( is_string( $value ) ? 'sanitize_text_field' : null ) ); + if ( strpos( $callback, '_builder' ) !== false ) + $callback = [&$this, $callback]; if ( is_array( $value ) ) for ( $i = count( $value ) -1; $i >= 0; --$i ) { $value[$i] = $this->sanitize_value( true, $value[$i] ); } - return $callback ? call_user_func_array( $callback, [$value] ) : $value; + return $callback && is_callable( $callback ) ? call_user_func_array( $callback, [$value] ) : $value; + } + /** + * Returns value escaped with WPDB `esc_like`, + * @since 1.0.6 + * + * @param mixed $value + * + * @return string + */ + private function _builder_esc_like( $value ) + { + global $wpdb; + return $wpdb->esc_like( $value ); + } + /** + * Returns escaped value for LIKE comparison and appends wild card at the beggining. + * @since 1.0.6 + * + * @param mixed $value + * + * @return string + */ + private function _builder_esc_like_wild_value( $value ) + { + return '%' . $this->_builder_esc_like( $value ); + } + /** + * Returns escaped value for LIKE comparison and appends wild card at the end. + * @since 1.0.6 + * + * @param mixed $value + * + * @return string + */ + private function _builder_esc_like_value_wild( $value ) + { + return $this->_builder_esc_like( $value ) . '%'; + } + /** + * Returns escaped value for LIKE comparison and appends wild cards at both ends. + * @since 1.0.6 + * + * @param mixed $value + * + * @return string + */ + private function _builder_esc_like_wild_wild( $value ) + { + return '%' . $this->_builder_esc_like( $value ) . '%'; } } \ No newline at end of file diff --git a/tests/cases/QueryBuilderStatementsTest.php b/tests/cases/QueryBuilderStatementsTest.php index b601981..430ac16 100644 --- a/tests/cases/QueryBuilderStatementsTest.php +++ b/tests/cases/QueryBuilderStatementsTest.php @@ -642,7 +642,10 @@ public function testWhereRawStatement() ->from( 'test_table' ) ->where([ 'test_field' => 1, - 'raw' => 'a = b', + 'raw' => [ + 'value' => 'a = b', + 'sanitize_callback' => false, + ], ]) ->get(); // Assert diff --git a/tests/cases/SanitizationTest.php b/tests/cases/SanitizationTest.php new file mode 100644 index 0000000..40dd7a6 --- /dev/null +++ b/tests/cases/SanitizationTest.php @@ -0,0 +1,207 @@ + Hyper Tribal + * @author 10 Quality + * @license MIT + * @package wp-query-builder + * @version 1.0.6 + */ +class SanitizationTest extends PHPUnit_Framework_TestCase +{ + /** + * Test query builder + * @since 1.0.6 + */ + public function testSanitizeInt() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => '123', + ] ) + ->get(); + $args_int = $wpdb->filter_prepare_args(123); + $args_string = $wpdb->filter_prepare_args('123'); + // Assert + $this->assertTrue(count($args_int) >= 1); + $this->assertTrue(count($args_string) == 0); + } + /** + * Test query builder + * @since 1.0.6 + */ + public function testSanitizeFloat() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => '99.99', + ] ) + ->get(); + $args_float = $wpdb->filter_prepare_args(99.99); + $args_string = $wpdb->filter_prepare_args('99.99'); + // Assert + $this->assertTrue(count($args_float) >= 1); + $this->assertTrue(count($args_string) == 0); + } + /** + * Test query builder + * @since 1.0.6 + */ + public function testSanitizeString() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => 'string val', + ] ) + ->get(); + $args = $wpdb->filter_prepare_args('sanitized(string val)'); + // Assert + $this->assertTrue(count($args) >= 1); + } + /** + * Test query builder + * @since 1.0.6 + */ + public function testSanitizeCustomCallable() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => [ + 'value' => 'string val', + 'sanitize_callback' => 'custom_sanitize', + ], + ] ) + ->get(); + $args = $wpdb->filter_prepare_args('custom(string val)'); + // Assert + $this->assertTrue(count($args) >= 1); + } + /** + * Test query builder + * @since 1.0.6 + */ + public function testEscLike() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => [ + 'operator' => 'LIKE', + 'value' => 'test esc like', + 'sanitize_callback' => '_builder_esc_like', + ], + ] ) + ->get(); + $args = $wpdb->filter_prepare_args('esc_like(test esc like)'); + // Assert + $this->assertTrue(count($args) >= 1); + } + /** + * Test query builder + * @since 1.0.6 + */ + public function testEscLikeWildValue() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => [ + 'operator' => 'LIKE', + 'value' => 'test esc like', + 'sanitize_callback' => '_builder_esc_like_wild_value', + ], + ] ) + ->get(); + $args = $wpdb->filter_prepare_args('%esc_like(test esc like)'); + // Assert + $this->assertTrue(count($args) >= 1); + } + /** + * Test query builder + * @since 1.0.6 + */ + public function testEscLikeValueWild() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => [ + 'operator' => 'LIKE', + 'value' => 'test esc like', + 'sanitize_callback' => '_builder_esc_like_value_wild', + ], + ] ) + ->get(); + $args = $wpdb->filter_prepare_args('esc_like(test esc like)%'); + // Assert + $this->assertTrue(count($args) >= 1); + } + /** + * Test query builder + * @since 1.0.6 + */ + public function testEscLikeWildWild() + { + // Preapre + global $wpdb; + $builder = QueryBuilder::create( 'test' ); + // Prepare + // Prepare + $builder->select( '*' ) + ->from( 'test_table' ) + ->where( [ + 'field' => [ + 'operator' => 'LIKE', + 'value' => 'test esc like', + 'sanitize_callback' => '_builder_esc_like_wild_wild', + ], + ] ) + ->get(); + $args = $wpdb->filter_prepare_args('%esc_like(test esc like)%'); + // Assert + $this->assertTrue(count($args) >= 1); + } +} \ No newline at end of file diff --git a/tests/framework/wp.php b/tests/framework/wp.php index 81264d8..b0855cb 100644 --- a/tests/framework/wp.php +++ b/tests/framework/wp.php @@ -17,5 +17,8 @@ function absint( $value ) { return intval( $value ); } function sanitize_text_field( $value ) { - return $value; + return 'sanitized(' . $value . ')'; +} +function custom_sanitize( $value ) { + return 'custom(' . $value . ')'; } \ No newline at end of file diff --git a/tests/framework/wpdb.php b/tests/framework/wpdb.php index 73c9646..175f94e 100644 --- a/tests/framework/wpdb.php +++ b/tests/framework/wpdb.php @@ -1,7 +1,7 @@