yauzl vs archiver vs adm-zip vs yazl vs zip-lib vs node-zip
Choosing the Right ZIP Library for Node.js Applications
yauzlarchiveradm-zipyazlzip-libnode-zipSimilar Packages:

Choosing the Right ZIP Library for Node.js Applications

These libraries handle ZIP file creation and extraction in Node.js, but they serve different architectural needs. archiver is the industry standard for streaming large files without crashing memory. adm-zip offers a simple, synchronous API for small, local files but struggles with streams. yauzl and yazl are low-level, high-performance tools built specifically for streaming data, often used as building blocks for other tools. node-zip is deprecated and should be avoided. zip-lib provides a modern, promise-based wrapper around native bindings, useful when you need specific compression features not found in pure JS solutions.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
yauzl42,268,715815110 kB12a month agoMIT
archiver31,609,8462,97039.6 kB1652 months agoMIT
adm-zip17,877,8592,172140 kB528 days agoMIT
yazl2,866,04838058.7 kB202 years agoMIT
zip-lib140,1824568.8 kB1a month agoMIT
node-zip97,106217-1911 years ago-

Node.js ZIP Libraries: Streaming vs. Memory Trade-offs

Handling ZIP files in Node.js is a common requirement, from bundling assets for download to processing user uploads. However, not all ZIP libraries are built the same. The critical difference lies in how they handle data: do they load the whole file into memory, or do they stream it piece by piece? Let's break down the technical realities of adm-zip, archiver, node-zip, yauzl, yazl, and zip-lib.

🚨 Deprecation Warning: node-zip

Before diving into the viable options, we must address node-zip. This package is deprecated and no longer maintained. It relies on older native bindings that often fail to compile on modern Node.js versions.

// āŒ DO NOT USE
// const zip = require('node-zip'); 
// This package is abandoned and poses security risks.

Recommendation: If you see node-zip in a legacy codebase, plan a migration to archiver or yazl immediately. Do not start new projects with it.

šŸ’¾ Memory Model: Loading All vs. Streaming

The most important architectural decision is between loading the entire file into RAM (Buffer) versus processing it as a Stream.

adm-zip loads everything into memory. It is synchronous and blocking by default.

  • Great for small files (< 50MB) or local CLI tools.
  • Dangerous for web servers handling large files; it can crash your process with an "Out of Memory" error.
// adm-zip: Loads entire file into RAM
const AdmZip = require('adm-zip');
const zip = new AdmZip();

// Adds file to memory buffer immediately
zip.addLocalFile("./large-video.mp4"); 

// Blocks the event loop until writing is done
zip.writeZip("./output.zip"); 

archiver uses streams. It processes data in chunks.

  • Ideal for HTTP responses, large file processing, and pipes.
  • Keeps memory usage low regardless of file size.
// archiver: Streams data directly to disk or network
const archiver = require('archiver');
const output = require('fs').createWriteStream('./output.zip');
const archive = archiver('zip');

output.on('close', () => console.log('Done'));
archive.pipe(output);

// Streams file content without loading it all into RAM
archive.file('./large-video.mp4', { name: 'video.mp4' }); 
archive.finalize();

yazl and yauzl are also stream-based but offer a lower-level API.

  • yazl (writer) and yauzl (reader) give you raw control over the ZIP structure.
  • They are lighter than archiver but require you to manage more details manually.
// yazl: Low-level streaming write
const yazl = require('yazl');
const zipFile = new yazl.ZipFile();
const output = require('fs').createWriteStream('./output.zip');

zipFile.outputStream.pipe(output);

// You must explicitly end the stream
zipFile.addFile('./large-video.mp4', 'video.mp4');
zipFile.end();

zip-lib attempts to bridge the gap with a Promise-based API, often wrapping native libraries.

  • It offers async/await syntax which is cleaner than callbacks.
  • Still generally loads more into memory than pure streams but less than adm-zip.
// zip-lib: Promise-based API
const { zip, unzip } = require('zip-lib');

// Returns a promise, non-blocking
await zip(['./large-video.mp4'], './output.zip');

šŸ“¦ Adding Files: Simplicity vs. Control

How you add files to the archive varies significantly between high-level and low-level libraries.

adm-zip makes it trivial to add local folders recursively.

  • One line of code handles directory traversal.
// adm-zip: Recursive folder add
const zip = new AdmZip();
// Automatically adds all files in 'src' folder
zip.addLocalFolder("./src", "dist/src"); 

archiver also supports recursive directory appending easily via plugins or built-in methods.

  • It maintains the stream benefit while offering high-level convenience.
// archiver: Recursive directory append
const archive = archiver('zip');
// Streams all files in 'src' folder
archive.directory('./src', 'dist/src'); 
archive.finalize();

yazl requires you to manually handle file discovery if you want to add a folder.

  • You typically pair it with readdir or glob to find files first.
  • This adds code complexity but gives you exact control over what gets included.
// yazl: Manual file addition (no built-in recursive folder add)
const fs = require('fs');
const path = require('path');
const yazl = require('yazl');
const zipFile = new yazl.ZipFile();

// You must manually iterate files
const files = ['file1.txt', 'file2.txt']; 
files.forEach(f => zipFile.addFile(f, path.basename(f)));

zipFile.end();

zip-lib provides a clean array-based input for multiple files.

  • Simpler than yazl for multiple files but less flexible than archiver for complex glob patterns.
// zip-lib: Array of files
const { zip } = require('zip-lib');
// Zips specific files listed in array
await zip(['file1.txt', 'file2.txt'], './output.zip');

šŸ“„ Extraction: Reading ZIP Files

Reading archives has similar trade-offs between ease of use and memory safety.

adm-zip extracts synchronously to the disk.

  • Simple for build scripts, but blocks the server thread.
// adm-zip: Sync extraction
const zip = new AdmZip("./archive.zip");
// Blocks execution until all files are written
zip.extractAllTo("./target-folder", true); 

yauzl is the gold standard for streaming extraction.

  • It emits events for each entry, allowing you to pipe data directly to a destination stream.
  • Essential for processing large uploads without disk I/O bottlenecks.
// yauzl: Streaming extraction
const yauzl = require('yauzl');

yauzl.open("./archive.zip", { lazyEntries: true }, (err, zipfile) => {
  zipfile.on('entry', (entry) => {
    zipfile.openReadStream(entry, (err, readStream) => {
      // Pipe directly to file or processing logic
      readStream.pipe(require('fs').createWriteStream(entry.fileName));
      zipfile.readEntry(); // Read next entry
    });
  });
});

archiver is primarily for creation. For extraction, you would typically use yauzl or unzip-stream alongside it.

  • zip-lib offers a simple async extract function similar to adm-zip but non-blocking.
// zip-lib: Async extraction
const { unzip } = require('zip-lib');
// Returns a promise, doesn't block the event loop
await unzip('./archive.zip', './target-folder');

