Skip to content

Commit 0ab6195

Browse files
swissspidyCopilot
andauthored
Detect file extension from downloaded archives (#334)
* Detect file extension from downloaded archives * Fix indentation * Address code review feedback * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Lint fix * Address code review feedback * no suppression * add error --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 738c6d2 commit 0ab6195

2 files changed

Lines changed: 103 additions & 2 deletions

File tree

features/core-download.feature

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,3 +556,40 @@ Feature: Download WordPress
556556
Success:
557557
"""
558558

559+
Scenario: Extracts provided tar.gz files
560+
Given an empty directory
561+
562+
When I run `wp core download https://downloads.wordpress.org/release/wordpress-7.0.tar.gz --force`
563+
Then the {RUN_DIR} directory should contain:
564+
"""
565+
index.php
566+
license.txt
567+
"""
568+
569+
Scenario: Extracts provided zip files
570+
Given an empty directory
571+
572+
When I run `wp core download https://downloads.wordpress.org/release/wordpress-7.0.zip --force`
573+
Then the {RUN_DIR} directory should contain:
574+
"""
575+
index.php
576+
license.txt
577+
"""
578+
579+
Scenario: Error when downloading an unsupported archive format
580+
Given an empty directory
581+
And that HTTP requests to http://example.com/unsupported.txt will respond with:
582+
"""
583+
HTTP/1.1 200 OK
584+
Content-Type: text/plain
585+
586+
This is not a zip or tarball file.
587+
"""
588+
589+
When I try `wp core download http://example.com/unsupported.txt --force`
590+
Then STDERR should contain:
591+
"""
592+
Error: Unsupported archive format. The downloaded file is not a valid zip or tar.gz archive.
593+
"""
594+
And the return code should be 1
595+

src/Core_Command.php

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,9 @@ public function download( $args, $assoc_args ) {
297297
}
298298

299299
if ( ! $cache_file || $bad_cache ) {
300-
$temp = Utils\get_temp_dir() . uniqid( 'wp_' ) . ".{$extension}";
300+
$temp = Utils\get_temp_dir() . uniqid( 'wp_' ) . '.tmp';
301301
register_shutdown_function(
302-
function () use ( $temp ) {
302+
function () use ( &$temp ) {
303303
if ( file_exists( $temp ) ) {
304304
unlink( $temp );
305305
}
@@ -322,6 +322,70 @@ function () use ( $temp ) {
322322
WP_CLI::error( "Couldn't access download URL (HTTP code {$response->status_code})." );
323323
}
324324

325+
if ( ! file_exists( $temp ) || ! is_readable( $temp ) ) {
326+
WP_CLI::error( "Downloaded file could not be written to or read from disk: {$temp}" );
327+
}
328+
329+
$extension = '';
330+
$mime = function_exists( 'mime_content_type' ) ? mime_content_type( $temp ) : '';
331+
if ( 'application/zip' === $mime || 'application/x-zip-compressed' === $mime ) {
332+
$extension = 'zip';
333+
} elseif ( 'application/x-gzip' === $mime || 'application/gzip' === $mime ) {
334+
$extension = 'tar.gz';
335+
} else {
336+
// Fallback to magic bytes.
337+
$handle = @fopen( $temp, 'rb' );
338+
if ( $handle ) {
339+
$bytes = fread( $handle, 2 );
340+
fclose( $handle );
341+
if ( 'PK' === $bytes ) {
342+
$extension = 'zip';
343+
} elseif ( "\x1f\x8b" === $bytes ) {
344+
$extension = 'tar.gz';
345+
}
346+
}
347+
}
348+
349+
// Deep validation for tar.gz archives: verify it's a valid, readable gzip stream.
350+
if ( 'tar.gz' === $extension && function_exists( 'gzopen' ) ) {
351+
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Silence potential gzopen warnings on corrupt streams.
352+
$gz = @gzopen( $temp, 'rb' );
353+
if ( $gz ) {
354+
$header = gzread( $gz, 262 );
355+
gzclose( $gz );
356+
if ( ! is_string( $header ) || strlen( $header ) < 1 ) {
357+
$extension = '';
358+
}
359+
} else {
360+
$extension = '';
361+
}
362+
}
363+
364+
if ( ! in_array( $extension, [ 'zip', 'tar.gz' ], true ) ) {
365+
WP_CLI::error( 'Unsupported archive format. The downloaded file is not a valid zip or tar.gz archive.' );
366+
}
367+
368+
$actual_temp = substr( $temp, 0, -4 ) . ".{$extension}";
369+
if ( ! rename( $temp, $actual_temp ) ) {
370+
// Fallback to copy + unlink.
371+
if ( ! copy( $temp, $actual_temp ) ) {
372+
WP_CLI::error( 'Failed to copy the downloaded file.' );
373+
}
374+
$old_temp = $temp;
375+
$temp = $actual_temp;
376+
if ( ! unlink( $old_temp ) ) {
377+
register_shutdown_function(
378+
function () use ( $old_temp ) {
379+
if ( file_exists( $old_temp ) ) {
380+
unlink( $old_temp );
381+
}
382+
}
383+
);
384+
}
385+
} else {
386+
$temp = $actual_temp;
387+
}
388+
325389
if ( 'nightly' !== $version ) {
326390
unset( $options['filename'] );
327391
/** @var \WpOrg\Requests\Response $md5_response */

0 commit comments

Comments
 (0)