-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #163 from woocommerce/24-04/implement-incompatible…
…-archive-check Add `woo:validate-zip <path>` command
- Loading branch information
Showing
10 changed files
with
440 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
### How to run tests? | ||
|
||
Run `make tests` to run all tests. | ||
|
||
Or, run any of the commands below to run tests: | ||
|
||
```bash | ||
make phpcs | ||
make phpstan | ||
make phpunit | ||
make phan | ||
``` | ||
|
||
### How to run tests with Xdebug: | ||
|
||
To run tests with Xdebug, you can use the following commands: | ||
|
||
```bash | ||
make phpunit DEBUG=1 | ||
``` | ||
|
||
### How to update snapshots: | ||
|
||
To update snapshots, run the following command: | ||
|
||
```bash | ||
make phpunit ARGS='-d --update-snapshots' | ||
``` | ||
|
||
You can also combine both: | ||
|
||
```bash | ||
make phpunit ARGS='-d --update-snapshots' DEBUG=1 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
namespace QIT_CLI\Commands; | ||
|
||
use QIT_CLI\Woo\ZipValidator; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputArgument; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
class WooValidateZipCommand extends Command { | ||
protected static $defaultName = 'woo:validate-zip'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase | ||
|
||
private $zip_validator; | ||
|
||
public function __construct( ZipValidator $zip_validator ) { | ||
$this->zip_validator = $zip_validator; | ||
|
||
parent::__construct(); | ||
} | ||
|
||
protected function configure() { | ||
$this | ||
->setDescription( 'Validate a local ZIP file\'s content.' ) | ||
->setHelp( 'If invalid content or wrong format is found in ZIP file, an error will be shown.' ) | ||
->addArgument( 'path', InputArgument::REQUIRED, 'The ZIP file path' ); | ||
} | ||
|
||
protected function execute( InputInterface $input, OutputInterface $output ): int { | ||
try { | ||
$zip_file = $input->getArgument( 'path' ); | ||
|
||
$this->zip_validator->validate_zip( $zip_file ); | ||
|
||
$output->writeln( '<info>ZIP file content is valid.</info>' ); | ||
} catch ( \UnexpectedValueException $e ) { | ||
$output->writeln( sprintf( '<comment>%s</comment>', $e->getMessage() ) ); | ||
|
||
return Command::FAILURE; | ||
} catch ( \Exception $e ) { | ||
$output->writeln( sprintf( '<error>An error occurred while validating the provided file. Error: %s</error>', $e->getMessage() ) ); | ||
|
||
return Command::FAILURE; | ||
} | ||
|
||
return Command::SUCCESS; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
<?php | ||
|
||
namespace QIT_CLI\Woo; | ||
|
||
use ZipArchive; | ||
|
||
class ZipValidator { | ||
|
||
/** | ||
* @var string[] | ||
*/ | ||
protected $system_files = [ | ||
// https://github.com/github/gitignore/blob/main/Global/Windows.gitignore. | ||
'Thumbs.db', | ||
'Thumbs.db:encryptable', | ||
'Desktop.ini', | ||
'desktop.ini', | ||
'ehthumbs.db', | ||
'ehthumbs_vista.db', | ||
'$RECYCLE.BIN/', | ||
// https://github.com/github/gitignore/blob/main/Global/Linux.gitignore. | ||
'~', | ||
'.directory', | ||
// https://github.com/github/gitignore/blob/main/Global/macOS.gitignore. | ||
'.DS_Store', | ||
'.AppleDouble', | ||
'.LSOverride', | ||
'.Spotlight-V100', | ||
'.Trashes', | ||
'.fseventsd', | ||
]; | ||
|
||
/** | ||
* @param string $filepath | ||
* | ||
* @return \ZipArchive | ||
* @throws \RuntimeException If the file is not a valid zip file. | ||
* @throws \UnexpectedValueException If the zip file is inconsistent. | ||
*/ | ||
private function open_file( string $filepath ) { | ||
$zip = new ZipArchive(); | ||
$opened = $zip->open( $filepath, ZipArchive::CHECKCONS ); | ||
|
||
if ( $opened === 21 ) { | ||
throw new \UnexpectedValueException( 'ZIP file is inconsistent. ZIP files generated by the Archive Utility from macOS may be the cause.' ); | ||
} | ||
|
||
if ( $opened !== true ) { | ||
throw new \RuntimeException( 'This is not a valid ZIP file.' ); | ||
} | ||
|
||
return $zip; | ||
} | ||
|
||
/** | ||
* @param string $filepath | ||
* | ||
* @return void | ||
*/ | ||
public function validate_zip( string $filepath ) { | ||
$zip = $this->open_file( $filepath ); | ||
|
||
// Example (foo => foo/). | ||
$plugin_slug = $this->extract_slug_from_filepath( $filepath ); | ||
$parent_dir = strtolower( trim( trim( $plugin_slug ), '/' ) . '/' ); | ||
|
||
$found_parent_directory = false; | ||
|
||
$left = 0; | ||
$right = $zip->numFiles - 1; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase | ||
|
||
while ( $left <= $right ) { | ||
foreach ( [ $left, $right ] as $i ) { | ||
$info = $zip->statIndex( $i ); | ||
|
||
if ( ! $info ) { | ||
throw new \RuntimeException( 'Cannot read ZIP index.', 400 ); | ||
} | ||
|
||
if ( str_contains( $info['name'], '/vendor' ) || str_contains( $info['name'], '/node_modules' ) ) { | ||
continue; | ||
} | ||
|
||
if ( $this->is_file_invalid( $info['name'] ) ) { | ||
throw new \RuntimeException( sprintf( 'Invalid (%s) file/folder inside the provided ZIP file', $info['name'] ), 400 ); | ||
} | ||
|
||
if ( ! $found_parent_directory && str_starts_with( strtolower( $info['name'] ), $parent_dir ) ) { | ||
$found_parent_directory = true; | ||
} | ||
} | ||
|
||
++$left; | ||
--$right; | ||
} | ||
|
||
$zip->close(); | ||
} | ||
|
||
/** | ||
* @param string $filepath | ||
* | ||
* @return string | ||
*/ | ||
private function extract_slug_from_filepath( string $filepath ): string { | ||
$filename = basename( $filepath ); | ||
|
||
return substr( $filename, 0, strpos( $filename, '.' ) ); | ||
} | ||
|
||
/** | ||
* @param string $name | ||
* | ||
* @return bool | ||
*/ | ||
protected function is_file_invalid( string $name ): bool { | ||
foreach ( $this->system_files as $system_file ) { | ||
if ( str_ends_with( $name, $system_file ) ) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
namespace QIT_CLI_Tests\Helpers; | ||
|
||
use RuntimeException; | ||
use ZipArchive; | ||
|
||
class ZipBuilderHelper { | ||
private $filename; | ||
private $files = array(); | ||
/** | ||
* @var string | ||
*/ | ||
private $filepath; | ||
|
||
/** | ||
* @param string $filename The name of the zip file to be created. | ||
*/ | ||
public function __construct( $filename ) { | ||
$this->filename = $filename; | ||
$this->filepath = __DIR__ . "/../$this->filename"; | ||
} | ||
|
||
/** | ||
* Adds a new file to the archive. | ||
* | ||
* @param string $source The file to be added. | ||
* @param string $target The target path in the archive. | ||
* | ||
* @return self The current ZipBuilder instance. | ||
*/ | ||
public function with_file( $source, $target ): self { | ||
$this->files[] = array( | ||
'source' => $source, | ||
'target' => $target, | ||
); | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Builds the zip archive. | ||
* | ||
* @return string The zip file path. | ||
* @throws RuntimeException If the zip file could not be created. | ||
* | ||
*/ | ||
public function build(): string { | ||
$zip = new ZipArchive(); | ||
|
||
if ( $zip->open( $this->get_file_path(), ZipArchive::CREATE ) !== true ) { | ||
throw new RuntimeException( 'Could not create ZIP file.' ); | ||
} | ||
|
||
foreach ( $this->files as $file ) { | ||
if ( ! $zip->addFile( $file['source'], $file['target'] ) ) { | ||
throw new RuntimeException( 'Could not add file to zip: ' . $file['source'] ); | ||
} | ||
} | ||
|
||
$zip->close(); | ||
|
||
clearstatcache(); | ||
|
||
if ( ! file_exists( $this->get_file_path() ) ) { | ||
throw new RuntimeException( 'ZIP file was not created.' ); | ||
} | ||
|
||
return $this->get_file_path(); | ||
} | ||
|
||
function corrupt() { | ||
$data = file_get_contents( $this->get_file_path() ); | ||
if ( ! $data ) { | ||
throw new \RuntimeException( 'Could not read ZIP file.' ); | ||
} | ||
|
||
$data = substr_replace( $data, "", 20, 4 ); // Removing 4 bytes from offset 20 | ||
|
||
file_put_contents( $this->get_file_path(), $data ); | ||
} | ||
|
||
public function get_file_path(): string { | ||
return $this->filepath; | ||
} | ||
} |
Oops, something went wrong.