From 45b0abbef1f15041948cf95ef94ff676f9583a09 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Mon, 27 Apr 2015 14:02:45 +0000 Subject: [PATCH] WPDB: Sanity check that any strings being stored in the DB are not too long to store correctly. git-svn-id: https://develop.svn.wordpress.org/trunk@32299 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/wp-db.php | 114 ++++++++++++++++++++++++++++++++ tests/phpunit/tests/comment.php | 26 ++++++++ tests/phpunit/tests/db.php | 1 + 3 files changed, 141 insertions(+) diff --git a/src/wp-includes/wp-db.php b/src/wp-includes/wp-db.php index 5e5cd1eefd..c49396dc65 100644 --- a/src/wp-includes/wp-db.php +++ b/src/wp-includes/wp-db.php @@ -1946,11 +1946,20 @@ class wpdb { */ protected function process_fields( $table, $data, $format ) { $data = $this->process_field_formats( $data, $format ); + if ( false === $data ) { + return false; + } + $data = $this->process_field_charsets( $data, $table ); if ( false === $data ) { return false; } + $data = $this->process_field_lengths( $data, $table ); + if ( false === $data ) { + return false; + } + $converted_data = $this->strip_invalid_text( $data ); if ( $data !== $converted_data ) { @@ -2031,6 +2040,40 @@ class wpdb { return $data; } + /** + * For string fields, record the maximum string length that field can safely save. + * + * @since 4.2.1 + * @access protected + * + * @param array $data As it comes from the wpdb::process_field_charsets() method. + * @param string $table Table name. + * @return array|False The same array as $data with additional 'length' keys, or false if + * any of the values were too long for their corresponding field. + */ + protected function process_field_lengths( $data, $table ) { + foreach ( $data as $field => $value ) { + if ( '%d' === $value['format'] || '%f' === $value['format'] ) { + // We can skip this field if we know it isn't a string. + // This checks %d/%f versus ! %s because it's sprintf() could take more. + $value['length'] = false; + } else { + $value['length'] = $this->get_col_length( $table, $field ); + if ( is_wp_error( $value['length'] ) ) { + return false; + } + } + + if ( false !== $value['length'] && strlen( $value['value'] ) > $value['length'] ) { + return false; + } + + $data[ $field ] = $value; + } + + return $data; + } + /** * Retrieve one variable from the database. * @@ -2361,6 +2404,77 @@ class wpdb { return $charset; } + /** + * Retrieve the maximum string length allowed in a given column. + * + * @since 4.2.1 + * @access public + * + * @param string $table Table name. + * @param string $column Column name. + * @return mixed Max column length as an int. False if the column has no + * length. WP_Error object if there was an error. + */ + public function get_col_length( $table, $column ) { + $tablekey = strtolower( $table ); + $columnkey = strtolower( $column ); + + // Skip this entirely if this isn't a MySQL database. + if ( false === $this->is_mysql ) { + return false; + } + + if ( empty( $this->col_meta[ $tablekey ] ) ) { + // This primes column information for us. + $table_charset = $this->get_table_charset( $table ); + if ( is_wp_error( $table_charset ) ) { + return $table_charset; + } + } + + if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { + return false; + } + + $typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type ); + + $type = strtolower( $typeinfo[0] ); + if ( ! empty( $typeinfo[1] ) ) { + $length = trim( $typeinfo[1], ')' ); + } else { + $length = false; + } + + switch( $type ) { + case 'binary': + case 'char': + case 'varbinary': + case 'varchar': + return $length; + break; + case 'tinyblob': + case 'tinytext': + return 255; // 2^8 - 1 + break; + case 'blob': + case 'text': + return 65535; // 2^16 - 1 + break; + case 'mediumblob': + case 'mediumtext': + return 16777215; // 2^24 - 1 + break; + case 'longblob': + case 'longtext': + return 4294967295; // 2^32 - 1 + break; + default: + return false; + } + + return false; + } + /** * Check if a string is ASCII. * diff --git a/tests/phpunit/tests/comment.php b/tests/phpunit/tests/comment.php index ff3cc1fc2d..3905f9f461 100644 --- a/tests/phpunit/tests/comment.php +++ b/tests/phpunit/tests/comment.php @@ -112,4 +112,30 @@ class Tests_Comment extends WP_UnitTestCase { unset( $_SERVER['REMOTE_ADDR'] ); } } + + public function test_comment_field_lengths() { + // `wp_new_comment()` checks REMOTE_ADDR, so we fake it to avoid PHP notices. + if ( isset( $_SERVER['REMOTE_ADDR'] ) ) { + $remote_addr = $_SERVER['REMOTE_ADDR']; + } else { + $_SERVER['REMOTE_ADDR'] = ''; + } + + $post_id = $this->factory->post->create(); + + $data = array( + 'comment_post_ID' => $post_id, + 'comment_author' => rand_str(), + 'comment_author_url' => '', + 'comment_author_email' => '', + 'comment_type' => '', + 'comment_content' => str_repeat( 'A', 65536 ), + 'comment_date' => '2011-01-01 10:00:00', + 'comment_date_gmt' => '2011-01-01 10:00:00', + ); + + $id = wp_new_comment( $data ); + + $this->assertFalse( $id ); + } } diff --git a/tests/phpunit/tests/db.php b/tests/phpunit/tests/db.php index 5662d4fefe..1df898756c 100644 --- a/tests/phpunit/tests/db.php +++ b/tests/phpunit/tests/db.php @@ -747,6 +747,7 @@ class Tests_DB extends WP_UnitTestCase { 'format' => '%s', 'charset' => $expected_charset, 'ascii' => false, + 'length' => $wpdb->get_col_length( $wpdb->posts, 'post_content' ), ) );