🌐 Real-World Scenarios

Scenario 1: "Download Report" API Endpoint

You need to generate a ZIP of 500MB of logs and send it to the browser.

  • āœ… Best Choice: archiver
  • Why? You can pipe the archive directly to the HTTP response (res). adm-zip would try to load 500MB into RAM, likely crashing your server instance.
// Express example with archiver
app.get('/download', (req, res) => {
  const archive = archiver('zip');
  res.setHeader('Content-Type', 'application/zip');
  res.setHeader('Content-Disposition', 'attachment; filename=logs.zip');
  
  archive.pipe(res); // Stream directly to client
  archive.directory('/var/logs', false);
  archive.finalize();
});

Scenario 2: Local Build Script / CLI Tool

You are writing a command-line tool to bundle a small project folder (under 10MB) for deployment.

  • āœ… Best Choice: adm-zip
  • Why? Simplicity wins here. You don't need streams for small local files. The synchronous API makes the script logic linear and easy to debug.
// CLI script with adm-zip
const zip = new AdmZip();
zip.addLocalFolder('./dist');
zip.writeZip('./release.zip');
console.log('Build complete!');

Scenario 3: Serverless Function (AWS Lambda)

You need to unzip a user upload in a Lambda function where cold start time and bundle size matter.

  • āœ… Best Choice: yauzl / yazl
  • Why? These libraries have zero native dependencies and are very small. zip-lib might require compiling native binaries which can be tricky in serverless environments depending on the runtime.
// Lambda handler with yauzl
exports.handler = async (event) => {
  // Process stream efficiently with minimal bundle size
  // ... implementation using yauzl events ...
};

šŸ“Š Summary Table

Featureadm-ziparchiveryauzl / yazlzip-libnode-zip
Primary ModeSynchronous / BufferStreamingStreamingAsync / PromiseNative / Deprecated
Memory UsageHigh (Loads all)Low (Chunks)Low (Chunks)MediumHigh
Ease of Use⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Recursive DirBuilt-inBuilt-inManualLimitedBuilt-in
Best ForCLI, Small FilesWeb Servers, Large FilesHigh Perf, Custom LogicModern Async AppsNone (Avoid)

šŸ’” Final Recommendation

The choice comes down to data size and execution context.

If you are building a web server or handling large files, archiver is the safest and most robust choice. Its streaming architecture protects your application from memory spikes, and its API is mature enough to handle complex directory structures without much fuss.

If you are writing small scripts, CLI tools, or dealing with tiny files locally, adm-zip remains the king of simplicity. Its synchronous API removes the need for complex async/await chains for trivial tasks.

For specialized high-performance needs or serverless environments where every kilobyte of bundle size counts, the yauzl/yazl duo provides the leanest, most efficient foundation, though it demands more code from the developer.

Avoid node-zip entirely. It belongs to the past. For a modern, promise-based experience with native speed, zip-lib is a solid alternative if archiver feels too heavy for your specific use case.

How to Choose: yauzl vs archiver vs adm-zip vs yazl vs zip-lib vs node-zip

  • yauzl:

    Choose yauzl when you need maximum performance and control for reading (extracting) ZIP files via streams. It is ideal for backend services that process uploaded archives where memory efficiency is critical. Be prepared to write more boilerplate code compared to higher-level libraries like adm-zip.

  • archiver:

    Choose archiver when building web servers, APIs, or data pipelines that need to stream ZIP files directly to the user or disk. It is the best choice for handling large datasets because it processes data in chunks, preventing memory crashes. Its plugin ecosystem also makes it ideal if you need to support formats beyond standard ZIP.

  • adm-zip:

    Choose adm-zip for simple scripts, CLI tools, or build steps where you need to zip small folders synchronously and don't care about streaming. It is perfect for local file manipulation where the entire file fits comfortably in RAM. Avoid it for web servers handling large uploads or downloads, as it loads everything into memory at once.

  • yazl:

    Choose yazl when you need a lightweight, pure-JavaScript solution for creating ZIP files via streams without the extra features of archiver. It is perfect for microservices or serverless functions where bundle size matters and you only need basic ZIP creation capabilities with high efficiency.

  • zip-lib:

    Choose zip-lib if you require a modern, Promise-based API and need access to native compression algorithms for better speed or ratio. It is a good middle ground if you find archiver too complex but need more robustness than adm-zip offers for medium-sized files.

  • node-zip:

    Do NOT choose node-zip for any new project. It is officially deprecated and no longer maintained. Using it introduces security risks and compatibility issues with modern Node.js versions. Migrate existing projects to archiver or yazl immediately.

README for yauzl

yauzl

yet another unzip library for node. For zipping, see yazl.

Design principles:

  • Follow the spec. Don't scan for local file headers. Read the central directory for file metadata. (see No Streaming Unzip API).
  • Don't block the JavaScript thread. Use and provide async APIs.
  • Keep memory usage under control. Don't attempt to buffer entire files in RAM at once.
  • Never crash (if used properly). Don't let malformed zip files bring down client applications who are trying to catch errors.
  • Catch unsafe file names. See validateFileName().

Usage

Using await:

const yauzl = require("yauzl");

(async () => {
  try {
    const zipfile = await yauzl.openPromise("path/to/file.zip");
    for await (let entry of zipfile.eachEntry()) {
      if (entry.fileName.endsWith("/")) {
        // Directory file names end with '/'.
        // Note that entries for directories themselves are optional.
        // An entry's fileName implicitly requires its parent directories to exist.
        continue;
      } else {
        // file entry
        const readStream = await zipfile.openReadStreamPromise(entry);
        await stream.promises.pipeline(readStream, somewhere);
      }
    }
  } catch (err) {
    // Indicates a malformed zipfile or I/O error.
    throw err;
  }
})();

Using callbacks:

var yauzl = require("yauzl");

yauzl.open("path/to/file.zip", {lazyEntries: true}, function(err, zipfile) {
  if (err) throw err;
  zipfile.readEntry();
  zipfile.on("entry", function(entry) {
    if (entry.fileName.endsWith("/")) {
      // Ignore directory entry.
      zipfile.readEntry();
    } else {
      // file entry
      zipfile.openReadStream(entry, function(err, readStream) {
        if (err) throw err;
        readStream.on("end", function() {
          zipfile.readEntry();
        });
        readStream.pipe(somewhere);
      });
    }
  });
});

See also examples/ for more usage examples.

API

The default for every optional callback parameter is:

function defaultCallback(err) {
  if (err) throw err;
}

open(path, [options], [callback])

Calls fs.open(path, "r") and reads the fd effectively the same as fromFd() would.

See also openPromise() to get Promise semantics.

options may be omitted or null. The defaults are {autoClose: true, lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}.

