Make WordPress Core


Ignore:
Timestamp:
05/06/2015 07:11:47 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.9 branch.

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

See #32165.

File:
1 edited

Legend:

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

    r32316 r32389  
    17521752         */
    17531753        function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
     1754                $this->insert_id = 0;
     1755
    17541756                if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
    17551757                        return false;
     
    17721774                $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
    17731775
    1774                 $this->insert_id = 0;
    17751776                $this->check_current_query = false;
    17761777                return $this->query( $this->prepare( $sql, $values ) );
     
    19691970                                // This checks %d/%f versus ! %s because it's sprintf() could take more.
    19701971                                $value['charset'] = false;
    1971                         } elseif ( $this->check_ascii( $value['value'] ) ) {
    1972                                 // If it's ASCII, then we don't need the charset. We can skip this field.
    1973                                 $value['charset'] = false;
    19741972                        } else {
    19751973                                $value['charset'] = $this->get_col_charset( $table, $field );
     
    19771975                                        return false;
    19781976                                }
    1979 
    1980                                 // This isn't ASCII. Don't have strip_invalid_text() re-check.
    1981                                 $value['ascii'] = false;
    19821977                        }
    19831978
     
    20102005                                        return false;
    20112006                                }
    2012                         }
    2013 
    2014                         if ( false !== $value['length'] && strlen( $value['value'] ) > $value['length'] ) {
    2015                                 return false;
    20162007                        }
    20172008
     
    23442335        /**
    23452336         * Retrieve the maximum string length allowed in a given column.
     2337         * The length may either be specified as a byte length or a character length.
    23462338         *
    23472339         * @since 4.2.1
     
    23502342         * @param string $table  Table name.
    23512343         * @param string $column Column name.
    2352          * @return mixed Max column length as an int. False if the column has no
    2353          *               length. WP_Error object if there was an error.
     2344         * @return mixed array( 'length' => (int), 'type' => 'byte' | 'char' )
     2345         *               false if the column has no length (for example, numeric column)
     2346         *               WP_Error object if there was an error.
    23542347         */
    23552348        public function get_col_length( $table, $column ) {
     
    23842377
    23852378                switch( $type ) {
     2379                        case 'char':
     2380                        case 'varchar':
     2381                                return array(
     2382                                        'type'   => 'char',
     2383                                        'length' => (int) $length,
     2384                                );
     2385                                break;
    23862386                        case 'binary':
    2387                         case 'char':
    23882387                        case 'varbinary':
    2389                         case 'varchar':
    2390                                 return $length;
     2388                                return array(
     2389                                        'type'   => 'byte',
     2390                                        'length' => (int) $length,
     2391                                );
    23912392                                break;
    23922393                        case 'tinyblob':
    23932394                        case 'tinytext':
    2394                                 return 255; // 2^8 - 1
     2395                                return array(
     2396                                        'type'   => 'byte',
     2397                                        'length' => 255,        // 2^8 - 1
     2398                                );
    23952399                                break;
    23962400                        case 'blob':
    23972401                        case 'text':
    2398                                 return 65535; // 2^16 - 1
     2402                                return array(
     2403                                        'type'   => 'byte',
     2404                                        'length' => 65535,      // 2^16 - 1
     2405                                );
    23992406                                break;
    24002407                        case 'mediumblob':
    24012408                        case 'mediumtext':
    2402                                 return 16777215; // 2^24 - 1
     2409                                return array(
     2410                                        'type'   => 'byte',
     2411                                        'length' => 16777215,   // 2^24 - 1
     2412                                );
    24032413                                break;
    24042414                        case 'longblob':
    24052415                        case 'longtext':
    2406                                 return 4294967295; // 2^32 - 1
     2416                                return array(
     2417                                        'type'   => 'byte',
     2418                                        'length' => 4294967295, // 2^32 - 1
     2419                                );
    24072420                                break;
    24082421                        default:
     
    25112524                // If any of the columns don't have one of these collations, it needs more sanity checking.
    25122525        protected function strip_invalid_text( $data ) {
    2513                 // Some multibyte character sets that we can check in PHP.
    2514                 $mb_charsets = array(
    2515                         'ascii'   => 'ASCII',
    2516                         'big5'    => 'BIG-5',
    2517                         'eucjpms' => 'eucJP-win',
    2518                         'gb2312'  => 'EUC-CN',
    2519                         'ujis'    => 'EUC-JP',
    2520                         'utf32'   => 'UTF-32',
    2521                 );
    2522 
    2523                 $supported_charsets = array();
    2524                 if ( function_exists( 'mb_list_encodings' ) ) {
    2525                         $supported_charsets = mb_list_encodings();
    2526                 }
    2527 
    25282526                $db_check_string = false;
    25292527
     
    25312529                        $charset = $value['charset'];
    25322530
    2533                         // Column isn't a string, or is latin1, which will will happily store anything.
    2534                         if ( false === $charset || 'latin1' === $charset ) {
     2531                        if ( is_array( $value['length'] ) ) {
     2532                                $length = $value['length']['length'];
     2533                        } else {
     2534                                $length = false;
     2535                        }
     2536
     2537                        // There's no charset to work with.
     2538                        if ( false === $charset ) {
    25352539                                continue;
    25362540                        }
    25372541
     2542                        // Column isn't a string.
    25382543                        if ( ! is_string( $value['value'] ) ) {
    25392544                                continue;
    25402545                        }
    25412546
    2542                         // ASCII is always OK.
    2543                         if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) {
    2544                                 continue;
    2545                         }
    2546 
    2547                         // Convert the text locally.
    2548                         if ( $supported_charsets ) {
    2549                                 if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
    2550                                         $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
     2547                        $truncate_by_byte_length = 'byte' === $value['length']['type'];
     2548
     2549                        $needs_validation = true;
     2550                        if (
     2551                                // latin1 can store any byte sequence
     2552                                'latin1' === $charset
     2553                        ||
     2554                                // ASCII is always OK.
     2555                                ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
     2556                        ) {
     2557                                $truncate_by_byte_length = true;
     2558                                $needs_validation = false;
     2559                        }
     2560
     2561                        if ( $truncate_by_byte_length ) {
     2562                                mbstring_binary_safe_encoding();
     2563                                if ( false !== $length && strlen( $value['value'] ) > $length ) {
     2564                                        $value['value'] = substr( $value['value'], 0, $length );
     2565                                }
     2566                                reset_mbstring_encoding();
     2567
     2568                                if ( ! $needs_validation ) {
    25512569                                        continue;
    25522570                                }
     
    25542572
    25552573                        // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
    2556                         if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
     2574                        if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
    25572575                                $regex = '/
    25582576                                        (
     
    25642582                                                |   [\xEE-\xEF][\x80-\xBF]{2}';
    25652583
    2566                                 if ( 'utf8mb4' === $charset) {
     2584                                if ( 'utf8mb4' === $charset ) {
    25672585                                        $regex .= '
    25682586                                                |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
     
    25772595                                        /x';
    25782596                                $value['value'] = preg_replace( $regex, '$1', $value['value'] );
     2597
     2598
     2599                                if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
     2600                                        $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
     2601                                }
    25792602                                continue;
    25802603                        }
     
    25932616                                        }
    25942617
    2595                                         // Split the CONVERT() calls by charset, so we can make sure the connection is right
    2596                                         $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
     2618                                        // We're going to need to truncate by characters or bytes, depending on the length value we have.
     2619                                        if ( 'byte' === $value['length']['type'] ) {
     2620                                                // Split the CONVERT() calls by charset, so we can make sure the connection is right
     2621                                                $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING binary ), %d ) USING {$value['charset']} )", $value['value'], $value['length']['length'] );
     2622                                        } else {
     2623                                                $queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] );
     2624                                        }
     2625
    25972626                                        unset( $data[ $col ]['db'] );
    25982627                                }
     
    26132642                                $this->check_current_query = false;
    26142643
    2615                                 $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
     2644                                $sql = array();
     2645                                foreach ( $query as $column => $column_query ) {
     2646                                        $sql[] = $column_query . " AS x_$column";
     2647                                }
     2648
     2649                                $row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A );
    26162650                                if ( ! $row ) {
    26172651                                        $this->set_charset( $this->dbh, $connection_charset );
     
    26192653                                }
    26202654
    2621                                 $cols = array_keys( $query );
    2622                                 $col_count = count( $cols );
    2623                                 for ( $ii = 0; $ii < $col_count; $ii++ ) {
    2624                                         $data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
     2655                                foreach ( array_keys( $query ) as $column ) {
     2656                                        $data[ $column ]['value'] = $row["x_$column"];
    26252657                                }
    26262658                        }
     
    26642696                        'charset' => $charset,
    26652697                        'ascii'   => false,
     2698                        'length'  => false,
    26662699                );
    26672700
     
    26862719         */
    26872720        public function strip_invalid_text_for_column( $table, $column, $value ) {
    2688                 if ( ! is_string( $value ) || $this->check_ascii( $value ) ) {
     2721                if ( ! is_string( $value ) ) {
    26892722                        return $value;
    26902723                }
     
    27032736                                'value'   => $value,
    27042737                                'charset' => $charset,
    2705                                 'ascii'   => false,
     2738                                'length'  => $this->get_col_length( $table, $column ),
    27062739                        )
    27072740                );
Note: See TracChangeset for help on using the changeset viewer.