Make WordPress Core


Ignore:
Timestamp:
10/31/2017 12:22:07 PM (9 years ago)
Author:
pento
Message:

Database: Restore numbered placeholders in wpdb::prepare().

[41496] removed support for numbered placeholders in queries send through wpdb::prepare(), which, despite being undocumented, were quite commonly used.

This change restores support for numbered placeholders (as well as a subset of placeholder formatting), while also adding extra checks to ensure the correct number of arguments are being passed to wpdb::prepare(), given the number of placeholders.

Merges [41662], [42056] to the 4.8 branch.
See #41925.

Location:
branches/4.8
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • branches/4.8

  • branches/4.8/tests/phpunit/tests/db.php

    r41497 r42057  
    271271                global $wpdb;
    272272                $sql = $wpdb->prepare( "UPDATE test_table SET string_column = '%%f is a float, %%d is an int %d, %%s is a string', field = %s", 3, '4' );
     273                $this->assertContains( $wpdb->placeholder_escape(), $sql );
     274
     275                $sql = $wpdb->remove_placeholder_escape( $sql );
    273276                $this->assertEquals( "UPDATE test_table SET string_column = '%f is a float, %d is an int 3, %s is a string', field = '4'", $sql );
    274277        }
     
    376379        }
    377380
    378         function test_prepare_vsprintf() {
    379                 global $wpdb;
     381    function test_prepare_vsprintf() {
     382                global $wpdb;
    380383
    381384                $prepared = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", array( 1, "admin" ) );
     
    394397                $prepared = @$wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", array( array( 1 ), "admin" ) );
    395398                $this->assertEquals( "SELECT * FROM $wpdb->users WHERE id = 0 AND user_login = 'admin'", $prepared );
    396         }
     399        }
     400
     401        /**
     402         * @ticket 42040
     403         * @dataProvider data_prepare_incorrect_arg_count
     404         * @expectedIncorrectUsage wpdb::prepare
     405         */
     406        public function test_prepare_incorrect_arg_count( $query, $args, $expected ) {
     407                global $wpdb;
     408
     409                // $query is the first argument to be passed to wpdb::prepare()
     410                array_unshift( $args, $query );
     411
     412                $prepared = @call_user_func_array( array( $wpdb, 'prepare' ), $args );
     413                $this->assertEquals( $expected, $prepared );
     414        }
     415
     416        public function data_prepare_incorrect_arg_count() {
     417                global $wpdb;
     418
     419                return array(
     420                        array(
     421                                "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s",     // Query
     422                                array( 1, "admin", "extra-arg" ),                                   // ::prepare() args, to be passed via call_user_func_array
     423                                "SELECT * FROM $wpdb->users WHERE id = 1 AND user_login = 'admin'", // Expected output
     424                        ),
     425                        array(
     426                                "SELECT * FROM $wpdb->users WHERE id = %%%d AND user_login = %s",
     427                                array( 1 ),
     428                                false,
     429                        ),
     430                        array(
     431                                "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s",
     432                                array( array( 1, "admin", "extra-arg" ) ),
     433                                "SELECT * FROM $wpdb->users WHERE id = 1 AND user_login = 'admin'",
     434                        ),
     435                        array(
     436                                "SELECT * FROM $wpdb->users WHERE id = %d AND %% AND user_login = %s",
     437                                array( 1, "admin", "extra-arg" ),
     438                                "SELECT * FROM $wpdb->users WHERE id = 1 AND {$wpdb->placeholder_escape()} AND user_login = 'admin'",
     439                        ),
     440                        array(
     441                                "SELECT * FROM $wpdb->users WHERE id = %%%d AND %F AND %f AND user_login = %s",
     442                                array( 1, 2.3, "4.5", "admin", "extra-arg" ),
     443                                "SELECT * FROM $wpdb->users WHERE id = {$wpdb->placeholder_escape()}1 AND 2.300000 AND 4.500000 AND user_login = 'admin'",
     444                        ),
     445                        array(
     446                                "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s",
     447                                array( array( 1 ), "admin", "extra-arg" ),
     448                                "SELECT * FROM $wpdb->users WHERE id = 0 AND user_login = 'admin'",
     449                        ),
     450                        array(
     451                                "SELECT * FROM $wpdb->users WHERE id = %d and user_nicename = %s and user_status = %d and user_login = %s",
     452                                array( 1, "admin", 0 ),
     453                                '',
     454                        ),
     455                        array(
     456                                "SELECT * FROM $wpdb->users WHERE id = %d and user_nicename = %s and user_status = %d and user_login = %s",
     457                                array( array( 1, "admin", 0 ) ),
     458                                '',
     459                        ),
     460                        array(
     461                                "SELECT * FROM $wpdb->users WHERE id = %d and %% and user_login = %s and user_status = %d and user_login = %s",
     462                                array( 1, "admin", "extra-arg" ),
     463                                '',
     464                        ),
     465                );
     466        }
    397467
    398468        function test_db_version() {
     
    11191189
    11201190        /**
    1121          *
    1122          */
    1123         function test_prepare_with_unescaped_percents() {
    1124                 global $wpdb;
    1125 
    1126                 $sql = $wpdb->prepare( '%d %1$d %%% %', 1 );
    1127                 $this->assertEquals( '1 %1$d %% %', $sql );
     1191         * @dataProvider data_prepare_with_placeholders
     1192         */
     1193        function test_prepare_with_placeholders_and_individual_args( $sql, $values, $incorrect_usage, $expected) {
     1194                global $wpdb;
     1195
     1196                if ( $incorrect_usage ) {
     1197                        $this->setExpectedIncorrectUsage( 'wpdb::prepare' );
     1198                }
     1199
     1200                if ( ! is_array( $values ) ) {
     1201                        $values = array( $values );
     1202                }
     1203
     1204                array_unshift( $values, $sql );
     1205
     1206                $sql = call_user_func_array( array( $wpdb, 'prepare' ), $values );
     1207                $this->assertEquals( $expected, $sql );
     1208        }
     1209
     1210        /**
     1211         * @dataProvider data_prepare_with_placeholders
     1212         */
     1213        function test_prepare_with_placeholders_and_array_args( $sql, $values, $incorrect_usage, $expected) {
     1214                global $wpdb;
     1215
     1216                if ( $incorrect_usage ) {
     1217                        $this->setExpectedIncorrectUsage( 'wpdb::prepare' );
     1218                }
     1219
     1220                if ( ! is_array( $values ) ) {
     1221                        $values = array( $values );
     1222                }
     1223
     1224                $sql = call_user_func_array( array( $wpdb, 'prepare' ), array( $sql, $values ) );
     1225                $this->assertEquals( $expected, $sql );
     1226        }
     1227
     1228        function data_prepare_with_placeholders() {
     1229                global $wpdb;
     1230
     1231                return array(
     1232                        array(
     1233                                '%5s',   // SQL to prepare
     1234                                'foo',   // Value to insert in the SQL
     1235                                false,   // Whether to expect an incorrect usage error or not
     1236                                '  foo', // Expected output
     1237                        ),
     1238                        array(
     1239                                '%1$d %%% % %%1$d%% %%%1$d%%',
     1240                                1,
     1241                                true,
     1242                                "1 {$wpdb->placeholder_escape()}{$wpdb->placeholder_escape()} {$wpdb->placeholder_escape()} {$wpdb->placeholder_escape()}1\$d{$wpdb->placeholder_escape()} {$wpdb->placeholder_escape()}1{$wpdb->placeholder_escape()}",
     1243                        ),
     1244                        array(
     1245                                '%-5s',
     1246                                'foo',
     1247                                false,
     1248                                'foo  ',
     1249                        ),
     1250                        array(
     1251                                '%05s',
     1252                                'foo',
     1253                                false,
     1254                                '00foo',
     1255                        ),
     1256                        array(
     1257                                "%'#5s",
     1258                                'foo',
     1259                                false,
     1260                                '##foo',
     1261                        ),
     1262                        array(
     1263                                '%.3s',
     1264                                'foobar',
     1265                                false,
     1266                                'foo',
     1267                        ),
     1268                        array(
     1269                                '%.3f',
     1270                                5.123456,
     1271                                false,
     1272                                '5.123',
     1273                        ),
     1274                        array(
     1275                                '%.3f',
     1276                                5.12,
     1277                                false,
     1278                                '5.120',
     1279                        ),
     1280                        array(
     1281                                '%s',
     1282                                ' %s ',
     1283                                false,
     1284                                "' {$wpdb->placeholder_escape()}s '",
     1285                        ),
     1286                        array(
     1287                                '%1$s',
     1288                                ' %s ',
     1289                                false,
     1290                                " {$wpdb->placeholder_escape()}s ",
     1291                        ),
     1292                        array(
     1293                                '%1$s',
     1294                                ' %1$s ',
     1295                                false,
     1296                                " {$wpdb->placeholder_escape()}1\$s ",
     1297                        ),
     1298                        array(
     1299                                '%d %1$d %%% %',
     1300                                1,
     1301                                true,
     1302                                "1 1 {$wpdb->placeholder_escape()}{$wpdb->placeholder_escape()} {$wpdb->placeholder_escape()}",
     1303                        ),
     1304                        array(
     1305                                '%d %2$s',
     1306                                array( 1, 'hello' ),
     1307                                false,
     1308                                "1 hello",
     1309                        ),
     1310                        array(
     1311                                "'%s'",
     1312                                'hello',
     1313                                false,
     1314                                "'hello'",
     1315                        ),
     1316                        array(
     1317                                '"%s"',
     1318                                'hello',
     1319                                false,
     1320                                "'hello'",
     1321                        ),
     1322                        array(
     1323                                "%s '%1\$s'",
     1324                                'hello',
     1325                                true,
     1326                                "'hello' 'hello'",
     1327                        ),
     1328                        array(
     1329                                "%s '%1\$s'",
     1330                                'hello',
     1331                                true,
     1332                                "'hello' 'hello'",
     1333                        ),
     1334                        array(
     1335                                '%s "%1$s"',
     1336                                'hello',
     1337                                true,
     1338                                "'hello' \"hello\"",
     1339                        ),
     1340                        array(
     1341                                "%%s %%'%1\$s'",
     1342                                'hello',
     1343                                false,
     1344                                "{$wpdb->placeholder_escape()}s {$wpdb->placeholder_escape()}'hello'",
     1345                        ),
     1346                        array(
     1347                                '%%s %%"%1$s"',
     1348                                'hello',
     1349                                false,
     1350                                "{$wpdb->placeholder_escape()}s {$wpdb->placeholder_escape()}\"hello\"",
     1351                        ),
     1352                        array(
     1353                                '%s',
     1354                                ' %  s ',
     1355                                false,
     1356                                "' {$wpdb->placeholder_escape()}  s '",
     1357                        ),
     1358                        array(
     1359                                '%%f %%"%1$f"',
     1360                                3,
     1361                                false,
     1362                                "{$wpdb->placeholder_escape()}f {$wpdb->placeholder_escape()}\"3.000000\"",
     1363                        ),
     1364                        array(
     1365                                'WHERE second=\'%2$s\' AND first=\'%1$s\'',
     1366                                array( 'first arg', 'second arg' ),
     1367                                false,
     1368                                "WHERE second='second arg' AND first='first arg'",
     1369                        ),
     1370                        array(
     1371                                'WHERE second=%2$d AND first=%1$d',
     1372                                array( 1, 2 ),
     1373                                false,
     1374                                "WHERE second=2 AND first=1",
     1375                        ),
     1376                        array(
     1377                                "'%'%%s",
     1378                                'hello',
     1379                                true,
     1380                                "'{$wpdb->placeholder_escape()}'{$wpdb->placeholder_escape()}s",
     1381                        ),
     1382                        array(
     1383                                "'%'%%s%s",
     1384                                'hello',
     1385                                false,
     1386                                "'{$wpdb->placeholder_escape()}'{$wpdb->placeholder_escape()}s'hello'",
     1387                        ),
     1388                        array(
     1389                                "'%'%%s %s",
     1390                                'hello',
     1391                                false,
     1392                                "'{$wpdb->placeholder_escape()}'{$wpdb->placeholder_escape()}s 'hello'",
     1393                        ),
     1394                        array(
     1395                                "'%-'#5s' '%'#-+-5s'",
     1396                                array( 'hello', 'foo' ),
     1397                                false,
     1398                                "'hello' 'foo##'",
     1399                        ),
     1400                );
     1401        }
     1402
     1403        /**
     1404         * @dataProvider data_escape_and_prepare
     1405         */
     1406        function test_escape_and_prepare( $escape, $sql, $values, $incorrect_usage, $expected ) {
     1407                global $wpdb;
     1408
     1409                if ( $incorrect_usage ) {
     1410                        $this->setExpectedIncorrectUsage( 'wpdb::prepare' );
     1411                }
     1412
     1413                $escape = esc_sql( $escape );
     1414
     1415                $sql = str_replace( '{ESCAPE}', $escape, $sql );
     1416
     1417                $actual = $wpdb->prepare( $sql, $values );
     1418
     1419                $this->assertEquals( $expected, $actual );
     1420        }
     1421
     1422        function data_escape_and_prepare() {
     1423                global $wpdb;
     1424                return array(
     1425                        array(
     1426                                '%s',                                  // String to pass through esc_url()
     1427                                ' {ESCAPE} ',                          // Query to insert the output of esc_url() into, replacing "{ESCAPE}"
     1428                                'foo',                                 // Data to send to prepare()
     1429                                true,                                  // Whether to expect an incorrect usage error or not
     1430                                " {$wpdb->placeholder_escape()}s ",    // Expected output
     1431                        ),
     1432                        array(
     1433                                'foo%sbar',
     1434                                "SELECT * FROM bar WHERE foo='{ESCAPE}' OR baz=%s",
     1435                                array( ' SQLi -- -', 'pewpewpew' ),
     1436                                true,
     1437                                null,
     1438                        ),
     1439                        array(
     1440                                '%s',
     1441                                ' %s {ESCAPE} ',
     1442                                'foo',
     1443                                false,
     1444                                " 'foo' {$wpdb->placeholder_escape()}s ",
     1445                        ),
     1446                );
     1447        }
     1448
     1449        /**
     1450         * @expectedIncorrectUsage wpdb::prepare
     1451         */
     1452        function test_double_prepare() {
     1453                global $wpdb;
     1454
     1455                $part = $wpdb->prepare( ' AND meta_value = %s', ' %s ' );
     1456                $this->assertNotContains( '%s', $part );
     1457                $query = $wpdb->prepare( 'SELECT * FROM {$wpdb->postmeta} WHERE meta_key = %s $part', array( 'foo', 'bar' ) );
     1458                $this->assertNull( $query );
     1459        }
     1460
     1461        function test_prepare_numeric_placeholders_float_args() {
     1462                global $wpdb;
     1463
     1464                $actual = $wpdb->prepare(
     1465                        'WHERE second=%2$f AND first=%1$f',
     1466                        1.1,
     1467                        2.2
     1468                );
     1469
     1470                /* Floats can be right padded, need to assert differently */
     1471                $this->assertContains( ' first=1.1', $actual );
     1472                $this->assertContains( ' second=2.2', $actual );
     1473        }
     1474
     1475        function test_prepare_numeric_placeholders_float_array() {
     1476                global $wpdb;
     1477
     1478                $actual = $wpdb->prepare(
     1479                        'WHERE second=%2$f AND first=%1$f',
     1480                        array( 1.1, 2.2 )
     1481                );
     1482
     1483                /* Floats can be right padded, need to assert differently */
     1484                $this->assertContains( ' first=1.1', $actual );
     1485                $this->assertContains( ' second=2.2', $actual );
     1486        }
     1487
     1488        function test_query_unescapes_placeholders() {
     1489                global $wpdb;
     1490
     1491                $value = ' %s ';
     1492
     1493                $wpdb->query( "CREATE TABLE {$wpdb->prefix}test_placeholder( a VARCHAR(100) );" );
     1494                $sql = $wpdb->prepare( "INSERT INTO {$wpdb->prefix}test_placeholder VALUES(%s)", $value );
     1495                $wpdb->query( $sql );
     1496
     1497                $actual = $wpdb->get_var( "SELECT a FROM {$wpdb->prefix}test_placeholder" );
     1498
     1499                $wpdb->query( "DROP TABLE {$wpdb->prefix}test_placeholder" );
     1500
     1501                $this->assertNotContains( '%s', $sql );
     1502                $this->assertEquals( $value, $actual );
     1503        }
     1504
     1505        function test_esc_sql_with_unsupported_placeholder_type() {
     1506                global $wpdb;
     1507
     1508                $sql = $wpdb->prepare( ' %s %1$c ', 'foo' );
     1509                $sql = $wpdb->prepare( " $sql %s ", 'foo' );
     1510
     1511                $this->assertEquals( "  'foo' {$wpdb->placeholder_escape()}1\$c  'foo' ", $sql );
    11281512        }
    11291513}
Note: See TracChangeset for help on using the changeset viewer.