autoClose is effectively equivalent to:

zipfile.once("end", function() {
  zipfile.close();
});

lazyEntries indicates that entries should be read only when readEntry() is called. If lazyEntries is false, entry events will be emitted as fast as possible to allow pipe()ing file data from all entries in parallel. This is not recommended, as it can lead to out of control memory usage for zip files with many entries. See issue #22. If lazyEntries is true, an entry or end event will be emitted in response to each call to readEntry(). This allows processing of one entry at a time, and will keep memory usage under control for zip files with many entries.

decodeStrings is the default and causes yauzl to decode strings with CP437 or UTF-8 as required by the spec. The exact effects of turning this option off are:

  • zipfile.comment, entry.fileName, and entry.fileComment will be Buffer objects instead of Strings.
  • Any Info-ZIP Unicode Path Extra Field will be ignored. See extraFields.
  • Automatic file name validation will not be performed. See validateFileName().

validateEntrySizes is the default and ensures that an entry's reported uncompressed size matches its actual uncompressed size. This check happens as early as possible, which is either before emitting each "entry" event (for entries with no compression), or during the readStream piping after calling openReadStream(). See openReadStream() for more information on defending against zip bomb attacks.

When strictFileNames is false (the default) and decodeStrings is true, all backslash (\) characters in each entry.fileName are replaced with forward slashes (/). The spec forbids file names with backslashes, but Microsoft's System.IO.Compression.ZipFile class in .NET versions 4.5.0 until 4.6.1 creates non-conformant zipfiles with backslashes in file names. strictFileNames is false by default so that clients can read these non-conformant zipfiles without knowing about this Microsoft-specific bug. When strictFileNames is true and decodeStrings is true, entries with backslashes in their file names will result in an error. See validateFileName(). When decodeStrings is false, strictFileNames has no effect.

The callback is given the arguments (err, zipfile). An err is provided if the End of Central Directory Record cannot be found, or if its metadata appears malformed. This kind of error usually indicates that this is not a zip file. Otherwise, zipfile is an instance of ZipFile.

fromFd(fd, [options], [callback])

Reads from the fd, which is presumed to be an open .zip file. Note that random access is required by the zip file specification, so the fd cannot be an open socket or any other fd that does not support random access.

See also fromFdPromise() to get Promise semantics.

options may be omitted or null. The defaults are {autoClose: false, lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}.

See open() for the meaning of the options and callback.

fromBuffer(buffer, [options], [callback])

Like fromFd(), but reads from a RAM buffer instead of an open file. buffer is a Buffer.

See also fromBufferPromise() to get Promise semantics.

If a ZipFile is acquired from this method, it will never emit the close event, and calling close() is not necessary.

options may be omitted or null. The defaults are {lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}.

See open() for the meaning of the options and callback. The autoClose option is ignored for this method.

fromRandomAccessReader(reader, totalSize, [options], [callback])

This method of reading a zip file allows clients to implement their own back-end file system. For example, a client might translate read calls into network requests.

See also fromRandomAccessReaderPromise() to get Promise semantics.

The reader parameter must be of a type that is a subclass of RandomAccessReader that implements the required methods. The totalSize is a Number and indicates the total file size of the zip file.

options may be omitted or null. The defaults are {autoClose: true, lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}.

See open() for the meaning of the options and callback.

openPromise(path, [options])

Wraps open() with Promise semantics. The returned promise is rejected if the callback for open() receives an err, otherwise the promise is resolved with the zipfile.

options may be omitted or null, and path and options are passed to open(). options.lazyEntries is always set to true, overriding any value you pass in. This is required for using zipfile.eachEntry(), which you probably also want to use.

fromFdPromise(fd, [options])

Wraps fromFd() with Promise semantics. The returned promise is rejected if the callback for fromFd() receives an err, otherwise the promise is resolved with the zipfile.

options may be omitted or null, and fd and options are passed to fromFd(). options.lazyEntries is always set to true, overriding any value you pass in. This is required for using zipfile.eachEntry(), which you probably also want to use.

fromBufferPromise(buffer, [options])

Wraps fromBuffer() with Promise semantics. The returned promise is rejected if the callback for fromBuffer() receives an err, otherwise the promise is resolved with the zipfile.

options may be omitted or null, and buffer and options are passed to fromBuffer(). options.lazyEntries is always set to true, overriding any value you pass in. This is required for using zipfile.eachEntry(), which you probably also want to use.

fromRandomAccessReaderPromise(reader, totalSize, [options])

Wraps fromRandomAccessReader() with Promise semantics. The returned promise is rejected if the callback for fromRandomAccessReader() receives an err, otherwise the promise is resolved with the zipfile.

options may be omitted or null, and reader, totalSize, and options are passed to fromRandomAccessReader(). options.lazyEntries is always set to true, overriding any value you pass in. This is required for using zipfile.eachEntry(), which you probably also want to use.

dosDateTimeToDate(date, time)

Deprecated. Since yauzl 3.2.0, it is highly recommended to call entry.getLastModDate() instead of this function due to enhanced support for reading third-party extra fields. If you ever have a use case for calling this function directly please open an issue against yauzl requesting that this function be properly supported again.

This function only remains exported in order to maintain compatibility with older versions of yauzl. It will be removed in yauzl 4.0.0 unless someone asks for it to remain supported.

getFileNameLowLevel(generalPurposeBitFlag, fileNameBuffer, extraFields, strictFileNames)

If you are setting decodeStrings to false, then this function can be used to decode the file name yourself. This function is effectively used internally by yauzl to populate the entry.fileName field when decodeStrings is true.

WARNING: This method of getting the file name bypasses the security checks in validateFileName(). You should call that function yourself to be sure to guard against malicious file paths.

generalPurposeBitFlag can be found on an Entry or LocalFileHeader. Only General Purpose Bit 11 is used, and only when an Info-ZIP Unicode Path Extra Field cannot be found in extraFields.

fileNameBuffer is a Buffer representing the file name field of the entry. This is entry.fileNameRaw or localFileHeader.fileName.

extraFields is the parsed extra fields array from entry.extraFields or parseExtraFields().

strictFileNames is a boolean, the same as the option of the same name in open(). When false, backslash characters (\) will be replaced with forward slash characters (/).

This function always returns a string, although it may not be a valid file name. See validateFileName().

validateFileName(fileName)

Returns null or a String error message depending on the validity of fileName. If fileName starts with "/" or /[A-Za-z]:\// or if it contains ".." path segments or "\\", this function returns an error message appropriate for use like this:

var errorMessage = yauzl.validateFileName(fileName);
if (errorMessage != null) throw new Error(errorMessage);

This function is automatically run for each entry, as long as decodeStrings is true. See open(), strictFileNames, and Event: "entry" for more information.

parseExtraFields(extraFieldBuffer)

