Make WordPress Core

Ticket #41925: 41925.patch

File 41925.patch, 5.7 KB (added by soulseekah, 8 years ago)

First attempt at making wpdb::prepare great again

  • src/wp-includes/wp-db.php

     
    12481248                        }
    12491249                }
    12501250
    1251                 $query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it
    1252                 $query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting
    1253                 $query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware
    1254                 $query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s
    1255                 $query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query ); // escape any unescaped percents
     1251                $query = preg_replace( '#[\'"]%(\d+\$)?s[\'"]#', '%$1s', $query ); // Remove mistakenly single/doublequoted placeholders
     1252
     1253                // Add single quotes around placeholders, but only for an odd number of leading % signs.
     1254                // Force floats to be locale unaware, but only for an odd number of leading % signs.
     1255                $query = preg_replace_callback( '#(%+)(\d+\$)?([fs])#', array( $this, '_normalize_prepare_placehoders' ), $query );
     1256
    12561257                array_walk( $args, array( $this, 'escape_by_ref' ) );
    12571258                return @vsprintf( $query, $args );
    12581259        }
    12591260
    12601261        /**
     1262         * Make sure the %f and %s placeholders are prepared correctly.
     1263         *
     1264         * Apply single quotes around an %s placeholder.
     1265         * Make the %f placeholder locale-unaware.
     1266         *
     1267         * Make sure we only do this around the actual placeholder
     1268         *  disregarding any of the leading % signs where available.
     1269         *
     1270         * @since 4.8.2
     1271         * @see wpdb::prepare
     1272         *
     1273         * @param array $matches The matches from within the replace callback above.
     1274         * @return string
     1275         */
     1276        public function _normalize_prepare_placehoders( $matches ) {
     1277                // Just to make sure we received the needed stuff.
     1278                if ( ! is_array( $matches ) || empty( $matches ) ) {
     1279                        return '';
     1280                }
     1281
     1282                $placeholder = $matches[0];
     1283
     1284                // Return as is, not sure what to normalize here.
     1285                if ( count( $matches ) < 3 ) {
     1286                        return $placeholder;
     1287                }
     1288
     1289                // The number of leading % signs.
     1290                $npercent = strlen( $matches[1] );
     1291
     1292                // An even number of leading % signs renders the placeholder inert.
     1293                if ( $npercent % 2 == 0 ) {
     1294                        return $placeholder;
     1295                }
     1296
     1297                switch ( $matches[3] ) {
     1298                        case 'f':
     1299                                // Replace just the needed part.
     1300                                return str_replace( 'f', 'F', $placeholder );
     1301                        case 's':
     1302                                // Quote just the needed part.
     1303                                return substr( $placeholder, 0, $npercent - 1 ) . "'" . substr( $placeholder, $npercent - 1 ) . "'";
     1304                        default:
     1305                                return $placeholder;
     1306                }
     1307        }
     1308
     1309        /**
    12611310         * First half of escaping for LIKE special characters % and _ before preparing for MySQL.
    12621311         *
    12631312         * Use this only before wpdb::prepare() or esc_sql().  Reversing the order is very bad for security.
  • tests/phpunit/tests/db.php

     
    11181118        }
    11191119
    11201120        /**
    1121          *
     1121         * @ticket 41925
    11221122         */
    1123         function test_prepare_with_unescaped_percents() {
     1123        function test_prepare_with_numbered_parameters_and_leading_escapes() {
    11241124                global $wpdb;
    11251125
    1126                 $sql = $wpdb->prepare( '%d %1$d %%% %', 1 );
    1127                 $this->assertEquals( '1 %1$d %% %', $sql );
     1126                $sql = $wpdb->prepare( '%1$d %%% % %%1$d%% %%%1$d%%', 1 );
     1127                $this->assertEquals( '1 %% %1$d% %1%', $sql );
     1128
     1129                $sql = $wpdb->prepare( '%d %2$s', 1, 'hello' );
     1130                $this->assertEquals( "1 'hello'", $sql );
     1131
     1132                $sql = $wpdb->prepare( "'%s'", 'hello' );
     1133                $this->assertEquals( "'hello'", $sql );
     1134
     1135                $sql = $wpdb->prepare( '"%s"', 'hello' );
     1136                $this->assertEquals( "'hello'", $sql );
     1137
     1138                $sql = $wpdb->prepare( "%s '%1\$s'", 'hello' );
     1139                $this->assertEquals( "'hello' 'hello'", $sql );
     1140
     1141                $sql = $wpdb->prepare( "%s '%1\$s'", 'hello' );
     1142                $this->assertEquals( "'hello' 'hello'", $sql );
     1143
     1144                $sql = $wpdb->prepare( '%s "%1$s"', 'hello' );
     1145                $this->assertEquals( "'hello' 'hello'", $sql );
     1146
     1147                $sql = $wpdb->prepare( "%%s %%'%1\$s'", 'hello' );
     1148                $this->assertEquals( "%s %'hello'", $sql );
     1149
     1150                $sql = $wpdb->prepare( '%%s %%"%1$s"', 'hello' );
     1151                $this->assertEquals( "%s %'hello'", $sql );
     1152
     1153                $sql = $wpdb->prepare( '%%f %%"%1$f"', 3 );
     1154                $this->assertEquals( '%f %"3.000000"', $sql );
    11281155        }
     1156
     1157        function test_placeholder_normalization() {
     1158                global $wpdb;
     1159
     1160                $prepare_pattern = '#(%+)(\d+\$)?([fs])#';
     1161
     1162                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%s' );
     1163                $this->assertEquals( "'%s'", $sql );
     1164
     1165                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%%s' );
     1166                $this->assertEquals( "%%s", $sql );
     1167
     1168                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%%%s' );
     1169                $this->assertEquals( "%%'%s'", $sql );
     1170
     1171                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%f' );
     1172                $this->assertEquals( '%F', $sql );
     1173
     1174                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%%f' );
     1175                $this->assertEquals( '%%f', $sql );
     1176
     1177                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%%%f' );
     1178                $this->assertEquals( '%%%F', $sql );
     1179
     1180                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%%d' );
     1181                $this->assertEquals( '%%d', $sql );
     1182
     1183                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), '%%%d' );
     1184                $this->assertEquals( '%%%d', $sql );
     1185
     1186                $sql = preg_replace_callback( $prepare_pattern, array( $wpdb, '_normalize_prepare_placehoders' ), 'nothing to see here' );
     1187                $this->assertEquals( 'nothing to see here', $sql );
     1188        }
    11291189}