node-archiver icon indicating copy to clipboard operation
node-archiver copied to clipboard

Unzip archived file get "End-of-central-directory signature not found"

Open jiangdonghan opened this issue 4 years ago • 9 comments

Hi, i got unzip error "End-of-central-directory signature not found" when i use unzip command on mac os , also i tried directly open the zip file using mac os default archive utility,i got "Error 97 - Inappropriate File Type or Format". Howerver, I downloaded other archive apps and i can unzip this file.

My device: macbook pro m1 version node-archiver version: 5.3.0

jiangdonghan avatar Dec 01 '21 06:12 jiangdonghan

Got the same error here.

veronicawonghy avatar Feb 27 '22 03:02 veronicawonghy

Macbook pro 16 Intel also have the same problem. MacOS: 11.6.5 default zip app. And it works with The Unarchive. I still get error: corrupted files, but I select continue and voila

kyakaze avatar May 30 '22 12:05 kyakaze

The thing that fixed this for us was waiting for the write stream to close after calling archive.finalize().

await archive.finalize();

await new Promise((resolve, reject) => {
  output.on("close", resolve);
  output.on("error", reject);
});

I'd say this should be handled internally by the finalize() method but is probably not right now.

ifeltsweet avatar Sep 27 '23 19:09 ifeltsweet

waiting for the write stream to close

Unfortunately that didn't work for me. I wait on the out stream, the archive, and the finalize...

    const output = await fs.open(path.join(outdir, name), 'w');
    const outStream = output.createWriteStream();

    const archive = archiver.create('zip', {
      zlib: { level: 9 },
    });

    promises.push(
      new Promise((resolve, reject) => {
        outStream.on('close', () => {
          resolve(undefined);
        });

        outStream.on('error', (e) => {
          reject(e);
        });
      }),
    );

    promises.push(
      new Promise((resolve, reject) => {
        archive.on('error', (e) => {
          console.error(e);
          reject(e);
        });

        archive.on('finish', () => {
          resolve(undefined);
        });
      }),
    );

    archive.pipe(outStream);
    archive.append(Buffer.from(file.contents), { name: file.path });
    promises.push(archive.finalize());

    await Promise.all(promises);

The example code for making a .zip doesn't even work on Mac (tried node 16 and 18, in case it made a difference):

$ node pack-zip.js

$ unzip example-output.zip
Archive:  example-output.zip
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.

Also maybe worth mentioning that not only can the archives not be decompressed on Mac, but AWS also can't extraxt them if you try to use it to upload code to Lambdas

operation error Lambda: CreateFunction, https response error StatusCode: 400, RequestID: f90296d2-36c6-4d4f-a541-0c0ef5ac0f10, InvalidParameterValueException: Could not unzip uploaded file. Please check your file, then try to upload again.

jvalore-medallia avatar Jan 11 '24 13:01 jvalore-medallia

waiting for the write stream to close

Unfortunately that didn't work for me. I wait on the out stream, the archive, and the finalize...

    const output = await fs.open(path.join(outdir, name), 'w');
    const outStream = output.createWriteStream();

    const archive = archiver.create('zip', {
      zlib: { level: 9 },
    });

    promises.push(
      new Promise((resolve, reject) => {
        outStream.on('close', () => {
          resolve(undefined);
        });

        outStream.on('error', (e) => {
          reject(e);
        });
      }),
    );

    promises.push(
      new Promise((resolve, reject) => {
        archive.on('error', (e) => {
          console.error(e);
          reject(e);
        });

        archive.on('finish', () => {
          resolve(undefined);
        });
      }),
    );

    archive.pipe(outStream);
    archive.append(Buffer.from(file.contents), { name: file.path });
    promises.push(archive.finalize());

    await Promise.all(promises);

The example code for making a .zip doesn't even work on Mac (tried node 16 and 18, in case it made a difference):

$ node pack-zip.js

$ unzip example-output.zip
Archive:  example-output.zip
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.

Also maybe worth mentioning that not only can the archives not be decompressed on Mac, but AWS also can't extraxt them if you try to use it to upload code to Lambdas

operation error Lambda: CreateFunction, https response error StatusCode: 400, RequestID: f90296d2-36c6-4d4f-a541-0c0ef5ac0f10, InvalidParameterValueException: Could not unzip uploaded file. Please check your file, then try to upload again.

Did that ever get fixed? I think I'm having the same issue on macOS. It just doesn't work at all. Anything this package compresses is corrupted.

tbergeron avatar Feb 15 '24 17:02 tbergeron

➜  /tmp unzip generated-output_1707207217.zip                                                             [01:32:03]
Archive:  generated-output_1707207217.zip
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of generated-output_1707207217.zip or
        generated-output_1707207217.zip.zip, and cannot find generated-output_1707207217.zip.ZIP, period.

tbergeron avatar Feb 15 '24 17:02 tbergeron

Tested on my Ubuntu server and it worked right away. IT IS a problem with macOS zlib version 😠

tbergeron avatar Feb 15 '24 18:02 tbergeron

For anyone looking for a way to move forward with generating zip files, I eventually realized this library just wrappers https://github.com/archiverjs/node-zip-stream when set to zip archive type, so I just used that library directly instead.

While trying to make zip-stream work, I also realized that if you are using createWriteStream from the standard node fs library, it uses old school node event emitters, which so not work when in a JS async function. Normally Node will exit when the work queue is empty but the old school node .on( event handlers do not put themselves on that work queue. If you do

        stream.on('finish', () => {

Node will not wait for that finish handler to be called (because it might never be called). Your code needs to sit and wait for it. If you have an async block, like in my code above where I try to wrapper things in a Promise, node will just exit after that because it doesnt care about waiting on that .on( event emitter handler. The exit of Node leaves the .zip file half written / corrupted.

Interestingly, archiver's finalize() method returns a Promise with an event handler set up inside it. https://github.com/archiverjs/node-archiver/blob/master/lib/core.js#L791-L798 I don't see how this possibly works, or maybe works depending on if your system can write the zip file and flush the i/o buffer fast enough to finish before node exits.

Node would run finalize which adds an event handler

self._module.on('end', function() {
      if (!errored) {
        resolve();
      }
    })

then returns, and now there is nothing left on the work steam so the process would exit. (potentially before the end handler is run, leaving the file corrupt).

jvalore-medallia avatar Feb 15 '24 19:02 jvalore-medallia