This function is used internally by yauzl to compute entry.extraFields. It is exported in case you want to call it on localFileHeader.extraField.

extraFieldBuffer is a Buffer, such as localFileHeader.extraField. Returns an Array with each item in the form {id: id, data: data}, where id is a Number and data is a Buffer. Throws an Error if the data encodes an item with a size that exceeds the bounds of the buffer.

You may want to surround calls to this function with try { ... } catch (err) { ... } to handle the error.

Class: ZipFile

The constructor for the class is not part of the public API. Use open(), fromFd(), fromBuffer(), or fromRandomAccessReader() instead.

Event: "entry"

Callback gets (entry), which is an Entry. See open() and readEntry() for when this event is emitted. See also eachEntry() for an async iterator alternative to this event.

If decodeStrings is true, entries emitted via this event have already passed file name validation. See validateFileName() and open() for more information.

If validateEntrySizes is true and this entry's compressionMethod is 0 (stored without compression), this entry has already passed entry size validation. See open() for more information.

Event: "end"

Emitted after the last entry event has been emitted. See open() and readEntry() for more info on when this event is emitted. See also eachEntry() for an async iterator alternative to this event.

Event: "close"

Emitted after the fd is actually closed. This is after calling close() (or after the end event when autoClose is true), and after all stream pipelines created from openReadStream() have finished reading data from the fd.

If this ZipFile was acquired from fromRandomAccessReader(), the "fd" in the previous paragraph refers to the RandomAccessReader implemented by the client.

If this ZipFile was acquired from fromBuffer(), this event is never emitted.

Event: "error"

Emitted in the case of errors with reading the zip file. (Note that other errors can be emitted from the streams created from openReadStream() as well.) After this event has been emitted, no further entry, end, or error events will be emitted, but the close event may still be emitted.

See also eachEntry() for an async iterator alternative to this event.

readEntry()

Causes this ZipFile to emit an entry or end event (or an error event). This method must only be called when this ZipFile was created with the lazyEntries option set to true (see open()), and must not be called if you are using eachEntry(). When this ZipFile was created with the lazyEntries option set to true, entry and end events are only ever emitted in response to this method call.

See also eachEntry() for an async iterator alternative to this method.

The event that is emitted in response to this method will not be emitted until after this method has returned, so it is safe to call this method before attaching event listeners.

After calling this method, calling this method again before the response event has been emitted will cause undefined behavior. Calling this method after the end event has been emitted will cause undefined behavior. Calling this method after calling close() will cause undefined behavior.

eachEntry()

Returns an async iterable/iterator that yields each entry in this ZipFile. This allows using for await (let entry of zipfile.eachEntry()) { ... } to iterate through the Entry objects that would otherwise be emitted via Event: "entry".

The object returned from eachEntry() implements next(), return(), and throw() satisfying the async iterator API, and implements [Symbol.asyncIterator](), which simply returns this, satisfying the async iterable API.

next() returns a promise which is eventually either rejected with an error, or resolves to one of:

  • {value: entry} with the next Entry, analogous to Event: "entry", or
  • {done: true} signaling the end of iteration, analogous to Event: "end".

You must abstain from calling next() again until the promise returned from the previous call to next() has resolved. This is how for await...of and Array.fromAsync() work.

If autoClose is true, this ZipFile is closed at the end of iteration, or when iteration is interrupted by return(), which is called implicitly when exiting a for await...of loop with break, return, etc. If you want to still call openReadStream() after iteration has ended (such as when using Array.fromAsync() to collect entries all ahead of time), set autoClose to false, and call close() manually when you are done with the ZipFile. There is no supported way to resume iteration if it is interrupted with return().

Using eachEntry() is incompatible with older styles of accessing this ZipFile. If you use eachEntry():

  • lazyEntries must be true (see open()). (Some APIs set this automatically for you.)
  • You must never call readEntry() on this ZipFile.
  • It is not recommend to listen for Event: "entry" or Event: "end", because that information is fully provided by the async iterator instead.
  • It is not recommended to listen for Event: "error" on this ZipFile, but rather handle errors conveyed through the Promise API, such as with a try...catch block around the for await...of expression, or with the catch() method on each promise returned by next(), etc.
  • You must not call eachEntry() more than once. The iteration state is maintained in the ZipFile object itself, not in the iterator, and it is not possible to reset iteration to get the same Entry objects more than once.

See examples/promises.js for an example using this method.

openReadStream(entry, [options], callback)

entry must be an Entry object from this ZipFile. callback gets (err, readStream), where readStream is a Readable Stream that provides the file data for this entry. If this zipfile is already closed (see close()), the callback will receive an err.

See also openReadStreamPromise() to get Promise semantics.

options may be omitted or null, and has the following defaults:

{
  decodeFileData: true,
  start: 0,                  // actually the default is null, see below
  end: entry.compressedSize, // actually the default is null, see below
  decompress: null, // deprecated
  decrypt: null,    // deprecated
}

When decodeFileData is true (or null or omitted), yauzl will attempt to decode the file data using a zlib inflate transform when appropriate. Setting decodeFileData to false will provide the bytes of the entry's contents exactly as-is in the archive. When compressionMethod is 0 (stored with no compression) and the entry is not encrypted, decodeFileData has no effect.

If canDecodeFileData() returns false, then decodeFileData must be set to false, or else callback will receive an error. It is up to the caller to make sense of any file data with encryption and/or an uncommon compression method.

The start (inclusive) and end (exclusive) options are byte offsets into this entry's file data, and can be used to obtain part of an entry's file data rather than the whole thing. When specifying start and/or end, it is recommended to unconditionally set decodeFileData to false. If either start and/or end are specified and non-null, then either decodeFileData must be set to false, or both isEncrypted() must return false and compressionMethod must be 0. Specifying {start: 0, end: entry.compressedSize} will result in the complete file.

For compatibility with versions of yauzl prior to 3.3.x, start and end can also be used when the deprecated decompress and decrypt options are used appropriately. This is no longer documented, but an archive of the documentation can be found here: https://github.com/thejoshwolfe/yauzl/blob/c4695215b05c6adffda613b9051a2a85429b33fe/README.md#openreadstreamentry-options-callback

It's possible for the readStream provided to the callback to emit errors for several reasons. For example, if zlib cannot decompress the data, the zlib error will be emitted from the readStream. Two more error cases (when validateEntrySizes is true) are if the decompressed data has too many or too few actual bytes compared to the reported byte count from the entry's uncompressedSize field. yauzl notices this false information and emits an error from the readStream after some number of bytes have already been piped through the stream.

This check allows clients to trust the uncompressedSize field in Entry objects. Guarding against zip bomb attacks can be accomplished by doing some heuristic checks on the size metadata and then watching out for the above errors. Such heuristics are outside the scope of this library, but enforcing the uncompressedSize is implemented here as a security feature.

