5

I am writing software which, among other things, downloads a zip archive using the Dropbox API and then unzips that archive using yauzl.

The way the files are stored and downloaded from DB often ends up with nested folders, and I need to keep it that way.

However my implementation of yauzl is not capable of unzipping while keeping that nested folder structure, if there is a nested folder in the archive it does not unzip at all.

Here is my unzip function, which is the default yauzl example with the addition of local file write at the end.

const unzip = () => {
    let zipPath = "pathToFile.zip"
    let extractPath = "pathToExtractLocation"

        yauzl.open(zipPath, {lazyEntries: true}, function(err, zipfile) {
            if (err) throw err;
            zipfile.readEntry();
            zipfile.on("entry", function(entry) {
            if (/\/$/.test(entry.fileName)) {
                // Directory file names end with '/'.
                // Note that entries for directories themselves are optional.
                // An entry's fileName implicitly requires its parent directories to exist.
                zipfile.readEntry();
            } else {
                // file entry
                zipfile.openReadStream(entry, function(err, readStream) {
                if (err) throw err;
                readStream.on("end", function() {
                    zipfile.readEntry();
                });
                const writer = fs.createWriteStream(path.join(extractPath, entry.fileName));
                readStream.pipe(writer);
                });
            }
            });
        });
}

Removing the if (/\/$/.test(entry.fileName)) check treats the top level folder as a file, extracting it with no file extension and 0kb size. What I want it to do is extract the archive including subfolders (to at least a depth of 2, being aware of the risk of zip bombing).

Is that possible using yauzl?

KoG
  • 51
  • 2

1 Answers1

2

The code needs to create the directory tree at the extract path. You may use fs.mkdir with the recursive option to ensure that a directory exists before extract to it.

if (/\/$/.test(entry.fileName)) {
  // Directory file names end with '/'.
  // Note that entries for directories themselves are optional.
  // An entry's fileName implicitly requires its parent directories to exist.
  zipfile.readEntry();
} else {
  // file entry
  fs.mkdir(
    path.join(extractPath, path.dirname(entry.fileName)),
    { recursive: true },
    (err) => {
      if (err) throw err;
      zipfile.openReadStream(entry, function (err, readStream) {
        if (err) throw err;
        readStream.on("end", function () {
          zipfile.readEntry();
        });
        const writer = fs.createWriteStream(
          path.join(extractPath, entry.fileName)
        );
        readStream.pipe(writer);
      });
    }
  );
}
Hugo Wood
  • 2,140
  • 15
  • 19
  • This is the correct answer. `fs.createWriteStream` will not create a directory tree for you. In fact, you should get an error when trying to create a writable stream to a folder which doesn't exist. Make sure you listen for errors on the writer (and the readStream): `writer.on('error', ...); readStream.on('error', ...);` – Ryan Wheale Apr 21 '22 at 15:31