Make WordPress Core


Ignore:
Timestamp:
05/06/2015 07:06:02 PM (8 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 4.1 branch.

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

See #32165.

File:
1 edited

Legend:

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

    r32307 r32387  
    17901790     */
    17911791    function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
     1792        $this->insert_id = 0;
     1793
    17921794        if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
    17931795            return false;
     
    18101812        $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
    18111813
    1812         $this->insert_id = 0;
    18131814        $this->check_current_query = false;
    18141815        return $this->query( $this->prepare( $sql, $values ) );
     
    20022003                // This checks %d/%f versus ! %s because it's sprintf() could take more.
    20032004                $value['charset'] = false;
    2004             } elseif ( $this->check_ascii( $value['value'] ) ) {
    2005                 // If it's ASCII, then we don't need the charset. We can skip this field.
    2006                 $value['charset'] = false;
    20072005            } else {
    20082006                $value['charset'] = $this->get_col_charset( $table, $field );
     
    20102008                    return false;
    20112009                }
    2012 
    2013                 // This isn't ASCII. Don't have strip_invalid_text() re-check.
    2014                 $value['ascii'] = false;
    20152010            }
    20162011
     
    20432038                    return false;
    20442039                }
    2045             }
    2046 
    2047             if ( false !== $value['length'] && strlen( $value['value'] ) > $value['length'] ) {
    2048                 return false;
    20492040            }
    20502041
     
    23812372    /**
    23822373     * Retrieve the maximum string length allowed in a given column.
     2374     * The length may either be specified as a byte length or a character length.
    23832375     *
    23842376     * @since 4.2.1
     
    23872379     * @param string $table  Table name.
    23882380     * @param string $column Column name.
    2389      * @return mixed Max column length as an int. False if the column has no
    2390      *               length. WP_Error object if there was an error.
     2381     * @return mixed array( 'length' => (int), 'type' => 'byte' | 'char' )
     2382     *               false if the column has no length (for example, numeric column)
     2383     *               WP_Error object if there was an error.
    23912384     */
    23922385    public function get_col_length( $table, $column ) {
     
    24212414
    24222415        switch( $type ) {
     2416            case 'char':
     2417            case 'varchar':
     2418                return array(
     2419                    'type'   => 'char',
     2420                    'length' => (int) $length,
     2421                );
     2422                break;
    24232423            case 'binary':
    2424             case 'char':
    24252424            case 'varbinary':
    2426             case 'varchar':
    2427                 return $length;
     2425                return array(
     2426                    'type'   => 'byte',
     2427                    'length' => (int) $length,
     2428                );
    24282429                break;
    24292430            case 'tinyblob':
    24302431            case 'tinytext':
    2431                 return 255; // 2^8 - 1
     2432                return array(
     2433                    'type'   => 'byte',
     2434                    'length' => 255,        // 2^8 - 1
     2435                );
    24322436                break;
    24332437            case 'blob':
    24342438            case 'text':
    2435                 return 65535; // 2^16 - 1
     2439                return array(
     2440                    'type'   => 'byte',
     2441                    'length' => 65535,      // 2^16 - 1
     2442                );
    24362443                break;
    24372444            case 'mediumblob':
    24382445            case 'mediumtext':
    2439                 return 16777215; // 2^24 - 1
     2446                return array(
     2447                    'type'   => 'byte',
     2448                    'length' => 16777215,   // 2^24 - 1
     2449                );
    24402450                break;
    24412451            case 'longblob':
    24422452            case 'longtext':
    2443                 return 4294967295; // 2^32 - 1
     2453                return array(
     2454                    'type'   => 'byte',
     2455                    'length' => 4294967295, // 2^32 - 1
     2456                );
    24442457                break;
    24452458            default:
     
    25482561        // If any of the columns don't have one of these collations, it needs more sanity checking.
    25492562    protected function strip_invalid_text( $data ) {
    2550         // Some multibyte character sets that we can check in PHP.
    2551         $mb_charsets = array(
    2552             'ascii'   => 'ASCII',
    2553             'big5'    => 'BIG-5',
    2554             'eucjpms' => 'eucJP-win',
    2555             'gb2312'  => 'EUC-CN',
    2556             'ujis'    => 'EUC-JP',
    2557             'utf32'   => 'UTF-32',
    2558         );
    2559 
    2560         $supported_charsets = array();
    2561         if ( function_exists( 'mb_list_encodings' ) ) {
    2562             $supported_charsets = mb_list_encodings();
    2563         }
    2564 
    25652563        $db_check_string = false;
    25662564
     
    25682566            $charset = $value['charset'];
    25692567
    2570             // Column isn't a string, or is latin1, which will will happily store anything.
    2571             if ( false === $charset || 'latin1' === $charset ) {
     2568            if ( is_array( $value['length'] ) ) {
     2569                $length = $value['length']['length'];
     2570            } else {
     2571                $length = false;
     2572            }
     2573
     2574            // There's no charset to work with.
     2575            if ( false === $charset ) {
    25722576                continue;
    25732577            }
    25742578
     2579            // Column isn't a string.
    25752580            if ( ! is_string( $value['value'] ) ) {
    25762581                continue;
    25772582            }
    25782583
    2579             // ASCII is always OK.
    2580             if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) {
    2581                 continue;
    2582             }
    2583 
    2584             // Convert the text locally.
    2585             if ( $supported_charsets ) {
    2586                 if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
    2587                     $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
     2584            $truncate_by_byte_length = 'byte' === $value['length']['type'];
     2585
     2586            $needs_validation = true;
     2587            if (
     2588                // latin1 can store any byte sequence
     2589                'latin1' === $charset
     2590            ||
     2591                // ASCII is always OK.
     2592                ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
     2593            ) {
     2594                $truncate_by_byte_length = true;
     2595                $needs_validation = false;
     2596            }
     2597
     2598            if ( $truncate_by_byte_length ) {
     2599                mbstring_binary_safe_encoding();
     2600                if ( false !== $length && strlen( $value['value'] ) > $length ) {
     2601                    $value['value'] = substr( $value['value'], 0, $length );
     2602                }
     2603                reset_mbstring_encoding();
     2604
     2605                if ( ! $needs_validation ) {
    25882606                    continue;
    25892607                }
     
    25912609
    25922610            // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
    2593             if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
     2611            if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
    25942612                $regex = '/
    25952613                    (
     
    26012619                        |   [\xEE-\xEF][\x80-\xBF]{2}';
    26022620
    2603                 if ( 'utf8mb4' === $charset) {
     2621                if ( 'utf8mb4' === $charset ) {
    26042622                    $regex .= '
    26052623                        |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
     
    26142632                    /x';
    26152633                $value['value'] = preg_replace( $regex, '$1', $value['value'] );
     2634
     2635
     2636                if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
     2637                    $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
     2638                }
    26162639                continue;
    26172640            }
     
    26302653                    }
    26312654
    2632                     // Split the CONVERT() calls by charset, so we can make sure the connection is right
    2633                     $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
     2655                    // We're going to need to truncate by characters or bytes, depending on the length value we have.
     2656                    if ( 'byte' === $value['length']['type'] ) {
     2657                        // Split the CONVERT() calls by charset, so we can make sure the connection is right
     2658                        $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING binary ), %d ) USING {$value['charset']} )", $value['value'], $value['length']['length'] );
     2659                    } else {
     2660                        $queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] );
     2661                    }
     2662
    26342663                    unset( $data[ $col ]['db'] );
    26352664                }
     
    26502679                $this->check_current_query = false;
    26512680
    2652                 $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
     2681                $sql = array();
     2682                foreach ( $query as $column => $column_query ) {
     2683                    $sql[] = $column_query . " AS x_$column";
     2684                }
     2685
     2686                $row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A );
    26532687                if ( ! $row ) {
    26542688                    $this->set_charset( $this->dbh, $connection_charset );
     
    26562690                }
    26572691
    2658                 $cols = array_keys( $query );
    2659                 $col_count = count( $cols );
    2660                 for ( $ii = 0; $ii < $col_count; $ii++ ) {
    2661                     $data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
     2692                foreach ( array_keys( $query ) as $column ) {
     2693                    $data[ $column ]['value'] = $row["x_$column"];
    26622694                }
    26632695            }
     
    27012733            'charset' => $charset,
    27022734            'ascii'   => false,
     2735            'length'  => false,
    27032736        );
    27042737
     
    27232756     */
    27242757    public function strip_invalid_text_for_column( $table, $column, $value ) {
    2725         if ( ! is_string( $value ) || $this->check_ascii( $value ) ) {
     2758        if ( ! is_string( $value ) ) {
    27262759            return $value;
    27272760        }
     
    27402773                'value'   => $value,
    27412774                'charset' => $charset,
    2742                 'ascii'   => false,
     2775                'length'  => $this->get_col_length( $table, $column ),
    27432776            )
    27442777        );
Note: See TracChangeset for help on using the changeset viewer.