It is possible to destroy the readStream before it has piped all of its data. To do this, call readStream.destroy(). You must unpipe() the readStream from any destination before calling readStream.destroy(). If this zipfile was created using fromRandomAccessReader(), the RandomAccessReader implementation must provide readable streams that implement a ._destroy() method according to https://nodejs.org/api/stream.html#writable_destroyerr-callback (see randomAccessReader._readStreamForRange()) in order for calls to readStream.destroy() to work in this context.

readLocalFileHeader(entry, [options], callback)

This is a low-level function you probably don't need to call. The intended use case is either preparing to call openReadStreamLowLevel() or simply examining the content of the local file header out of curiosity or for debugging zip file structure issues.

entry is an entry obtained from Event: "entry". An entry in this library is a file's metadata from a Central Directory Header, and this function gives the corresponding redundant data in a Local File Header.

options may be omitted or null, and has the following defaults:

{
  minimal: false,
}

If minimal is false (or omitted or null), the callback receives a full LocalFileHeader. If minimal is true, the callback receives an object with a single property and no prototype {fileDataStart: fileDataStart}. For typical zipfile reading usecases, this field is the only one you need, and yauzl internally effectively uses the {minimal: true} option as part of openReadStream().

The callback receives (err, localFileHeaderOrAnObjectWithJustOneFieldDependingOnTheMinimalOption), where the type of the second parameter is described in the above discussion of the minimal option.

openReadStreamLowLevel(fileDataStart, compressedSize, relativeStart, relativeEnd, decompress, uncompressedSize, callback)

This is a low-level function available for advanced use cases. You probably want openReadStream() instead.

See also openReadStreamLowLevelPromise() to get Promise semantics.

The intended use case for this function is calling readEntry() and readLocalFileHeader() with {minimal: true} first, and then opening the read stream at a later time, possibly after closing and reopening the entire zipfile, possibly even in a different process. The parameters are all integers and booleans, which are friendly to serialization.

  • fileDataStart - from localFileHeader.fileDataStart
  • compressedSize - from entry.compressedSize
  • relativeStart - the resolved value of options.start from openReadStream(). Must be a non-negative integer, not null. Typically 0 to start at the beginning of the data.
  • relativeEnd - the resolved value of options.end from openReadStream(). Must be a non-negative integer, not null. Typically entry.compressedSize to include all the data.
  • decompress - boolean indicating whether the data should be piped through a zlib inflate stream.
  • uncompressedSize - from entry.uncompressedSize. Only used when validateEntrySizes is true. If validateEntrySizes is false, this value is ignored, but must still be present, not omitted, in the arguments; you have to give it some value, even if it's null.
  • callback - receives (err, readStream), the same as for openReadStream()

This low-level function does not read any metadata from the underlying storage before opening the read stream. This is both a performance feature and a safety hazard. None of the integer parameters are bounds checked. None of the validation from openReadStream() with respect to compression and encryption is done here either. Only the bounds checks from validateEntrySizes are done, because that is part of processing the stream data.

openReadStreamPromise(entry, [options])

Wraps openReadStream() with Promise semantics. The returned promise is rejected if the callback for openReadStream() receives an err, otherwise the promise is resolved with the readStream.

entry and options are passed directly to openReadStream().

readLocalFileHeaderPromise(entry, [options])

Wraps readLocalFileHeader() with Promise semantics. The returned promise is rejected if the callback for readLocalFileHeader() receives an err, otherwise the promise is resolved with the readStream.

entry and options are passed directly to readLocalFileHeader().

openReadStreamLowLevelPromise(fileDataStart, compressedSize, relativeStart, relativeEnd, decompress, uncompressedSize)

Wraps openReadStreamLowLevel() with Promise semantics. The returned promise is rejected if the callback for openReadStreamLowLevel() receives an err, otherwise the promise is resolved with the readStream.

All arguments, fileDataStart, compressedSize, relativeStart, relativeEnd, decompress, and uncompressedSize, are passed directly to openReadStreamLowLevel().

close()

Causes all future calls to openReadStream() to fail, and closes the fd, if any, after all streams created by openReadStream() have emitted their end events.

If the autoClose option is set to true (see open()), this function will be called automatically effectively in response to this object's end event.

If the lazyEntries option is set to false (see open()) and this object's end event has not been emitted yet, this function causes undefined behavior. If the lazyEntries option is set to true, you can call this function instead of calling readEntry() to abort reading the entries of a zipfile.

It is safe to call this function multiple times; after the first call, successive calls have no effect. This includes situations where the autoClose option effectively calls this function for you.

If close() is never called, then the zipfile is "kept open". For zipfiles created with fromFd(), this will leave the fd open, which may be desirable. For zipfiles created with open(), this will leave the underlying fd open, thereby "leaking" it, which is probably undesirable. For zipfiles created with fromRandomAccessReader(), the reader's close() method will never be called. For zipfiles created with fromBuffer(), the close() function has no effect on resource usage.

Regardless of how this ZipFile was created, there are no resources other than those listed above that require cleanup from this function. This means it may be desirable to never call close() in some usecases.

isOpen

Boolean. true until close() is called; then it's false.

entryCount

Number. Total number of central directory records.

comment

String. Always decoded with CP437 per the spec.

If decodeStrings is false (see open()), this field is the undecoded Buffer instead of a decoded String.

Class: Entry

Objects of this class represent Central Directory Records. Refer to the zipfile specification for more details about these fields.

These fields are of type Number:

  • versionMadeBy
  • versionNeededToExtract
  • generalPurposeBitFlag
  • compressionMethod
  • lastModFileTime (MS-DOS format, see getLastModDate())
  • lastModFileDate (MS-DOS format, see getLastModDate())
  • crc32
  • compressedSize
  • uncompressedSize
  • fileNameLength (in bytes)
  • extraFieldLength (in bytes)
  • fileCommentLength (in bytes)
  • internalFileAttributes
  • externalFileAttributes
  • relativeOffsetOfLocalHeader

These fields are of type Buffer, and represent variable-length bytes before being processed:

  • fileNameRaw
  • extraFieldRaw
  • fileCommentRaw

There are additional fields described below: fileName, extraFields, fileComment. These are the processed versions of the *Raw fields listed above. See their own sections below. (Note the inconsistency in pluralization of "field" vs "fields" in extraField, extraFields, and extraFieldRaw. Sorry about that.)

The new Entry() constructor is available for clients to call, but it's usually not useful. The constructor takes no parameters and does nothing; no fields will exist.

fileName

String. Following the spec, the bytes for the file name are decoded with UTF-8 if generalPurposeBitFlag & 0x800, otherwise with CP437. Alternatively, this field may be populated from the Info-ZIP Unicode Path Extra Field (see extraFields).

