diff --git a/src/WordPress.php b/src/WordPress.php index e4516d1..5fa061b 100644 --- a/src/WordPress.php +++ b/src/WordPress.php @@ -278,6 +278,10 @@ public function create( $args, $assoc_args ) { $this->logger->debug( 'args:', $args ); $this->logger->debug( 'assoc_args:', empty( $assoc_args ) ? array( 'NULL' ) : $assoc_args ); $this->site_data['site_url'] = strtolower( \EE\Utils\remove_trailing_slash( $args[0] ) ); + // site-command's create router validates too; this guard keeps an older site-command working. + if ( function_exists( '\EE\Site\Utils\validate_site_name' ) ) { + \EE\Site\Utils\validate_site_name( $this->site_data['site_url'] ); + } $mu = \EE\Utils\get_flag_value( $assoc_args, 'mu' ); $this->site_data['app_sub_type'] = $mu ?? 'wp'; @@ -508,10 +512,10 @@ private function enable_page_cache() { \EE_DOCKER::docker_compose_exec( "wp config set RT_WP_NGINX_HELPER_REDIS_HOSTNAME $redis_host --add=true --type=constant", 'php', 'bash', 'www-data' ); \EE_DOCKER::docker_compose_exec( "wp config set RT_WP_NGINX_HELPER_REDIS_PORT 6379 --add=true --type=constant", 'php', 'bash', 'www-data' ); - \EE_DOCKER::docker_compose_exec( "wp config set RT_WP_NGINX_HELPER_REDIS_PREFIX $page_cache_key_prefix --add=true --type=constant", 'php', 'bash', 'www-data' ); - \EE_DOCKER::docker_compose_exec( "wp config set WP_CACHE_KEY_SALT $obj_cache_key_prefix --add=true --type=constant", 'php', 'bash', 'www-data' ); + \EE_DOCKER::docker_compose_exec( 'wp config set RT_WP_NGINX_HELPER_REDIS_PREFIX ' . escapeshellarg( $page_cache_key_prefix ) . ' --add=true --type=constant', 'php', 'bash', 'www-data' ); + \EE_DOCKER::docker_compose_exec( 'wp config set WP_CACHE_KEY_SALT ' . escapeshellarg( $obj_cache_key_prefix ) . ' --add=true --type=constant', 'php', 'bash', 'www-data' ); \EE_DOCKER::docker_compose_exec( "wp config set WP_REDIS_MAXTTL 14400 --add=true --type=constant", 'php', 'bash', 'www-data' ); - \EE_DOCKER::docker_compose_exec( "wp $wp_cli_params rt_wp_nginx_helper_options '$plugin_data' --format=json", 'php', 'bash', 'www-data' ); + \EE_DOCKER::docker_compose_exec( "wp $wp_cli_params rt_wp_nginx_helper_options " . escapeshellarg( $plugin_data ) . ' --format=json', 'php', 'bash', 'www-data' ); } @@ -898,14 +902,19 @@ private function maybe_verify_remote_db_connection() { \EE::log( 'Verifying connection to remote database' ); - $check_db_connection = sprintf( - "docker exec %s sh -c \"mysql --host='%s' --port='%s' --user='%s' --password='%s' --execute='EXIT'\"", - $container_name, - $db_host, - $this->site_data['db_port'], - $this->site_data['db_user'], - $this->site_data['db_password'] + // Each value is quoted for the shell inside the container, then the whole script for the host shell. + $db_args = sprintf( + '--host=%s --port=%s --user=%s --password=%s', + escapeshellarg( $db_host ), + escapeshellarg( $this->site_data['db_port'] ), + escapeshellarg( $this->site_data['db_user'] ), + escapeshellarg( $this->site_data['db_password'] ) ); + $db_exec = function ( $shell, $script ) use ( $container_name ) { + return sprintf( 'docker exec %s %s -c %s', $container_name, $shell, escapeshellarg( $script ) ); + }; + + $check_db_connection = $db_exec( 'sh', "mysql $db_args --execute='EXIT'" ); if ( ! \EE::exec( $check_db_connection ) ) { \EE::exec( "docker rm -f $container_name" ); throw new \Exception( 'Unable to connect to remote db' ); @@ -913,19 +922,11 @@ private function maybe_verify_remote_db_connection() { \EE::success( 'Connection to remote db verified' ); $name = str_replace( '_', '\_', $this->site_data['db_name'] ); - $check_db_exists = sprintf( "docker exec %s bash -c \"mysqlshow --user='%s' --password='%s' --host='%s' --port='%s' '%s'\"", $container_name, $this->site_data['db_user'], $this->site_data['db_password'], $db_host, $this->site_data['db_port'], $name ); + $check_db_exists = $db_exec( 'bash', "mysqlshow $db_args " . escapeshellarg( $name ) ); if ( ! \EE::exec( $check_db_exists ) ) { \EE::log( sprintf( 'Database `%s` does not exist. Attempting to create it.', $this->site_data['db_name'] ) ); - $create_db_command = sprintf( - "docker exec %s bash -c \"mysql --host='%s' --port='%s' --user='%s' --password='%s' --execute='CREATE DATABASE %s;'\"", - $container_name, - $db_host, - $this->site_data['db_port'], - $this->site_data['db_user'], - $this->site_data['db_password'], - $this->site_data['db_name'] - ); + $create_db_command = $db_exec( 'bash', "mysql $db_args --execute=" . escapeshellarg( 'CREATE DATABASE ' . $this->site_data['db_name'] . ';' ) ); if ( ! \EE::exec( $create_db_command ) ) { \EE::exec( "docker rm -f $container_name" ); @@ -939,38 +940,10 @@ private function maybe_verify_remote_db_connection() { } } else { if ( $this->force ) { - \EE::exec( - sprintf( - "docker exec %s bash -c \"mysql --host='%s' --port='%s' --user='%s' --password='%s' --execute='DROP DATABASE %s;'\"", - $container_name, - $db_host, - $this->site_data['db_port'], - $this->site_data['db_user'], - $this->site_data['db_password'], - $this->site_data['db_name'] - ) - ); - \EE::exec( - sprintf( - "docker exec %s bash -c \"mysql --host='%s' --port='%s' --user='%s' --password='%s' --execute='CREATE DATABASE %s;'\"", - $container_name, - $db_host, - $this->site_data['db_port'], - $this->site_data['db_user'], - $this->site_data['db_password'], - $this->site_data['db_name'] - ) - ); + \EE::exec( $db_exec( 'bash', "mysql $db_args --execute=" . escapeshellarg( 'DROP DATABASE ' . $this->site_data['db_name'] . ';' ) ) ); + \EE::exec( $db_exec( 'bash', "mysql $db_args --execute=" . escapeshellarg( 'CREATE DATABASE ' . $this->site_data['db_name'] . ';' ) ) ); } - $check_tables = sprintf( - "docker exec %s bash -c \"mysql --host='%s' --port='%s' --user='%s' --password='%s' --execute='USE %s; show tables;'\"", - $container_name, - $db_host, - $this->site_data['db_port'], - $this->site_data['db_user'], - $this->site_data['db_password'], - $this->site_data['db_name'] - ); + $check_tables = $db_exec( 'bash', "mysql $db_args --execute=" . escapeshellarg( 'USE ' . $this->site_data['db_name'] . '; show tables;' ) ); $launch = \EE::launch( $check_tables ); if ( ! $launch->return_code ) { @@ -1208,7 +1181,7 @@ private function wp_download_and_config( $assoc_args ) { } $db_host = isset( $this->site_data['db_port'] ) ? $this->site_data['db_host'] . ':' . $this->site_data['db_port'] : $this->site_data['db_host']; - $wp_config_create_command = sprintf( 'wp config create --dbuser=\'%s\' --dbname=\'%s\' --dbpass=\'%s\' --dbhost=\'%s\' %s --extra-php="%s"', $this->site_data['db_user'], $this->site_data['db_name'], $this->site_data['db_password'], $db_host, $config_arguments, $extra_php ); + $wp_config_create_command = sprintf( 'wp config create --dbuser=%s --dbname=%s --dbpass=%s --dbhost=%s %s --extra-php="%s"', escapeshellarg( $this->site_data['db_user'] ), escapeshellarg( $this->site_data['db_name'] ), escapeshellarg( $this->site_data['db_password'] ), escapeshellarg( $db_host ), $config_arguments, $extra_php ); try { if ( ! \EE_DOCKER::docker_compose_exec( $wp_config_create_command, 'php', 'bash', 'www-data' ) ) { @@ -1248,15 +1221,16 @@ private function install_wp() { } $prefix = ( $this->site_data['site_ssl'] ) ? 'https://' : 'http://'; - $install_command = sprintf( 'wp core %s --url=\'%s%s\' --title=\'%s\' --admin_user=\'%s\'', $wp_install_command, $prefix, $this->site_data['site_url'], $this->site_data['app_admin_url'], $this->site_data['app_admin_username'] ); - $install_command .= $this->site_data['app_admin_password'] ? sprintf( ' --admin_password=\'%s\'', $this->site_data['app_admin_password'] ) : ''; - $install_command .= sprintf( ' --admin_email=\'%s\' %s', $this->site_data['app_admin_email'], $maybe_multisite_type ); - - $core_install = \EE_DOCKER::docker_compose_exec( $install_command, 'php', 'bash', 'www-data', '', false, false, [ - $this->site_data['app_admin_username'], - $this->site_data['app_admin_email'], - $this->site_data['app_admin_password'], - ] ); + $install_command = sprintf( 'wp core %s --url=%s --title=%s --admin_user=%s', $wp_install_command, escapeshellarg( $prefix . $this->site_data['site_url'] ), escapeshellarg( $this->site_data['app_admin_url'] ), escapeshellarg( $this->site_data['app_admin_username'] ) ); + $install_command .= $this->site_data['app_admin_password'] ? ' --admin_password=' . escapeshellarg( $this->site_data['app_admin_password'] ) : ''; + $install_command .= sprintf( ' --admin_email=%s %s', escapeshellarg( $this->site_data['app_admin_email'] ), $maybe_multisite_type ); + + $secrets = array_filter( [ $this->site_data['app_admin_username'], $this->site_data['app_admin_email'], $this->site_data['app_admin_password'] ], function ( $value ) { + return '' !== (string) $value; + } ); + // The command holds the escaped forms, which differ from the raw values when they contain a quote; mask those first. + $obfuscate = array_merge( array_map( 'escapeshellarg', $secrets ), $secrets ); + $core_install = \EE_DOCKER::docker_compose_exec( $install_command, 'php', 'bash', 'www-data', '', false, false, $obfuscate ); if ( ! $core_install ) { throw new \Exception( 'WordPress install failed. Please check logs.' );