Skip to content
66 changes: 64 additions & 2 deletions php/WP_CLI/WpHttpCacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ class WpHttpCacheManager {
*/
protected $cache;

/**
* Minimum valid archive file size in bytes.
*
* This threshold (20 bytes) roughly corresponds to the smallest possible
* valid ZIP or TAR.GZ header, ensuring we skip obviously invalid or empty downloads.
*/
private const MIN_VALID_ARCHIVE_SIZE = 20;

/**
* @param FileCache $cache
*/
Expand Down Expand Up @@ -68,8 +76,8 @@ public function filter_pre_http_request( $response, $args, $url ) {
/**
* cache wp http api downloads
*
* @param array $response
* @param array $args
* @param array $response
* @param array $args
* @param string $url
* @return array
*/
Expand All @@ -86,11 +94,65 @@ public function filter_http_response( $response, $args, $url ) {
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return $response;
}
// Validate before caching.
if ( ! $this->validate_downloaded_file( $response['filename'], $url ) ) {
WP_CLI::warning( "Invalid or corrupt file from {$url}, skipping cache." );
return $response;
}
// cache downloaded file
$this->cache->import( $this->whitelist[ $url ]['key'], $response['filename'] );
return $response;
}

/**
* Validate downloaded file before adding to cache.
*
* @param string $file Path to the downloaded file.
* @param string $url Source URL.
* @return bool True if file is valid, false otherwise.
*/
private function validate_downloaded_file( $file, $url ) {
if ( ! is_readable( $file ) ) {
return false;
}

$size = filesize( $file );
if ( false === $size || $size < self::MIN_VALID_ARCHIVE_SIZE ) {
return false;
}

$ext = strtolower( pathinfo( (string) wp_parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
$mime = function_exists( 'mime_content_type' ) ? mime_content_type( $file ) : '';

if ( ( 'zip' === $ext || 'application/zip' === $mime ) && class_exists( '\ZipArchive' ) ) {
$zip = new \ZipArchive();
$result = $zip->open( $file );
if ( true !== $result ) {
return false;
}
// Optional deeper check: ensure we can read file list.
if ( 0 === $zip->numFiles ) { //phpcs:ignore
$zip->close();
return false;
}
$zip->close();
}

if ( ( preg_match( '/\.tar\.gz$/i', $url ) || 'application/gzip' === $mime ) && class_exists( '\PharData' ) ) {
try {
$phar = new \PharData( $file );
// Accessing the file list ensures it can be read.
if ( empty( iterator_to_array( $phar ) ) ) {
return false;
}
} catch ( \Exception $e ) {
return false;
}
}

return true;
}

/**
* whitelist a package url
*
Expand Down
Loading