This field is automatically validated by validateFileName() before yauzl emits an "entry" event. If this field would contain unsafe characters, yauzl emits an error instead of an entry.

If decodeStrings is false (see open()), this field is the undecoded Buffer instead of a decoded String. Therefore, generalPurposeBitFlag and any Info-ZIP Unicode Path Extra Field are ignored. Furthermore, no automatic file name validation is performed for this file name.

extraFields

Array with each item in the form {id: id, data: data}, where id is a Number and data is a Buffer.

This library looks for and reads the ZIP64 Extended Information Extra Field (0x0001) in order to support ZIP64 format zip files.

This library also looks for and reads the Info-ZIP Unicode Path Extra Field (0x7075) in order to support some zipfiles that use it instead of General Purpose Bit 11 to convey UTF-8 file names. When the field is identified and verified to be reliable (see the zipfile spec), the file name in this field is stored in the fileName property, and the file name in the central directory record for this entry is ignored. Note that when decodeStrings is false, all Info-ZIP Unicode Path Extra Fields are ignored.

See also getLastModDate() for additional extra fields that are recognized by this library.

fileComment

String decoded with the charset indicated by generalPurposeBitFlag & 0x800 as with the fileName. (The Info-ZIP Unicode Path Extra Field has no effect on the charset used for this field.)

If decodeStrings is false (see open()), this field is the undecoded Buffer instead of a decoded String.

Prior to yauzl version 2.7.0, this field was erroneously documented as comment instead of fileComment. For compatibility with any code that uses the field name comment, yauzl creates an alias field named comment which is identical to fileComment.

getLastModDate([options])

Returns the modification time of the file as a JavaScript Date object. The timezone situation is a mess due to both ZIP issues and JavaScript issues; read on to learn more.

Due to the zip file specification having lackluster support for specifying timestamps natively, there are several third-party extensions that add better support in the extraFields. yauzl supports these encodings:

  1. Info-ZIP "universal timestamp" extended field (0x5455 aka "UT"): signed 32-bit seconds since 1970-01-01 00:00:00Z, which supports the years 1901-2038 (partially inclusive) with 1-second precision. The value is timezone agnostic, i.e. always UTC.
  2. NTFS extended field (0x000a): 64-bit signed 100-nanoseconds since 1601-01-01 00:00:00Z, which supports the approximate years 20,000BCE-20,000CE with precision rounded to 1-millisecond (due to the JavaScript Date type). The value is timezone agnostic, i.e. always UTC.
  3. DOS lastModFileDate and lastModFileTime: supports the years 1980-2108 (inclusive) with 2-second precision. Timezone is interpreted either as the local timezone or UTC depending on the timezone option documented below.

If both the Info-ZIP "universal timestamp" and NTFS extended fields are found, yauzl uses one of them, but which one is unspecified. If neither are found, yauzl falls back to the built-in DOS lastModFileDate and lastModFileTime. Every possible bit pattern of every encoding can be represented by a JavaScript Date object, meaning this function cannot fail (barring parameter validation), and will never return an Invalid Date object.

options may be omitted or null, and has the following defaults:

{
  timezone: "local", // or "UTC"
  forceDosFormat: false,
}

Set forceDosFormat to true (and do not set timezone) to enable pre-yauzl 3.2.0 behavior where the Info-ZIP "universal timestamp" and NTFS extended fields are ignored.

The timezone option is only used in the DOS fallback. If timezone is "local" (or omitted or null), the lastModFileDate and lastModFileTime are interpreted in the system's current timezone (using new Date(year, ...)). If timezone is "UTC", the interpretation is in UTC+00:00 (using new Date(Date.UTC(year, ...))).

The JavaScript Date object, has several inherent limitations surrounding timezones. There is an ECMAScript proposal to add better timezone support to JavaScript called the Temporal API. Last I checked, it was at stage 4. https://github.com/tc39/proposal-temporal Once that new API is available and stable, better timezone handling should be possible here somehow. If you notice that the new API has become widely available, please open a feature request against this library to add support for it.

canDecodeFileData()

Returns true if and only if the metadata for this entry specifies an encoding that yauzl can automatically decode as part of openReadStream(). This considers compression method and encryption. Only compression methods 0 (stored with no compression) and 8 (deflated) are supported, and neither traditional encryption nor strong encryption is supported.

See also openReadStream().

isEncrypted()

Note that "Strong Encryption" is not supported, and will result in an "error" event emitted from the ZipFile.

This method is effectively implemented as return (this.generalPurposeBitFlag & 0x1) !== 0. yauzl provides no functionality to decrypt file data.

isCompressed()

DEPRECATED: use canDecodeFileData() instead, and/or manually check compressionMethod. This method is effectively implemented as return this.compressionMethod === 8, which misleadingly returns false for entries that are compressed using a compression method other than 8. This implementation is maintained for backward compatibility with yauzl versions prior to 3.3.x.

Class: LocalFileHeader

This is a trivial class that has no methods and only the following properties. The constructor is available to call, but it doesn't do anything. See readLocalFileHeader().

See the zipfile spec for what these fields mean.

  • fileDataStart - Number: inferred from fileNameLength, extraFieldLength, and this struct's position in the zipfile.
  • versionNeededToExtract - Number
  • generalPurposeBitFlag - Number
  • compressionMethod - Number
  • lastModFileTime - Number
  • lastModFileDate - Number
  • crc32 - Number
  • compressedSize - Number
  • uncompressedSize - Number
  • fileNameLength - Number
  • extraFieldLength - Number
  • fileName - Buffer
  • extraField - Buffer

Note that unlike Class: Entry, the fileName and extraField are completely unprocessed. This notably lacks Unicode and ZIP64 handling as well as any kind of safety validation on the file name. See also parseExtraFields().

Also note that if your object is missing some of these fields, make sure to read the docs on the minimal option in readLocalFileHeader().

Class: RandomAccessReader

This class is meant to be subclassed by clients and instantiated for the fromRandomAccessReader() function.

An example implementation can be found in test/test.js.

randomAccessReader._readStreamForRange(start, end)

Subclasses must implement this method.

start and end are Numbers and indicate byte offsets from the start of the file. end is exclusive, so _readStreamForRange(0x1000, 0x2000) would indicate to read 0x1000 bytes. end - start will always be at least 1.

This method should return a readable stream which will be pipe()ed into another stream. It is expected that the readable stream will provide data in several chunks if necessary. If the readable stream provides too many or too few bytes, an error will be emitted. (Note that validateEntrySizes has no effect on this check, because this is a low-level API that should behave correctly regardless of the contents of the file.) Any errors emitted on the readable stream will be handled and re-emitted on the client-visible stream (returned from zipfile.openReadStream()) or provided as the err argument to the appropriate callback (for example, for fromRandomAccessReader()).

