Skip to content

Commit 6b55379

Browse files
Escape the database settings written into wp-tests-config.php (#355)
prepare.php copies WPT_DB_NAME, WPT_DB_USER, WPT_DB_PASSWORD, WPT_DB_HOST and WPT_TABLE_PREFIX into single-quoted PHP strings in wp-tests-config.php without escaping them. A password with a single quote, or one ending with a backslash, made the file a parse error, so the test run failed and cleanup-db.php could not read the settings. A double backslash was silently written as one. The values are now escaped with addcslashes( $value, "\\'" ), as WordPress does when it writes wp-config.php in wp-admin/setup-config.php, and the same goes for WP_PHP_BINARY. The placeholders are replaced with strtr() in a single pass: str_replace() ran each pair over the previous result, so a password containing "localhost" had that part replaced with WPT_DB_HOST. The CI job now uses a password with a quote, a backslash and the word "localhost", and checks that DB_PASSWORD in the generated file equals WPT_DB_PASSWORD. The mysql image puts MYSQL_PASSWORD into SQL unescaped, so a step sets the password with ALTER USER instead. Co-authored-by: Amy Kamala <39748136+kittenkamala@users.noreply.github.com>
1 parent 549f035 commit 6b55379

2 files changed

Lines changed: 37 additions & 12 deletions

File tree

‎.github/workflows/phpunit-tests.yml‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ jobs:
5353
WPT_TEST_DIR: /tmp/wp-test-runner
5454
WPT_DB_NAME: wordpress_test
5555
WPT_DB_USER: wp_test
56-
WPT_DB_PASSWORD: wp_test
56+
# A quote, a backslash and a placeholder name, so the run proves that
57+
# prepare.php writes the password into wp-tests-config.php as given.
58+
WPT_DB_PASSWORD: 'wp''te\st-localhost'
5759
WPT_DB_HOST: 127.0.0.1
5860
WPT_REPORT_API_KEY: ''
5961

@@ -64,6 +66,17 @@ jobs:
6466
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
6567
persist-credentials: false
6668

69+
# The mysql image writes MYSQL_PASSWORD into SQL without escaping it, so
70+
# the password above is set here instead.
71+
- name: Set the database password
72+
env:
73+
MYSQL_PWD: root
74+
run: |
75+
mysql -h 127.0.0.1 -u root <<'SQL'
76+
SET SESSION sql_mode = 'NO_BACKSLASH_ESCAPES';
77+
ALTER USER 'wp_test'@'%' IDENTIFIED BY 'wp''te\st-localhost';
78+
SQL
79+
6780
- name: Set up PHP
6881
uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # v2.40.0
6982
with:
@@ -78,6 +91,10 @@ jobs:
7891
- name: Prepare environment
7992
run: php prepare.php
8093

94+
- name: Check the database password in wp-tests-config.php
95+
run: |
96+
php -r 'require getenv( "WPT_PREPARE_DIR" ) . "/wp-tests-config.php"; exit( DB_PASSWORD === getenv( "WPT_DB_PASSWORD" ) ? 0 : 1 );'
97+
8198
- name: Run unit tests
8299
run: php test.php
83100
continue-on-error: true
@@ -91,7 +108,7 @@ jobs:
91108

92109
- name: Verify database cleanup
93110
env:
94-
MYSQL_PWD: wp_test
111+
MYSQL_PWD: ${{ env.WPT_DB_PASSWORD }}
95112
run: |
96113
TABLES=$(mysql -h 127.0.0.1 -u wp_test wordpress_test -N -e "SHOW TABLES LIKE 'wptests\_%'")
97114
if [ -n "$TABLES" ]; then

‎prepare.php‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -389,27 +389,35 @@ function curl_selected_bits( $k ) {
389389
$system_logger = $logger_replace_string . $system_logger;
390390

391391
// Define a string that will set the 'WP_PHP_BINARY' constant to the path of the PHP executable.
392-
$php_binary_string = 'define( \'WP_PHP_BINARY\', \'' . $runner_vars['WPT_PHP_EXECUTABLE'] . '\' );';
392+
$php_binary_string = 'define( \'WP_PHP_BINARY\', \'' . addcslashes( $runner_vars['WPT_PHP_EXECUTABLE'], "\\'" ) . '\' );';
393393

394394
/*
395395
* Map configuration file placeholders to environment-specific values.
396396
*
397-
* This is used in the subsequent str_replace operation to replace placeholder
398-
* values in the wp-tests-config-sample.php file with the ones provided.
397+
* The values are written into single-quoted PHP strings, so their backslashes
398+
* and single quotes are escaped, as WordPress does when it writes wp-config.php
399+
* in wp-admin/setup-config.php. Without that, a database password containing a
400+
* single quote, or ending with a backslash, makes wp-tests-config.php a parse
401+
* error, and a double backslash is silently turned into one.
399402
*/
400403
$wpt_table_prefix = trim( getenv( 'WPT_TABLE_PREFIX' ) );
401404
$search_replace = array(
402-
'wptests_' => '' !== $wpt_table_prefix ? $wpt_table_prefix : 'wptests_',
403-
'youremptytestdbnamehere' => trim( getenv( 'WPT_DB_NAME' ) ),
404-
'yourusernamehere' => trim( getenv( 'WPT_DB_USER' ) ),
405-
'yourpasswordhere' => trim( getenv( 'WPT_DB_PASSWORD' ) ),
406-
'localhost' => trim( getenv( 'WPT_DB_HOST' ) ),
405+
'wptests_' => addcslashes( '' !== $wpt_table_prefix ? $wpt_table_prefix : 'wptests_', "\\'" ),
406+
'youremptytestdbnamehere' => addcslashes( trim( getenv( 'WPT_DB_NAME' ) ), "\\'" ),
407+
'yourusernamehere' => addcslashes( trim( getenv( 'WPT_DB_USER' ) ), "\\'" ),
408+
'yourpasswordhere' => addcslashes( trim( getenv( 'WPT_DB_PASSWORD' ) ), "\\'" ),
409+
'localhost' => addcslashes( trim( getenv( 'WPT_DB_HOST' ) ), "\\'" ),
407410
'define( \'WP_PHP_BINARY\', \'php\' );' => $php_binary_string,
408411
$logger_replace_string => $system_logger,
409412
);
410413

411-
// Replace the placeholders in the wp-tests-config-sample.php file content with actual values.
412-
$contents = str_replace( array_keys( $search_replace ), array_values( $search_replace ), $contents );
414+
/*
415+
* Replace the placeholders in a single pass. str_replace() runs each pair over
416+
* the result of the previous ones, so a value that contains a later placeholder,
417+
* such as a password containing "localhost", would be rewritten. strtr() never
418+
* revisits replaced text.
419+
*/
420+
$contents = strtr( $contents, $search_replace );
413421

414422
/*
415423
* Point the test suite's temporary directory inside the test directory, so

0 commit comments

Comments
 (0)