diff --git a/php/WP_CLI/FileCache.php b/php/WP_CLI/FileCache.php index ee5681e3b..cfc6480e1 100644 --- a/php/WP_CLI/FileCache.php +++ b/php/WP_CLI/FileCache.php @@ -156,12 +156,16 @@ public function read( $key, $ttl = null ) { * Copy a file into the cache * * @param string $key cache key - * @param string $source source filename + * @param string $source source filename; tmp file filepath from HTTP response * @return bool */ public function import( $key, $source ) { $filename = $this->prepare_write( $key ); + if ( ! is_readable( $source ) ) { + return false; + } + if ( $filename ) { return copy( $source, $filename ) && touch( $filename ); } @@ -339,7 +343,7 @@ protected function ensure_dir_exists( $dir ) { * Prepare cache write * * @param string $key cache key - * @return bool|string filename or false + * @return bool|string The destination filename or false when cache disabled or directory creation fails. */ protected function prepare_write( $key ) { if ( ! $this->enabled ) { @@ -377,11 +381,11 @@ protected function validate_key( $key ) { $parts = preg_replace( "#[^{$this->whitelist}]#i", '-', $parts ); - return implode( '/', $parts ); + return rtrim( implode( '/', $parts ), '.' ); } /** - * Filename from key + * Destination filename from key * * @param string $key * @return string filename diff --git a/php/WP_CLI/WpHttpCacheManager.php b/php/WP_CLI/WpHttpCacheManager.php index 23dc58f73..0c1cbffca 100644 --- a/php/WP_CLI/WpHttpCacheManager.php +++ b/php/WP_CLI/WpHttpCacheManager.php @@ -12,7 +12,7 @@ class WpHttpCacheManager { /** - * @var array map whitelisted urls to keys and ttls + * @var array map whitelisted urls to keys and ttls */ protected $whitelist = []; diff --git a/tests/FileCacheTest.php b/tests/FileCacheTest.php index d4841f01f..301397fe0 100644 --- a/tests/FileCacheTest.php +++ b/tests/FileCacheTest.php @@ -102,4 +102,104 @@ public function test_export() { unlink( $target ); rmdir( $target_dir ); } + + public function test_import() { + $max_size = 32; + $ttl = 60; + $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); + $cache = new FileCache( $cache_dir, $ttl, $max_size ); + + $tmp_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache-import', true ); + mkdir( $tmp_dir ); + + // "$group/$slug-$version.$ext"; + $key = 'plugin/my-fixture-plugin-1.0.0.zip'; + $fixture_filepath = $tmp_dir . '/my-downloaded-fixture-plugin-1.0.0.zip'; + + $zip = new ZipArchive(); + $zip->open( $fixture_filepath, ZIPARCHIVE::CREATE ); + $zip->addFile( __FILE__ ); + $zip->close(); + + $result = $cache->import( $key, $fixture_filepath ); + + // Assert file is imported. + $this->assertTrue( $result ); + $this->assertFileExists( "{$cache_dir}/{$key}" ); + + // Clean up. + $cache->clear(); + unlink( $fixture_filepath ); + rmdir( $tmp_dir ); + } + + /** + * @see https://github.com/wp-cli/wp-cli/pull/5947 + */ + public function test_import_do_not_use_cache_file_cannot_be_read() { + $max_size = 32; + $ttl = 60; + $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); + $cache = new FileCache( $cache_dir, $ttl, $max_size ); + + $tmp_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache-import', true ); + mkdir( $tmp_dir ); + + $key = 'plugin/my-fixture-plugin-1.0.0.zip'; + $fixture_filepath = $tmp_dir . '/my-bad-permissions-fixture-plugin-1.0.0.zip'; + + $zip = new ZipArchive(); + $zip->open( $fixture_filepath, ZIPARCHIVE::CREATE ); + $zip->addFile( __FILE__ ); + $zip->close(); + + chmod( $fixture_filepath, 0000 ); + + // "Warning: copy(-.): Failed to open stream: Permission denied". + $error = null; + set_error_handler( + function ( $errno, $errstr ) use ( &$error ) { + $error = $errstr; + } + ); + + $result = $cache->import( $key, $fixture_filepath ); + + restore_error_handler(); + + $this->assertNull( $error ); + $this->assertFalse( $result ); + + // Clean up. + $cache->clear(); + unlink( $fixture_filepath ); + rmdir( $tmp_dir ); + } + + /** + * Windows filenames cannot end in periods. + * + * @covers \WP_CLI\FileCache::validate_key + * + * @see https://github.com/wp-cli/wp-cli/pull/5947 + * @see https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions + */ + public function test_validate_key_ending_in_period() { + $max_size = 32; + $ttl = 60; + $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); + $cache = new FileCache( $cache_dir, $ttl, $max_size ); + + $key = 'plugin/advanced-sidebar-menu-pro-9.5.7.'; + + $reflection = new ReflectionClass( $cache ); + + $method = $reflection->getMethod( 'validate_key' ); + $method->setAccessible( true ); + + $result = $method->invoke( $cache, $key ); + + $this->assertStringEndsNotWith( '.', $result ); + $this->assertSame( 'plugin/advanced-sidebar-menu-pro-9.5.7', $result ); + } }