Make WordPress Core


Ignore:
Timestamp:
05/06/2015 02:59:50 AM (11 years ago)
Author:
pento
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.

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

See #32165.

File:
1 edited

Legend:

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

    r32306 r32364  
    18101810         */
    18111811        function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
     1812                $this->insert_id = 0;
     1813
    18121814                if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
    18131815                        return false;
     
    18301832                $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
    18311833
    1832                 $this->insert_id = 0;
    18331834                $this->check_current_query = false;
    18341835                return $this->query( $this->prepare( $sql, $values ) );
     
    20222023                                // This checks %d/%f versus ! %s because it's sprintf() could take more.
    20232024                                $value['charset'] = false;
    2024                         } elseif ( $this->check_ascii( $value['value'] ) ) {
    2025                                 // If it's ASCII, then we don't need the charset. We can skip this field.
    2026                                 $value['charset'] = false;
    20272025                        } else {
    20282026                                $value['charset'] = $this->get_col_charset( $table, $field );
     
    20302028                                        return false;
    20312029                                }
    2032 
    2033                                 // This isn't ASCII. Don't have strip_invalid_text() re-check.
    2034                                 $value['ascii'] = false;
    20352030                        }
    20362031
     
    20632058                                        return false;
    20642059                                }
    2065                         }
    2066 
    2067                         if ( false !== $value['length'] && mb_strlen( $value['value'] ) > $value['length'] ) {
    2068                                 return false;
    20692060                        }
    20702061
     
    24072398        /**
    24082399         * Retrieve the maximum string length allowed in a given column.
     2400         * The length may either be specified as a byte length or a character length.
    24092401         *
    24102402         * @since 4.2.1
     
    24132405         * @param string $table  Table name.
    24142406         * @param string $column Column name.
    2415          * @return mixed Max column length as an int. False if the column has no
    2416          *               length. WP_Error object if there was an error.
     2407         * @return mixed array( 'length' => (int), 'type' => 'byte' | 'char' )
     2408         *               false if the column has no length (for example, numeric column)
     2409         *               WP_Error object if there was an error.
    24172410         */
    24182411        public function get_col_length( $table, $column ) {
     
    24472440
    24482441                switch( $type ) {
     2442                        case 'char':
     2443                        case 'varchar':
     2444                                return array(
     2445                                        'type'   => 'char',
     2446                                        'length' => (int) $length,
     2447                                );
     2448                                break;
    24492449                        case 'binary':
    2450                         case 'char':
    24512450                        case 'varbinary':
    2452                         case 'varchar':
    2453                                 return $length;
     2451                                return array(
     2452                                        'type'   => 'byte',
     2453                                        'length' => (int) $length,
     2454                                );
    24542455                                break;
    24552456                        case 'tinyblob':
    24562457                        case 'tinytext':
    2457                                 return 255; // 2^8 - 1
     2458                                return array(
     2459                                        'type'   => 'byte',
     2460                                        'length' => 255,        // 2^8 - 1
     2461                                );
    24582462                                break;
    24592463                        case 'blob':
    24602464                        case 'text':
    2461                                 return 65535; // 2^16 - 1
     2465                                return array(
     2466                                        'type'   => 'byte',
     2467                                        'length' => 65535,      // 2^16 - 1
     2468                                );
    24622469                                break;
    24632470                        case 'mediumblob':
    24642471                        case 'mediumtext':
    2465                                 return 16777215; // 2^24 - 1
     2472                                return array(
     2473                                        'type'   => 'byte',
     2474                                        'length' => 16777215,   // 2^24 - 1
     2475                                );
    24662476                                break;
    24672477                        case 'longblob':
    24682478                        case 'longtext':
    2469                                 return 4294967295; // 2^32 - 1
     2479                                return array(
     2480                                        'type'   => 'byte',
     2481                                        'length' => 4294967295, // 2^32 - 1
     2482                                );
    24702483                                break;
    24712484                        default:
     
    25732586         */
    25742587        protected function strip_invalid_text( $data ) {
    2575                 // Some multibyte character sets that we can check in PHP.
    2576                 $mb_charsets = array(
    2577                         'ascii'   => 'ASCII',
    2578                         'big5'    => 'BIG-5',
    2579                         'eucjpms' => 'eucJP-win',
    2580                         'gb2312'  => 'EUC-CN',
    2581                         'ujis'    => 'EUC-JP',
    2582                         'utf32'   => 'UTF-32',
    2583                 );
    2584 
    2585                 $supported_charsets = array();
    2586                 if ( function_exists( 'mb_list_encodings' ) ) {
    2587                         $supported_charsets = mb_list_encodings();
    2588                 }
    2589 
    25902588                $db_check_string = false;
    25912589
     
    25932591                        $charset = $value['charset'];
    25942592
    2595                         // Column isn't a string, or is latin1, which will will happily store anything.
    2596                         if ( false === $charset || 'latin1' === $charset ) {
     2593                        if ( is_array( $value['length'] ) ) {
     2594                                $length = $value['length']['length'];
     2595                        } else {
     2596                                $length = false;
     2597                        }
     2598
     2599                        // There's no charset to work with.
     2600                        if ( false === $charset ) {
    25972601                                continue;
    25982602                        }
    25992603
     2604                        // Column isn't a string.
    26002605                        if ( ! is_string( $value['value'] ) ) {
    26012606                                continue;
    26022607                        }
    26032608
    2604                         // ASCII is always OK.
    2605                         if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) {
    2606                                 continue;
    2607                         }
    2608 
    2609                         // Convert the text locally.
    2610                         if ( $supported_charsets ) {
    2611                                 if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
    2612                                         $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
     2609                        $truncate_by_byte_length = 'byte' === $value['length']['type'];
     2610
     2611                        $needs_validation = true;
     2612                        if (
     2613                                // latin1 can store any byte sequence
     2614                                'latin1' === $charset
     2615                        ||
     2616                                // ASCII is always OK.
     2617                                ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
     2618                        ) {
     2619                                $truncate_by_byte_length = true;
     2620                                $needs_validation = false;
     2621                        }
     2622
     2623                        if ( $truncate_by_byte_length ) {
     2624                                mbstring_binary_safe_encoding();
     2625                                if ( false !== $length && strlen( $value['value'] ) > $length ) {
     2626                                        $value['value'] = substr( $value['value'], 0, $length );
     2627                                }
     2628                                reset_mbstring_encoding();
     2629
     2630                                if ( ! $needs_validation ) {
    26132631                                        continue;
    26142632                                }
     
    26162634
    26172635                        // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
    2618                         if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
     2636                        if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
    26192637                                $regex = '/
    26202638                                        (
     
    26262644                                                |   [\xEE-\xEF][\x80-\xBF]{2}';
    26272645
    2628                                 if ( 'utf8mb4' === $charset) {
     2646                                if ( 'utf8mb4' === $charset ) {
    26292647                                        $regex .= '
    26302648                                                |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
     
    26392657                                        /x';
    26402658                                $value['value'] = preg_replace( $regex, '$1', $value['value'] );
     2659
     2660
     2661                                if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
     2662                                        $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
     2663                                }
    26412664                                continue;
    26422665                        }
     
    26552678                                        }
    26562679
    2657                                         // Split the CONVERT() calls by charset, so we can make sure the connection is right
    2658                                         $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
     2680                                        // We're going to need to truncate by characters or bytes, depending on the length value we have.
     2681                                        if ( 'byte' === $value['length']['type'] ) {
     2682                                                // Split the CONVERT() calls by charset, so we can make sure the connection is right
     2683                                                $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING binary ), %d ) USING {$value['charset']} )", $value['value'], $value['length']['length'] );
     2684                                        } else {
     2685                                                $queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] );
     2686                                        }
     2687
    26592688                                        unset( $data[ $col ]['db'] );
    26602689                                }
     
    26752704                                $this->check_current_query = false;
    26762705
    2677                                 $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
     2706                                $sql = array();
     2707                                foreach ( $query as $column => $column_query ) {
     2708                                        $sql[] = $column_query . " AS x_$column";
     2709                                }
     2710
     2711                                $row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A );
    26782712                                if ( ! $row ) {
    26792713                                        $this->set_charset( $this->dbh, $connection_charset );
     
    26812715                                }
    26822716
    2683                                 $cols = array_keys( $query );
    2684                                 $col_count = count( $cols );
    2685                                 for ( $ii = 0; $ii < $col_count; $ii++ ) {
    2686                                         $data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
     2717                                foreach ( array_keys( $query ) as $column ) {
     2718                                        $data[ $column ]['value'] = $row["x_$column"];
    26872719                                }
    26882720                        }
     
    27262758                        'charset' => $charset,
    27272759                        'ascii'   => false,
     2760                        'length'  => false,
    27282761                );
    27292762
     
    27482781         */
    27492782        public function strip_invalid_text_for_column( $table, $column, $value ) {
    2750                 if ( ! is_string( $value ) || $this->check_ascii( $value ) ) {
     2783                if ( ! is_string( $value ) ) {
    27512784                        return $value;
    27522785                }
     
    27652798                                'value'   => $value,
    27662799                                'charset' => $charset,
    2767                                 'ascii'   => false,
     2800                                'length'  => $this->get_col_length( $table, $column ),
    27682801                        )
    27692802                );
Note: See TracChangeset for help on using the changeset viewer.