Make WordPress Core


Ignore:
Timestamp:
05/06/2015 07:16:41 PM (11 years ago)
Author:
mdawaffe
Message:

WPDB: When checking that a string can be sent to MySQL, we shouldn't use mb_convert_encoding(), as it behaves differently to MySQL's character encoding conversion.

Merge of [32364] to the 3.7 branch.

Props mdawaffe, pento, nbachiyski, jorbin, johnjamesjacoby, jeremyfelt.

See #32165.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/3.7/src/wp-includes/wp-db.php

    r32318 r32391  
    13721372         */
    13731373        function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
     1374                $this->insert_id = 0;
     1375
    13741376                if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
    13751377                        return false;
    … …  
    13921394                $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
    13931395
    1394                 $this->insert_id = 0;
    13951396                $this->check_current_query = false;
    13961397                return $this->query( $this->prepare( $sql, $values ) );
    … …  
    15891590                                // This checks %d/%f versus ! %s because it's sprintf() could take more.
    15901591                                $value['charset'] = false;
    1591                         } elseif ( $this->check_ascii( $value['value'] ) ) {
    1592                                 // If it's ASCII, then we don't need the charset. We can skip this field.
    1593                                 $value['charset'] = false;
    15941592                        } else {
    15951593                                $value['charset'] = $this->get_col_charset( $table, $field );
    … …  
    15971595                                        return false;
    15981596                                }
    1599 
    1600                                 // This isn't ASCII. Don't have strip_invalid_text() re-check.
    1601                                 $value['ascii'] = false;
    16021597                        }
    16031598
    … …  
    16301625                                        return false;
    16311626                                }
    1632                         }
    1633 
    1634                         if ( false !== $value['length'] && strlen( $value['value'] ) > $value['length'] ) {
    1635                                 return false;
    16361627                        }
    16371628
    … …  
    19591950        /**
    19601951         * Retrieve the maximum string length allowed in a given column.
     1952         * The length may either be specified as a byte length or a character length.
    19611953         *
    19621954         * @since 4.2.1
    … …  
    19651957         * @param string $table  Table name.
    19661958         * @param string $column Column name.
    1967          * @return mixed Max column length as an int. False if the column has no
    1968          *               length. WP_Error object if there was an error.
     1959         * @return mixed array( 'length' => (int), 'type' => 'byte' | 'char' )
     1960         *               false if the column has no length (for example, numeric column)
     1961         *               WP_Error object if there was an error.
    19691962         */
    19701963        public function get_col_length( $table, $column ) {
    … …  
    19991992
    20001993                switch( $type ) {
     1994                        case 'char':
     1995                        case 'varchar':
     1996                                return array(
     1997                                        'type'   => 'char',
     1998                                        'length' => (int) $length,
     1999                                );
     2000                                break;
    20012001                        case 'binary':
    2002                         case 'char':
    20032002                        case 'varbinary':
    2004                         case 'varchar':
    2005                                 return $length;
     2003                                return array(
     2004                                        'type'   => 'byte',
     2005                                        'length' => (int) $length,
     2006                                );
    20062007                                break;
    20072008                        case 'tinyblob':
    20082009                        case 'tinytext':
    2009                                 return 255; // 2^8 - 1
     2010                                return array(
     2011                                        'type'   => 'byte',
     2012                                        'length' => 255,        // 2^8 - 1
     2013                                );
    20102014                                break;
    20112015                        case 'blob':
    20122016                        case 'text':
    2013                                 return 65535; // 2^16 - 1
     2017                                return array(
     2018                                        'type'   => 'byte',
     2019                                        'length' => 65535,      // 2^16 - 1
     2020                                );
    20142021                                break;
    20152022                        case 'mediumblob':
    20162023                        case 'mediumtext':
    2017                                 return 16777215; // 2^24 - 1
     2024                                return array(
     2025                                        'type'   => 'byte',
     2026                                        'length' => 16777215,   // 2^24 - 1
     2027                                );
    20182028                                break;
    20192029                        case 'longblob':
    20202030                        case 'longtext':
    2021                                 return 4294967295; // 2^32 - 1
     2031                                return array(
     2032                                        'type'   => 'byte',
     2033                                        'length' => 4294967295, // 2^32 - 1
     2034                                );
    20222035                                break;
    20232036                        default:
    … …  
    21262139                // If any of the columns don't have one of these collations, it needs more sanity checking.
    21272140        protected function strip_invalid_text( $data ) {
    2128                 // Some multibyte character sets that we can check in PHP.
    2129                 $mb_charsets = array(
    2130                         'ascii'   => 'ASCII',
    2131                         'big5'    => 'BIG-5',
    2132                         'eucjpms' => 'eucJP-win',
    2133                         'gb2312'  => 'EUC-CN',
    2134                         'ujis'    => 'EUC-JP',
    2135                         'utf32'   => 'UTF-32',
    2136                 );
    2137 
    2138                 $supported_charsets = array();
    2139                 if ( function_exists( 'mb_list_encodings' ) ) {
    2140                         $supported_charsets = mb_list_encodings();
    2141                 }
    2142 
    21432141                $db_check_string = false;
    21442142
    … …  
    21462144                        $charset = $value['charset'];
    21472145
    2148                         // Column isn't a string, or is latin1, which will will happily store anything.
    2149                         if ( false === $charset || 'latin1' === $charset ) {
     2146                        if ( is_array( $value['length'] ) ) {
     2147                                $length = $value['length']['length'];
     2148                        } else {
     2149                                $length = false;
     2150                        }
     2151
     2152                        // There's no charset to work with.
     2153                        if ( false === $charset ) {
    21502154                                continue;
    21512155                        }
    21522156
     2157                        // Column isn't a string.
    21532158                        if ( ! is_string( $value['value'] ) ) {
    21542159                                continue;
    21552160                        }
    21562161
    2157                         // ASCII is always OK.
    2158                         if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) {
    2159                                 continue;
    2160                         }
    2161 
    2162                         // Convert the text locally.
    2163                         if ( $supported_charsets ) {
    2164                                 if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
    2165                                         $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
     2162                        $truncate_by_byte_length = 'byte' === $value['length']['type'];
     2163
     2164                        $needs_validation = true;
     2165                        if (
     2166                                // latin1 can store any byte sequence
     2167                                'latin1' === $charset
     2168                        ||
     2169                                // ASCII is always OK.
     2170                                ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
     2171                        ) {
     2172                                $truncate_by_byte_length = true;
     2173                                $needs_validation = false;
     2174                        }
     2175
     2176                        if ( $truncate_by_byte_length ) {
     2177                                mbstring_binary_safe_encoding();
     2178                                if ( false !== $length && strlen( $value['value'] ) > $length ) {
     2179                                        $value['value'] = substr( $value['value'], 0, $length );
     2180                                }
     2181                                reset_mbstring_encoding();
     2182
     2183                                if ( ! $needs_validation ) {
    21662184                                        continue;
    21672185                                }
    … …  
    21692187
    21702188                        // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
    2171                         if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
     2189                        if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
    21722190                                $regex = '/
    21732191                                        (
    … …  
    21792197                                                |   [\xEE-\xEF][\x80-\xBF]{2}';
    21802198
    2181                                 if ( 'utf8mb4' === $charset) {
     2199                                if ( 'utf8mb4' === $charset ) {
    21822200                                        $regex .= '
    21832201                                                |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
    … …  
    21922210                                        /x';
    21932211                                $value['value'] = preg_replace( $regex, '$1', $value['value'] );
     2212
     2213
     2214                                if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
     2215                                        $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
     2216                                }
    21942217                                continue;
    21952218                        }
    … …  
    22082231                                        }
    22092232
    2210                                         // Split the CONVERT() calls by charset, so we can make sure the connection is right
    2211                                         $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
     2233                                        // We're going to need to truncate by characters or bytes, depending on the length value we have.
     2234                                        if ( 'byte' === $value['length']['type'] ) {
     2235                                                // Split the CONVERT() calls by charset, so we can make sure the connection is right
     2236                                                $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING binary ), %d ) USING {$value['charset']} )", $value['value'], $value['length']['length'] );
     2237                                        } else {
     2238                                                $queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] );
     2239                                        }
     2240
    22122241                                        unset( $data[ $col ]['db'] );
    22132242                                }
    … …  
    22282257                                $this->check_current_query = false;
    22292258
    2230                                 $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
     2259                                $sql = array();
     2260                                foreach ( $query as $column => $column_query ) {
     2261                                        $sql[] = $column_query . " AS x_$column";
     2262                                }
     2263
     2264                                $row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A );
    22312265                                if ( ! $row ) {
    22322266                                        $this->set_charset( $this->dbh, $connection_charset );
    … …  
    22342268                                }
    22352269
    2236                                 $cols = array_keys( $query );
    2237                                 $col_count = count( $cols );
    2238                                 for ( $ii = 0; $ii < $col_count; $ii++ ) {
    2239                                         $data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
     2270                                foreach ( array_keys( $query ) as $column ) {
     2271                                        $data[ $column ]['value'] = $row["x_$column"];
    22402272                                }
    22412273                        }
    … …  
    22792311                        'charset' => $charset,
    22802312                        'ascii'   => false,
     2313                        'length'  => false,
    22812314                );
    22822315
    … …  
    23012334         */
    23022335        public function strip_invalid_text_for_column( $table, $column, $value ) {
    2303                 if ( ! is_string( $value ) || $this->check_ascii( $value ) ) {
     2336                if ( ! is_string( $value ) ) {
    23042337                        return $value;
    23052338                }
    … …  
    23182351                                'value'   => $value,
    23192352                                'charset' => $charset,
    2320                                 'ascii'   => false,
     2353                                'length'  => $this->get_col_length( $table, $column ),
    23212354                        )
    23222355                );
Note: See TracChangeset for help on using the changeset viewer.