Problem/Motivation

Using Drupal\Core\Archiver\Zip to initialize a zip object from a file path, I'm receiving a ArchiverException Cannot open %file_path, returned error by ZipArchive ZIP_ER_OPEN - 11

I was able to reproduce the issue with:
Ubuntu 14.04
PHP 5.6.23
ZIP lib 1.12.5

Passing a \ZipArchive::CREATE flag to ZipArchive open() method, fixed the problem, but Drupal\Core\Archiver\Zip doesn't allow to pass any additional arguments to constructor.

Proposed resolution

To add a second optional $flags argument in Drupal\Core\Archiver\Zip __constructor() and pass by default \ZipArchive::CREATE (create the archive if not exists) flag.

Comments

sdstyles created an issue. See original summary.

sdstyles’s picture

Status: Active » Needs review
StatusFileSize
new1 KB
Pavan B S’s picture

StatusFileSize
new1 KB

Rerolled the patch, please review.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

JamesK’s picture

Status: Needs review » Reviewed & tested by the community

Confirmed that the documentation states the file will be created as needed, which does not happen without this patch. (See https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Archiver%...)

Tested that this patch fixes the issue with:

$zip_path = file_create_filename($zip_name . '.zip', 'temporary://');
$zip = \Drupal::service('plugin.manager.archiver')->getInstance(['filepath' => $zip_path]);

Before patch, ArchiverException it thrown.
After patch, $zip is set to the Drupal/Core/Archiver/Zip object as expected.

JamesK’s picture

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

This could use some test coverage.

mr.baileys’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new1.66 KB
new2.66 KB

Added test coverage.

The last submitted patch, 8: 2850794-8-zip-archive-create-test-only.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

borisson_’s picture

Status: Needs review » Needs work

This testcoverage looks to be sufficient, that looks really good. It does bring some new coding standards violations though.

  1. +++ b/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
    @@ -0,0 +1,48 @@
    +/**
    + * @file
    + * Contains \Drupal\Tests\Core\Archiver\ZipTest.
    + */
    

    Should be removed.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
    @@ -0,0 +1,48 @@
    +  }
    

    There should be a newline following the method.

  3. +++ b/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
    @@ -0,0 +1,48 @@
    +}
    \ No newline at end of file
    

    There should be a newline at the end of the file.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new651 bytes
new2.58 KB

Fixed my own nitpicks.

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

All I did was fix nitpicks, I feel it's ok for me to set this to RTBC?

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 12: 2850794-12.patch, failed testing. View results

borisson_’s picture

1) Drupal\Tests\ComposerIntegrationTest::testComposerLockHash
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-0a3f405c686e98658196e44ca56ed3b0
+9d37d607bfdb3f5127d18051be44fca0

That was the failure in #12 I retested this because that doesn't seem to be related.

borisson_’s picture

Status: Needs work » Reviewed & tested by the community