If you call readStream.destroy() on streams you get from openReadStream(), the returned stream must implement a method ._destroy() according to https://nodejs.org/api/stream.html#writable_destroyerr-callback . If you never call readStream.destroy(), then streams returned from this method do not need to implement a method ._destroy(). ._destroy() should abort any streaming that is in progress and clean up any associated resources. ._destroy() will only be called after the stream has been unpipe()d from its destination.

Note that the stream returned from this method might not be the same object that is provided by openReadStream(). The stream returned from this method might be pipe()d through one or more filter streams (for example, a zlib inflate stream).

randomAccessReader.read(buffer, offset, length, position, callback)

Subclasses may implement this method. The default implementation uses createReadStream() to fill the buffer.

This method should behave like fs.read().

randomAccessReader.close(callback)

Subclasses may implement this method. The default implementation is effectively setImmediate(callback);.

callback takes parameters (err).

This method is called once the all streams returned from _readStreamForRange() have ended, and no more _readStreamForRange() or read() requests will be issued to this object.

How to Avoid Crashing

When a malformed zipfile is encountered, the default behavior is to crash (throw an exception). If you want to handle errors more gracefully than this, be sure to do the following:

  • When using an API with a callback parameter, check the err parameter.
  • When using an API that returns a Promise, check for promise rejection.
  • When accessing entries via Event: "entry" and Event: "end", attach a listener for Event: "error" on the ZipFile.
  • When accessing entires via eachEntry(), check for promise rejection on each returned promise. (When using await or for await...of, rejections will be thrown by those language constructs and must be caught with try...catch.)
  • Read streams for entries (such as from openReadStream()) can produce errors. Either attach a listen for Event: "error" on the read stream, or handle the error some other way.

Limitations

The automated tests for this project run on node versions 12 and up. Older versions of node are not supported.

Files corrupted by the Mac Archive Utility are not my problem

For a lengthy discussion, see issue #69. In summary, the Mac Archive Utility is buggy when creating large zip files, and this library does not make any effort to work around the bugs. This library will attempt to interpret the zip file data at face value, which may result in errors, or even silently incomplete data. If this bothers you, that's good! Please complain to Apple. :) I have accepted that this library will simply not support that nonsense.

No Streaming Unzip API

Due to the design of the .zip file format, it's impossible to interpret a .zip file from start to finish (such as from a readable stream) without sacrificing correctness. The Central Directory, which is the authority on the contents of the .zip file, is at the end of a .zip file, not the beginning. A streaming API would need to either buffer the entire .zip file to get to the Central Directory before interpreting anything (defeating the purpose of a streaming interface), or rely on the Local File Headers which are interspersed through the .zip file. However, the Local File Headers are explicitly denounced in the spec as being unreliable copies of the Central Directory, so trusting them would be a violation of the spec.

Any library that offers a streaming unzip API must make one of the above two compromises, which makes the library either dishonest or nonconformant (usually the latter). This library insists on correctness and adherence to the spec, and so does not offer a streaming API.

Here is a way to create a spec-conformant .zip file using the zip command line program (Info-ZIP) available in most unix-like environments, that is (nearly) impossible to parse correctly with a streaming parser:

$ echo -ne '\x50\x4b\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' > file.txt
$ zip -q0 - file.txt | cat > out.zip

This .zip file contains a single file entry that uses General Purpose Bit 3, which means the Local File Header doesn't know the size of the file. Any streaming parser that encounters this situation will either immediately fail, or attempt to search for the Data Descriptor after the file's contents. The file's contents is a sequence of 16-bytes crafted to exactly mimic a valid Data Descriptor for an empty file, which will fool any parser that gets this far into thinking that the file is empty rather than containing 16-bytes. What follows the file's real contents is the file's real Data Descriptor, which will likely cause some kind of signature mismatch error for a streaming parser (if one hasn't occurred already).

By using General Purpose Bit 3 (and compression method 0), it's possible to create arbitrarily ambiguous .zip files that distract parsers with file contents that contain apparently valid .zip file metadata.

Limited ZIP64 Support

For ZIP64, only zip files smaller than 8PiB are supported, not the full 16EiB range that a 64-bit integer should be able to index. This is due to the JavaScript Number type being an IEEE 754 double precision float.

The Node.js fs module probably has this same limitation.

ZIP64 Extensible Data Sector Is Ignored

The spec does not allow zip file creators to put arbitrary data here, but rather reserves its use for PKWARE and mentions something about Z390. This doesn't seem useful to expose in this library, so it is ignored.

No Multi-Disk Archive Support

This library does not support multi-disk zip files. The multi-disk fields in the zipfile spec were intended for a zip file to span multiple floppy disks, which probably never happens now. If the "number of this disk" field in the End of Central Directory Record is not 0, the open(), fromFd(), fromBuffer(), or fromRandomAccessReader() callback will receive an err. By extension the following zip file fields are ignored by this library and not provided to clients:

  • Disk where central directory starts
  • Number of central directory records on this disk
  • Disk number where file starts

Limited Encryption Handling

You can detect when a file entry is encrypted with "Traditional Encryption" via generalPurposeBitFlag, but yauzl will not help you decrypt it. See the openReadStream() option decodeFileData.

If a zip file contains file entries encrypted with "Strong Encryption", yauzl emits an error.

If the central directory is encrypted or compressed, yauzl emits an error.

Local File Headers Are Ignored

Many unzip libraries mistakenly read the Local File Header data in zip files. This data is officially defined to be redundant with the Central Directory information, and is not to be trusted. Aside from checking the signature, yauzl ignores the content of the Local File Header.

No CRC-32 Checking

This library provides the crc32 field of Entry objects read from the Central Directory. However, this field is not used for anything in this library.

versionNeededToExtract Is Ignored

The field versionNeededToExtract is ignored, because this library doesn't support the complete zip file spec at any version,

No Support For Obscure Compression Methods

Regarding the compressionMethod field of Entry objects, only method 0 (stored with no compression) and method 8 (deflated) are supported. Any of the other 15 official methods will cause the openReadStream() callback to receive an err.

Data Descriptors Are Ignored

There may or may not be Data Descriptor sections in a zip file. This library provides no support for finding or interpreting them.

Archive Extra Data Record Is Ignored

There may or may not be an Archive Extra Data Record section in a zip file. This library provides no support for finding or interpreting it.

No Language Encoding Flag Support

Zip files officially support charset encodings other than CP437 and UTF-8, but the zip file spec does not specify how it works. This library makes no attempt to interpret the Language Encoding Flag.

How Ambiguities Are Handled