Back to rtbc, because the test is green again.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
@@ -0,0 +1,44 @@
+class ZipTest extends KernelTestBase {
...
+    // ZipArchive::open() does not support the temporary stream wrapper, so we
+    // have to get the path to the temporary folder.
+    $filename = file_directory_temp() . '/' . $this->randomMachineName() . '.zip';
...
+      file_unmanaged_save_data('llama', file_directory_temp() . '/llama.txt', FILE_EXISTS_REPLACE);
+      $zip->add(file_directory_temp() . '/llama.txt');

Let's use BrowserTestBase here. Using the file_directory_temp() in a test like this is super risky and has lead to hard to spot random fails in the past. That way we get a real directory. Yes it is a shame we have to install Drupal but the predictability and clean up is important.

borisson_’s picture

Status: Needs work » Needs review
StatusFileSize
new641 bytes
new2.57 KB

Fixed #17.

Status: Needs review » Needs work

The last submitted patch, 18: 2850794-18.patch, failed testing. View results

borisson_’s picture

That test was green locally, I have no idea why it's not on the testbot.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

kevinfunk’s picture

Version: 8.6.x-dev » 8.9.x-dev
Related issues: +#2901618: Zip Archiver can't create files because of lack of flags.
alexpott’s picture

  1. +++ b/core/lib/Drupal/Core/Archiver/Zip.php
    @@ -23,12 +23,15 @@ class Zip implements ArchiverInterface {
    -  public function __construct($file_path) {
    +  public function __construct($file_path, $flags = \ZipArchive::CREATE) {
         $this->zip = new \ZipArchive();
    -    if ($this->zip->open($file_path) !== TRUE) {
    +    if ($this->zip->open($file_path, $flags) !== TRUE) {
    

    Imo we shouldn't change the default from \ZipArchive::open()... i.e our default should be NULL too. But we should allow passing in flags.

    Ah I see. So this change makes us comply with the docs and also probably behave the same as \Drupal\Core\Archiver\Tar

    That seems like a good idea.

  2. I guess we need to work out why this fails on DrupalCI.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

averagejoe3000’s picture

StatusFileSize
new2.54 KB
new581 bytes

Rerolled patch against 9.1.x. The exception message needed to be updated.

RedLucas25’s picture

Hey, I've been using this patch for a good three years now, it's just that failing test that's blocking this from being committed to master eh?

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new1.51 KB
new2.49 KB
new3.08 KB

As this is now targeted for Drupal 9, the test will have to use its file management methods.

  • Make it a Kernel test. There isn't a justification for it to be a BrowserTest. However, I just read #17 above so I'm not sure about my position on it.
  • Move the test to the correct namespace.
  • Use D9 file functions in the test.
  • Reorder variable declarations in the test to be closer to their usages.
  • Remove what I feel is an unnecessary try/catch in the test.

The last submitted patch, 27: 2850794-TESTONLY-27.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

cilefen’s picture

StatusFileSize
new1.6 KB
new2.57 KB
new1.01 KB

Here it is, again as a BrowserTest.

cilefen’s picture

StatusFileSize
new2.57 KB
new566 bytes

Fix the coding standards.

The last submitted patch, 29: 2850794-TESTONLY-28.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

cilefen’s picture

The 8.9.x failures are unrelated.

alexpott’s picture

Status: Needs review » Needs work
+++ b/core/tests/Drupal/FunctionalTests/Core/Archiver/ZipTest.php
@@ -0,0 +1,39 @@
+/**
+ * @coversDefaultClass \Drupal\Core\Archiver\Zip
+ * @group zip
+ */
+class ZipTest extends BrowserTestBase {

Looking back at my comments over 2 years ago - I realise we can use KernelTestBase we just should not use the temporary directory - we can use the sites directory - the test system will clear this up for us.

+++ b/core/tests/Drupal/FunctionalTests/Core/Archiver/ZipTest.php
@@ -0,0 +1,39 @@
+    // Create a temporary file that can be added to the archive.
+    $filename = \Drupal::service('file_system')->getTempDirectory() . '/' . $this->randomMachineName();
+    \Drupal::service('file_system')->saveData('llama', $filename, FileSystemInterface::EXISTS_REPLACE);

Can use TestFileCreationTrait (even in a Kernel test) to generate a test file.

rahulrasgon’s picture

Status: Needs work » Needs review
StatusFileSize
new3.03 KB
new3.34 KB

I have updated the patch as per @alexpott comments.
Please review.

alexpott’s picture

Status: Needs review » Needs work
  1. +++ b/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
    @@ -0,0 +1,66 @@
    +  /**
    +   * Modules to enable.
    +   *
    +   * @var array
    +   */
    +  public static $modules = ['system'];
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected $defaultTheme = 'stark';
    

    These are not required for this test.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
    @@ -0,0 +1,66 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function setUpFilesystem() {
    +    $public_file_directory = $this->siteDirectory . '/files';
    +
    +    require_once 'core/includes/file.inc';
    +
    +    mkdir($this->siteDirectory, 0775);
    +    mkdir($this->siteDirectory . '/files', 0775);
    +
    +    $this->setSetting('file_public_path', $public_file_directory);
    +  }
    

    This is not necessary if you change the test to:

      public function testCreateArchive() {
        /** @var \Drupal\Core\File\FileSystemInterface $file_system */
        $file_system = \Drupal::service('file_system');
        $textfile = current($this->getTestFiles('text'));
        $archiveFilename = 'public://' . $this->randomMachineName() . '.zip';
        $zip = new Zip($file_system->realPath($archiveFilename));
        $zip->add($file_system->realPath($textfile->uri));
        // Close the archive and make sure it is written to disk.
        $this->assertTrue($zip->getArchive()->close(), 'Successfully closed archive.');
        $this->assertFileExists($archiveFilename, 'Archive is automatically created if the file does not exist.');
      }
    

    Add extend from Drupal\KernelTests\Core\File\FileTestBase instead of KernelTestBase.

    We need to file a follow-up issue about Drupal\KernelTests\Core\File\FileTestBase not cleaning up after itself.

rahulrasgon’s picture

Status: Needs work » Needs review
StatusFileSize
new2.29 KB
new2.34 KB

@alexpott, Thank you for reviewing the patch.
I have updated the patch please.

rahulrasgon’s picture

StatusFileSize
new2.29 KB
new485 bytes

Fixed PHPLint Errors in #36.

idebr’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Archiver/Zip.php
@@ -23,12 +23,15 @@ class Zip implements ArchiverInterface {
+   * @param int $flags
+   *   (optional) The mode to use to open the archive. Defaults to create the
+   *   archive if it does not exist.
...
+  public function __construct($file_path, $flags = \ZipArchive::CREATE) {

Adding a new paramater to the constructor suggests this is something you can overwrite. However, since the plugin manager \Drupal\Core\Archiver\ArchiverManager::createInstance() only sends the $file_path this is not something you can change when creating a new plugin instance.

I suggest either:

  1. Pass a the full configuration object to the constructor, so the zip plugin can be instantiated with a different flag
  2. OR: Check the error code when calling $this->zip->open() https://www.php.net/manual/en/ziparchive.open.php and create a new zip on ZipArchive::ER_NOENT so the code matches what it says on the tin.
andypost’s picture

There's 2 different bugs

1) no api to create new archive, which could use to add createlist(array $files) as feature parity to \Archive_Tar, it looks better then exposing default value when extension could be missing

2) ability to exclude zip plugin if no zip extension installed in PHP or fallback somehow, better keeps in #3163123: Error: Class 'ZipArchive' not found in Drupal\Core\Archiver\Zip->__construct() (line 30 of core/lib/Drupal/Core/Archiver/Zip.php).

So the issue title could be fixed like "ArchiverZip has no method to create new archive"

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new2.67 KB

There's different approach, so test could be adopted from #37

Just always pass configuration to plugin

idebr’s picture

It seems the test coverage in #37 was lost in #40.

andypost’s picture

@idebr test from #37 is useless for this change, it needs extra test coverage for both plugins

andypost’s picture

My intent is start discussion of plugin config way to solve the issue
The changes in plugins is unnecessary and just shows example of benefits of this approach

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

collinhaines’s picture

StatusFileSize
new8.89 KB

I've been having to use touch($file_path); prior to running new Zip($file_path); because of not being able to pass flags. PHP 8.0 has deprecated opening an empty zip file:

Deprecated function: ZipArchive::open(): Using empty file as ZipArchive is deprecated

Continuing on #40's usage of having configuration via the plugin be each archiver constructor's second parameter, I've adjusted the $buffer in Tar to default to 512 to match the default value and to prevent a fread(): Length parameter must be greater than 0 error being thrown.

Left the fallback for $configuration['flags'] as 0 in Zip. PHP Documentation has it at zero although my intellisense has it as NULL. ZipArchive's constants appear to start at one so the chances of ZipArchive having a zero constant added in the future seems slim to none.

Continuing on #37's test coverage, I've added an overwrite test for the Zip archiver and a basic creation test for the Tar archiver, each extending a new ArchiverTestBase (that extends the previous FileTestBase). The tests themselves use direct Tar/Zip class constructions; however, the assertions go through the plugin - in an attempt to cover both ends.

andypost’s picture

@collinhaines great job! please fix documentation nitpicks, ++ rtbc

  1. +++ b/core/lib/Drupal/Core/Archiver/Tar.php
    @@ -21,11 +21,15 @@ class Tar implements ArchiverInterface {
    +   * @param array $configuration
    +   *   (Optional) settings to open archive.
    ...
    +  public function __construct($file_path, array $configuration = []) {
    +    $compress = $configuration['compress'] ?? NULL;
    +    $buffer = $configuration['buffer_length'] ?? 512;
    

    would be great to elaborate available for tar

  2. +++ b/core/lib/Drupal/Core/Archiver/Zip.php
    @@ -23,12 +23,14 @@ class Zip implements ArchiverInterface {
    +   * @param array $configuration
    +   *   (Optional) settings to open archive.
    ...
    -  public function __construct($file_path) {
    +  public function __construct($file_path, array $configuration = []) {
    ...
    +    if ($this->zip->open($file_path, $configuration['flags'] ?? 0) !== TRUE) {
    

    at least "flags" should be documented

collinhaines’s picture

StatusFileSize
new9.18 KB

Added in documentation for the compress, buffer_length, and flags keys.

andypost’s picture

StatusFileSize
new278 bytes
new9.45 KB

Thank you @collinhaines - fixing cspell

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

nod_’s picture

Status: Needs review » Reviewed & tested by the community

RTBC per #49 doc issues were addressed

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

The current solution looks really nice. Adding more configuration options to the plugins is a great idea. But we need a change record to tell devs about this.

andypost’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record
hchonov’s picture

Status: Needs review » Reviewed & tested by the community

Now that we have the CR this can go back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 70700fc and pushed to 10.1.x. Thanks!

  • alexpott committed 70700fcc on 10.1.x
    Issue #2850794 by cilefen, rahulrasgon, borisson_, andypost,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.