The zip file specification has several ambiguities inherent in its design. Yikes!

  • The .ZIP file comment must not contain the end of central dir signature bytes 50 4b 05 06. This corresponds to the text "PK☺☻" in CP437. While this is allowed by the specification, yauzl will hopefully reject this situation with an "Invalid comment length" error. However, in some situations unpredictable incorrect behavior will ensue, which will probably manifest in either an invalid signature error or some kind of bounds check error, such as "Unexpected EOF".
  • In non-ZIP64 files, the last central directory header must not have the bytes 50 4b 06 07 ("PK♠•" in CP437) exactly 20 bytes from its end, which might be in the file name, the extra field, or the file comment. The presence of these bytes indicates that this is a ZIP64 file.

Change History

  • 3.4.0

    • Add wrapper functions that give Promise semantics: openPromise(), fromFdPromise(), fromBufferPromise(), fromRandomAccessReaderPromise(), openReadStreamPromise(), readLocalFileHeaderPromise(), openReadStreamLowLevelPromise(). pull #171
    • Add eachEntry(), which gives async iterator semantics as an alternative to readEntry(), Event: "entry", Event: "end", and Event: "error".
    • Added more modern examples to README.md and examples/ using await on the new APIs.
    • Added a CONTRIBUTING.md.
  • 3.3.2

    • Vendor and simplify buffer-crc dependency and prefer zlib.crc32() when available. issue #172 pull #173
    • Fixed typo in error handling for obscure IO errors. Raised in issue #174.
  • 3.3.1

    • Fixed bugs related to interrupting streams with destroy() and with async iteration on read streams. issue #169 pull #170
  • 3.3.0

    • Added entry.canDecodeFileData() and option decodeFileData for entry.openReadStream(), and marked as deprecated isCompressed() and options decompress and decrypt. issue #80 pull #81 pull #82 issue #166
  • 3.2.1

  • 3.2.0

    • Added support for reading third-party extensions for timestamps: Info-ZIP "universal timestamp" extra field and NTFS extra field. pull #160
    • entry.getLastModDate() takes options forceDosFormat to revert the above change, and timezone to allow UTC interpretation of DOS timestamps.
    • Documented dosDateTimeToDate() as now deprecated.
  • 3.1.3

    • Fixed a crash when using fromBuffer() to read corrupt zip files that specify out of bounds file offsets. issue #156
    • Enahnced the test suite to run the error tests through fromBuffer() and fromRandomAccessReader() in addition to open(), which would have caught the above.
  • 3.1.2

    • Fixed handling non-64 bit entries (similar to the version 3.1.1 fix) that actually have exactly 0xffffffff values in the fields. This fixes erroneous "expected zip64 extended information extra field" errors. issue #109
  • 3.1.1

    • Fixed handling non-64 bit files that actually have exactly 0xffff or 0xffffffff values in End of Central Directory Record. This fixes erroneous "invalid zip64 end of central directory locator signature" errors. issue #108
    • Fixed handling of 64-bit zip files that put 0xffff or 0xffffffff in every field overridden in the Zip64 end of central directory record even if the value would have fit without overflow. In particular, this fixes an incorrect "multi-disk zip files are not supported" error. pull #118
  • 3.1.0

    • Added readLocalFileHeader() and Class: LocalFileHeader.
    • Added openReadStreamLowLevel().
    • Added getFileNameLowLevel() and parseExtraFields(). Added fields to Class: Entry: fileNameRaw, extraFieldRaw, fileCommentRaw.
    • Added examples/compareCentralAndLocalHeaders.js that demonstrate many of these low level APIs.
    • Noted dropped support of node versions before 12 in the "engines" field of package.json.
    • Fixed a crash when calling openReadStream() with an explicitly null options parameter (as opposed to omitted).
  • 3.0.0

    • BREAKING CHANGE: implementations of RandomAccessReader that implement a destroy method must instead implement _destroy in accordance with the node standard https://nodejs.org/api/stream.html#writable_destroyerr-callback (note the error and callback parameters). If you continue to override destory instead, some error handling may be subtly broken. Additionally, this is required for async iterators to work correctly in some versions of node. issue #110
    • BREAKING CHANGE: Drop support for node versions older than 12.
    • Maintenance: Fix buffer deprecation warning by bundling fd-slicer with a 1-line change, rather than depending on it. issue #114
    • Maintenance: Upgrade bl dependency; add package-lock.json; drop deprecated istanbul dependency. This resolves all security warnings for this project. pull #125
    • Maintenance: Replace broken Travis CI with GitHub Actions. pull #148
    • Maintenance: Fixed a long-standing issue in the test suite where a premature exit would incorrectly signal success.
    • Officially gave up on supporting Mac Archive Utility corruption in order to rescue my motivation for this project. issue #69
  • 2.10.0

    • Added support for non-conformant zipfiles created by Microsoft, and added option strictFileNames to disable the workaround. issue #66, issue #88
  • 2.9.2

    • Removed tools/hexdump-zip.js and tools/hex2bin.js. Those tools are now located here: thejoshwolfe/hexdump-zip and thejoshwolfe/hex2bin
    • Worked around performance problem with zlib when using fromBuffer() and readStream.destroy() for large compressed files. issue #87
  • 2.9.1

    • Removed console.log() accidentally introduced in 2.9.0. issue #64
  • 2.9.0

    • Throw an exception if readEntry() is called without lazyEntries:true. Previously this caused undefined behavior. issue #63
  • 2.8.0

    • Added option validateEntrySizes. issue #53
    • Added examples/promises.js
    • Added ability to read raw file data via decompress and decrypt options. issue #11, issue #38, pull #39
    • Added start and end options to openReadStream(). issue #38
  • 2.7.0

    • Added option decodeStrings. issue #42
    • Fixed documentation for entry.fileComment and added compatibility alias. issue #47
  • 2.6.0

    • Support Info-ZIP Unicode Path Extra Field, used by WinRAR for Chinese file names. issue #33
  • 2.5.0

    • Ignore malformed Extra Field that is common in Android .apk files. issue #31
  • 2.4.3

    • Fix crash when parsing malformed Extra Field buffers. issue #31
  • 2.4.2

    • Remove .npmignore and .travis.yml from npm package.
  • 2.4.1

    • Fix error handling.
  • 2.4.0

  • 2.3.1

    • Documentation updates.
  • 2.3.0

    • Check that uncompressedSize is correct, or else emit an error. issue #13
  • 2.2.1

    • Update dependencies.
  • 2.2.0

    • Update dependencies.
  • 2.1.0

    • Remove dependency on iconv.
  • 2.0.3

    • Fix crash when trying to read a 0-byte file.
  • 2.0.2

    • Fix event behavior after errors.
  • 2.0.1

    • Fix bug with using iconv.
  • 2.0.0

    • Initial release.

Development

One of the trickiest things in development is crafting test cases located in test/{success,failure}/. These are zip files that have been specifically generated or design to test certain conditions in this library. I recommend using hexdump-zip to examine the structure of a zipfile.

For making new error cases, I typically start by copying test/success/linux-info-zip.zip, and then editing a few bytes with a hex editor.