Giter Site home page Giter Site logo

yauzl's Introduction

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

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 (/\/$/.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();
        });
        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.

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.

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.

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.

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.

dosDateTimeToDate(date, time)

Converts MS-DOS date and time data into a JavaScript Date object. Each parameter is a Number treated as an unsigned 16-bit integer. Note that this format does not support timezones. The returned Date object will be constructed using the local timezone.

In order to interpret the parameters in UTC time instead of local time, you can convert with the following snippet:

var timestampInterpretedAsLocal = yauzl.dosDateTimeToDate(date, time); // or entry.getLastModDate()
var timestampInterpretedAsUTCInstead = new Date(
    timestampInterpretedAsLocal.getTime() -
    timestampInterpretedAsLocal.getTimezoneOffset() * 60 * 1000
);

Note that there is an ECMAScript proposal to add better timezone support to JavaScript called the Temporal API. Last I checked, it is at stage 3. https://github.com/tc39/proposal-temporal

Once that new API is available and stable, better timezone handling should be possible here somehow. Feel free to open a feature request against this library when the time comes.

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.

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.

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.

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()). 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.

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.

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.

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

{
  decompress: entry.isCompressed() ? true : null,
  decrypt: null,
  start: 0,                  // actually the default is null, see below
  end: entry.compressedSize, // actually the default is null, see below
}

If the entry is compressed (with a supported compression method), and the decompress option is true (or omitted), the read stream provides the decompressed data. Omitting the decompress option is what most clients should do.

The decompress option must be null (or omitted) when the entry is not compressed (see isCompressed()), and either true (or omitted) or false when the entry is compressed. Specifying decompress: false for a compressed entry causes the read stream to provide the raw compressed file data without going through a zlib inflate transform.

If the entry is encrypted (see isEncrypted()), clients may want to avoid calling openReadStream() on the entry entirely. Alternatively, clients may call openReadStream() for encrypted entries and specify decrypt: false. If the entry is also compressed, clients must also specify decompress: false. Specifying decrypt: false for an encrypted entry causes the read stream to provide the raw, still-encrypted file data. (This data includes the 12-byte header described in the spec.)

The decrypt option must be null (or omitted) for non-encrypted entries, and false for encrypted entries. Omitting the decrypt option (or specifying it as null) for an encrypted entry will result in the callback receiving an err. This default behavior is so that clients not accounting for encrypted files aren't surprised by bogus file data.

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. If either of these options are specified and non-null, then the above options must be used to obain the file's raw data. Specifying {start: 0, end: entry.compressedSize} will result in the complete file, which is effectively the default values for these options, but note that unlike omitting the options, when you specify start or end as any non-null value, the above requirement is still enforced that you must also pass the appropriate options to get the file's raw data.

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.

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.

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 whether called or not.

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.

None of the other fields are considered significant by this library. Fields that this library reads are left unaltered in the extraFields array.

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()

Effectively implemented as the following. See dosDateTimeToDate().

return dosDateTimeToDate(this.lastModFileDate, this.lastModFileTime);

isEncrypted()

Returns is this entry encrypted with "Traditional Encryption". Effectively implemented as:

return (this.generalPurposeBitFlag & 0x1) !== 0;

See openReadStream() for the implications of this value.

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

isCompressed()

Effectively implemented as:

return this.compressionMethod === 8;

See openReadStream() for the implications of this value.

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:

  • Provide callback parameters where they are allowed, and check the err parameter.
  • Attach a listener for the error event on any ZipFile object you get from open(), fromFd(), fromBuffer(), or fromRandomAccessReader().
  • Attach a listener for the error event on any stream you get from openReadStream().

Minor version updates to yauzl will not add any additional requirements to this list.

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 isEncrypted(), but yauzl will not help you decrypt it. See openReadStream().

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.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.

yauzl's People

Contributors

amilajack avatar andrewrk avatar mjomble avatar neverendingqs avatar overlookmotel avatar silverwind avatar styfle avatar tgabi333 avatar thejoshwolfe avatar wcmonty avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yauzl's Issues

non-fs API

hey I have a weird request. I wrote this https://github.com/maxogden/punzip for a somewhat common but annoying use case: given a large zip on a server, only extract a single file from it, as a stream, without downloading the whole thing.

here's more detail on the use case: https://gist.github.com/maxogden/11a85ae12074fed0b9f6

the cool thing is that it totally works! I can mount a 500mb zip, point yauzl at it, and my code translates yauzls calls into HTTP range calls like this:

  mount-url requested +542ms 514105344-514170879 received 65536 bytes
  mount-url requested +173ms 514170880-514172204 received 1325 bytes

those were yauzl getting the entry table at the end of the file (I think).

unfortunately I had to use fuse to make it compatible with yauzl. It would be nice, though, if I could give yauzl a function with e.g. 'getBytes(offset, length)` or something and it would be able to use that as the data source rather than a file descriptor/path to a file.

i'm open to any suggestions or ideas you might have for this use case!

validateFileName problem?

hi ,respect for @thejoshwolfe

just now, i got a problem, when i use zipfile.readEntry, it got a " invalid characters in fileName" ,so I trace code, find blow code
20170302000512

in index.js
but "fileName" are Common formats and have no error like "path\to\file.extension" ,
so i add a single line of code " fileName=fileName.replace(/\/g,'/') " at function's start ,and it's ok .
env:
OS: windows 10 ,×64
software: nodejs in nw.js
my question is why " invalid characters in fileName" come out ?

you got my respect, and thank you will be resolving my question.

destroying a readStream for a compressed entry may cause a memory leak

I'm not actually sure this is a problem, but I think it is. The zlib module appears to offer no way of aborting an inflate stream in progress. Currently, yauzl just unpipes and abandons it hoping that the GC will do something helpful. But since there are C resources supporting the JavaScript API, this is probably causing a memory leak.

file date

how to keep the file's origin date when unzip? and all origin attributes. when I do unzip the files,and use yazl zip the files, and compare the two zip files md5 does not equel.

Comments are CP437?

Hey @thejoshwolfe,

First off, you are awesome. Love your approach with this library.

Going through the code, I was curious why you are decoding comment text using CP437 encoding, I couldn't find a reference to this encoding in the PKWare spec. Should UTF8 just work fine?

encrypted zip files should not have undefined behavior

From the docs:

Currently, the presence of encryption is not even checked, and encrypted zip files will cause undefined behavior.

encrypted zip files should, at the very least, have well-defined behavior. for example, returning an error and not crashing.

Same with ZIP64 files. The docs say the behavior is undefined, but the behavior should be defined to give an error and not crash.

No entry for containing directory?

If there is a single directory inside the archive that's wrapping everything else, yauzl doesn't seem to emit an entry for that directory.

Example zip structure:

test/
    file.txt
    subdir/
        file2.txt

Test program:

var yauzl = require("yauzl");

yauzl.open(__dirname + "/test.zip", {lazyEntries: true}, function (err, zipfile) {
	if (err) throw err;
	zipfile.readEntry();
	zipfile.on("entry", function(entry) {
		console.log(entry.fileName);
		zipfile.readEntry();
	});
});

Output:

test/file.txt
test/subdir/
test/subdir2/
test/subdir2/file2.txt

I was expecting the output to start with test/.

The zip file is created using default Windows right-click thing. You can find the demo app and zip file I used here: https://github.com/panta82/yauzl-test

add lazyEntries option

One of the main goals of yauzl is to keep memory usage under control. The current pattern of entry and end events is hostile to this goal.

Using examples/unzip.js on http://wolfesoftware.com/crowded.zip.xz (unxz it first) results in over 2GB of virtual ram allocation as the program slows to a crawl while creating empty directories. This is failure. The problem is that we call openReadStream (0bf7f48#diff-5b07aa03e052f091324dde1dfbfd25bfR53) on every entry before pumping any of the file data. This is the naive solution to the problem that #18 ran into in the test system where autoClose can lead to race conditions.

Really, the problem is that autoClose expects you to call all your openReadStream()s before yauzl emits the end event, and yauzl races to emit the end event as soon as possible. Even with autoClose off, you need to keep all your Entry objects in ram as yauzl emits them, or you'll lose the information.

We need some way to throttle entry reading.

Also, it's questionable that yauzl's API encourages clients to extract all files in parallel. This would probably be worse performance on any file system that tries to pre-fetch data ahead of your read() calls (pretty much all of them, I think) than the normal strategy of extracting one file at a time from start to finish. I didn't test that, but it seems like in general file system access wouldn't necessarily benefit from parallelization; in fact, massive parallelization probably makes things much worse due to cache thrashing and the extra resources required to keep all the pipelines open.

It will be nice to remove examples/unzip.js's dependency on a third-party synchronization library pend, which is currently being used to iterate over the entries one at a time despite yauzl's API giving us all the entries in parallel. It's a smell that our own example doesn't use the recommended execution order.

Test error events emitted from yauzl internal objects

Hi,

I wrote a module for a project relying on yauzl in order to extract a zip file.
Extraction works.
However I am trying to reach 100% code coverage.
I am having hard time to figure how to test error events emitted by the ZipFile object as well as streams errors which could occurs within openReadStream.

My module expose a function through its public API :

function extractZip(targetPath, zipfilePath, fd, done) {
  
  const yauzlOptions = {"autoClose": true, "lazyEntries": true};

    yauzl.fromFd(fd, yauzlOptions, function processZipFile(err, zipfile) {
      if (err) {
        // This part was tested using a mocking object with proxyquire      
      }
      zipfile.readEntry();

      zipfile.on('error', function zipFileErrorEventHandler(err) {
         // That's the part of the code I would like to cover
      });
    }
}

I was thinking maybe I should stub/mock the ZipFile object and emit the error event but I can't figure it out what would be the best way or the simplest way to achive that ?

Any hint regarding this ?

Thanks.

throw errors when clients call the api wrong

The docs currently talk about a lot of "undefined behavior" (see especially readEntry). It would probably be more convenient for clients learning how to use this library if yauzl would throw an explicit exception instead of charging headlong into undefined behavior.

When reading zip file from buffer, "end" is fired before last "entry" event

var getRawBody = require('raw-body')
var yauzl = require("yauzl");
var q = require('q');

var streamToBuffer = function(sourceStream) {
var deferred = q.defer();

getRawBody(sourceStream, {}, function (err, buffer) {
    if (err) {
        deferred.reject(err);
    } else {
        deferred.resolve(buffer);
    }
});

return deferred.promise;

}

streamToBuffer(fileStream).then(function(buffer) {
    yauzl.fromBuffer(buffer, function(err, zipfile) {
        if (err) {
            console.log('error parsing zip file', error);
            throw err;
        }

        zipfile.on('entry', function (entry) {
            console.log('zip file entry: ' + entry.fileName);
        }).on('error', function (error) {
            console.log('error parsing zip file', error);
        }).on('end', function () {
            console.log('done with zip file');
        });

directory safety considerations

  • The README should reassure the API user that yauzl is protecting them against absolute paths in directory names so they can avoid writing those safety checks. Probably both in the usage example and in the API docs below.
  • What about directory names that have .. in them? E.g. ../../../gotcha/sucker

btw, use /foo/.test() instead of /foo/.exec() when you want a simple boolean answer.

add byte range options to openReadStream

Sort of thinking out loud here. I have a use case where I want to 'mount' a compressed archive and access bytes randomly without decompressing the whole archive up front. Basically I want to:

  1. Efficiently get the entry that matches some filename
  2. Read a byte range from that entry
  3. Repeat this many times, potentially reading the same entry multiple times

The yauzl API seems to be geared for single pass unzipping, which makes sense. One approach I was thinking is I could just get all on('entry') entries up front and keep them in memory, then when a byte range request comes in I can use the entry to retrieve the byte range, but I ran in to problems, it would be much nicer to be able to lazily consult the central directory as opposed to having to read it all up front.

The other issue is related to Deflate which requires decompression from the beginning of the entry. I guess an alternative compression type like BGZF would make arbitrary byte range lookups much faster, but it wouldn't be compatible with many implementations. However! I found another technique where you do a single pass over the entry and build an index (https://github.com/madler/zlib/blob/master/examples/zran.c). I think this would be acceptable for my use case.

Being able to implement the zran style indexing on top of yauzl would mean some API changes I think, e.g. a way to get a single entry from the CDR by name, and a lower level way to control the decompression state to support zran. Before I got too deep I wanted to sanity check this use case, does it seem doable?

ENOENT

events.js:85
throw er; // Unhandled 'error' event
^
Error: ENOENT, open 'TestZip/001.png'

at Error (native)

When running example script on any file I get this. I don't know if it's really an error, but I'm using the example script exactly as written changing only the path to my .zip file.

Allow settings custom character encoding for files within the zip

In the code below, what is the encoding of the file referred to by readStream?

    yauzl.open('file.zip', (err, zipfile) => {
      if (err) throw err
      zipfile.on('entry', (entry) => {
        zipfile.openReadStream(entry, (err, readStream) => {
          if (err) throw err
          // What is readStream encoding? 
        })
      })
    })

The README says

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.

It would be helpful if there can be a way to tell yauzl the encoding of the file if you know what it is. It's more efficient if yauzl does it directly than having to pipe it through a conversion stream like iconv-lite.

End of Central Directory Record signature not found for base64-encoded zipfile

Real Case (background info):

I need to receive zipped (6z) base64 data and unzip them.

Test Case:

Hello, I zipped a pages doc in a folder with the mac standard software (osx el capitan). Then I encoded this file to base64 with an online converter: http://base64converter.com/ and sent it to my app.

Code

Now I tried to unzip this file:

exports.unzip = (base64, callback)->
  buffer = new Buffer(base64, 'base64')
  yauzl.fromBuffer buffer, { lazyEntries: true }, (err, zipfile) ->
    if err?
      throw err
    zipfile.readEntry()
    zipfile.on 'entry', (entry) ->
      if /\/$/.test(entry.fileName)
        # directory file names end with '/'
        mkdirp entry.fileName, (err) ->
          if err
            throw err
          zipfile.readEntry()
          return
      else
        # file entry
        zipfile.openReadStream entry, (err, readStream) ->
          if err
            throw err
          # ensure parent directory exists
          mkdirp path.dirname(entry.fileName), (err) ->
            if err
              throw err
            console.log "SAVING, entry.fileName #{entry.fileName}"
            readStream.pipe fs.createWriteStream(entry.fileName)
            readStream.on 'end', ->
              zipfile.readEntry()
              return callback()
            return
          return
      return
    return

## Base64

This is the base64:
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

Zip

This is the zip file:
Test Doc.zip

Directory entry size validation

I use your amaizing module to uncompress zip archives from different sources. Archives may be produced via any compression tools and I don't exactly know which tool was used.

I've found a problem on some archives: readEntry() method fails on directory processing on entry size validation:

      if (entry.compressionMethod === 0) {
        if (entry.compressedSize !== entry.uncompressedSize) {
          var msg = "compressed/uncompressed size mismatch for stored file: " + entry.compressedSize + " != " + entry.uncompressedSize;
          return emitErrorAndAutoClose(self, new Error(msg));
        }
      }

Looks like some zip tool constantly set the following local headers for a directory entry:
compressionMethod: 0
compressedSize: 0
uncompressedSize: 4096

Unfortunatelly, I cannot provide zip example for this case because of sensitive data.

What do you think about some boolean option: validate size or skip validation? Or validate directory size or skip validation? If you accept this proposition I'll create pull request with my pleasure.

test suite failure

npm install && npm test fails for me:

test/success/linux-info-zip.zip(buffer): pass

/Users/maxogden/src/js/yauzl/test/test.js:55
              throw new Error(messagePrefix + "not supposed to exist");
                    ^
Error: test/success/unicode.zip(fd): Turmion Kätilöt/: not supposed to exist
    at /Users/maxogden/src/js/yauzl/test/test.js:55:21
    at pendGo (/Users/maxogden/src/js/yauzl/node_modules/pend/index.js:30:3)
    at Pend.go (/Users/maxogden/src/js/yauzl/node_modules/pend/index.js:13:5)
    at ZipFile.<anonymous> (/Users/maxogden/src/js/yauzl/test/test.js:51:27)
    at ZipFile.EventEmitter.emit (events.js:95:17)
    at /Users/maxogden/src/js/yauzl/index.js:237:12
    at /Users/maxogden/src/js/yauzl/index.js:329:5
    at /Users/maxogden/src/js/yauzl/node_modules/fd-slicer/index.js:28:7
    at Object.wrapper [as oncomplete] (fs.js:454:17)
npm ERR! Test failed.  See above for more details.
npm ERR! not ok code 0

this happens for a bunch of the zip files in the success/ folder. if I delete them then npm test finally passes.

could you set up travis CI on this repo? npm install -g travisjs && travisjs init

streaming unzip?

I just wanted to see if you guys thought about supporting a pure unzip use case, e.g.

cat file.zip | unzip

It seems thats the approach taken by node-unzip: https://github.com/EvanOxfeld/node-unzip/blob/5a62ecbcef6523708bb8b37decaf6e41728ac7fc/lib/parse.js#L51-L57

They basically scan through the zip beginning -> and and when they see different signatures they emit entries, whereas the yauzl approach is to first skip to the end and read the CDR and emit entries from that.

The nice thing about a pure stream API is you can use unix pipes and not require random access in order to unzip. On the other hand, the nice thing about reading the CDR first is you can do e.g. lazy file mounting + extracting use cases over HTTP like I did in the issue yesterday.

I'm just curious if there are other reasons why streaming unzipping isn't implemented in yauzl other than its currently a nice compact 400 line implementation and supporting two APIs would be tedious. Cheers :)

Support closing a zipfile partway through reading entries

It's possible to have several million entries in a zipfile. Reading them all can be slow, and a client may want to look for just one and then abort reading the entries partway through by calling close() instead of another readEntry(). The docs currently call this undefined behavior, but it should probably just work.

entry.fileComment is documented wrong

In the README, the field is called entry.comment. In the code, it's called entry.fileComment. It's possible that users have written code that relies on either on of these, so we should make them both exist as aliases of one another.

The README will now document that entry.fileComment is the recommended way, and entry.comment is deprecated.

We should also add a test for file comments. If it's in the README, it needs first-class support, and that means tests.

race condition in test.js: Error: closed

After some amount of normal test output:

/home/josh/dev/yauzl/test/test.js:78
                if (err) throw err;
                               ^
Error: closed
    at ZipFile.openReadStream (/home/josh/dev/yauzl/index.js:262:37)
    at /home/josh/dev/yauzl/test/test.js:77:23
    at pendGo (/home/josh/dev/yauzl/node_modules/pend/index.js:54:3)
    at onCb (/home/josh/dev/yauzl/node_modules/pend/index.js:41:7)
    at AssertByteCountStream.<anonymous> (/home/josh/dev/yauzl/test/test.js:91:19)
    at AssertByteCountStream.emit (events.js:129:20)
    at _stream_readable.js:908:16
    at process._tickCallback (node.js:355:11)
npm ERR! Test failed.  See above for more details.

The problem seems to be that we are deferring openReadStream until after entityProcessing.go lets us start. Since we're using yauzl's autoClose: true, we're missing the window of time when we can call openReadStream.

don't throw exceptions for malformed zipfiles

There are numerous ways for maliciously constructed .zip files to crash whatever program is using yauzl. One example is an invalid UTF8 filename encoding.

All such error cases should provide the err in callbacks or emit the error event instead of allowing exceptions to bubble out of the library and crash the client program.

Invalid comment length

I'm having trouble unzipping a zip file uploaded by a user. The zip opens fine in any other unzip software I've tried. The error I'm getting is:

Error: invalid comment length. expected: 12298. found: 0
    at /usr/src/app/node_modules/yauzl/index.js:125:25
    at /usr/src/app/node_modules/yauzl/index.js:539:5
    at /usr/src/app/node_modules/fd-slicer/index.js:32:7
    at FSReqWrap.wrapper [as oncomplete] (fs.js:681:17)

If I comment out line 125 of index.js where the error is thrown, the file does seem to unzip properly. Any thoughts?

CRC-32 checks

Readme says CRC32 check not performed. Is that an option or do you not plan to support it?

Concat shredded file content strings by default?

  zipfile.on("entry", function(entry) {
          if (/\/$/.exec(entry)) return;
          zipfile.openReadStream(entry, options, function(err, readStream) {
            if (err) throw err;
            readStream.on('data', data => {
              console.log(entry.fileName) 
              console.log(data.toString())
            })
           })
          zipfile.readEntry()
        });

I am extracting xml files from zip and when I log them inside on('data' event they are multiparted. Basically in logs, fileName gets repeated and data is shredded in parts.

Is there builtin method for concating this data as single strings that are ready to be parsed as whole?

option to emit raw string buffers instead of decoded strings

I'm using version 2.6.0 FWIW, node 6.

± node debug bin.js foo.zip
< Debugger listening on [::]:5858
connecting to 127.0.0.1:5858 ... ok
break in bin.js:2
  1
> 2 'use strict'
  3 const extractExec = require('./')
  4 const fs = require('fs')
c
break in index.js:46
 44     // TODO: what if we get multiple plists?
 45     const plist = plists[0]
>46     debugger
 47     getExecStream(fd, plist.CFBundleExecutable, (err, entry, exec) => {
 48       debugger
c
break in index.js:19
 17     zip.on('entry', function onentry (entry) {
 18       if ((/XXXThing.*app\/XXXThing-.*/i).test(entry.fileName)) {
>19         debugger;
 20       }
 21       if (!isOurExec(entry, execname)) { return }
repl
Press Ctrl + C to leave debug repl
> entry.fileName
'Payload/XXXThing-╬▓.app/XXXThing-╬▓'
> execname
'XXXThing-β'

as you can see the execname is right but the entry.fileName is not right utf-8 AFAICT.

Some directory file names isn`t end with /

Steps to Reproduce:
Download: https://github.com/f111fei/test-files/raw/b124ebeb0ac1378f1335b0601485bd926743031f/yauzl-test.zip

Run: node index.js

Get Output:

Current FileName: extension/1
Current FileName: extension/1/2
c:\Users\xzper\Desktop\yauzl-test\index.js:23
if (err) throw err;
^

Error: EEXIST: file already exists, mkdir 'c:\Users\xzper\Desktop\yauzl-tes
t\extension\1'
at Error (native)

entry is a directory, but entry.filename isn`t end with '/'

Executables fail to run if started too soon after being unzipped

This is pseudocode with lots of stuff like error handling removed.

yauzl.fromFd(fd, { lazyEntries: true }, (err, zipfile) => {
                zipfile.readEntry();
                zipfile.on('entry', (entry: yauzl.Entry) => {
                        zipfile.openReadStream(entry, (err, readStream) => {
                            mkdirp.mkdirp(path.dirname(path), { mode: 0o775 }, (err) => {
                                readStream.pipe(fs.createWriteStream(path, { mode: fileMode }));
                                readStream.on('end', () => {
                                    zipfile.readEntry();
                                });
                            });
                        });
                    }
                });
                zipfile.on('end', () => {
                    resolve();
                });
            });
        })
err = process.spawn();

The bug is that the process.spawn returns -4082 (0xFFFFF00E), which means the process isn't finished being written yet. It looks like something isn't flushing the buffer soon enough. Sitting in a busy wait loop will eventually cause the process.spawn to succeed. I tried to somehow close or flush the stream buffer but nothing fixed it.

We hit this bug with the implementation of our C++ VS Code extension, but we worked around it by installing the .exe we need to launch before installing the the other executables.

ability to abort a pipeline from openReadStream

The unpipe branch currently has a test failure due to trying to walk away from an 8GB read stream after reading 0x100 bytes. The pipe is left open, and yazul never emits the close event.

In general, we should have the ability to abort a read stream partway through. According to my research, we need to provide this feature rather than relying on some existing Node solution that works for all generic streams and pipes.

Credit to @timotm for finding this limitation 8 months ago in #13.

How do I stop traversing but still have an end event fire?

How do I stop traversing the zip but still have the zip 'end' event fire?
it seems that zipfile.readEntry() is the only thing that invokes the zip.end event,
but i dont want to call it when I have what I want, and I DO want the end event to be called.

autoClose: false doesnt help. teh close method doesnt fire for fromBuffer.
what am I missing?

var yauzl = require("yauzl");
var fs = require("fs");
var path = require("path");
var mkdirp = require("mkdirp"); // or similar

 yauzl.fromBuffer(buffer,{lazyEntries: true}, function(err, zip) {
  if (err) throw err;
  zipfile.readEntry();
  zipfile.on("entry", function(entry) {
    if (/\/$/.test(entry.fileName)) {
        //do something here then stop walking the zip file.

    } else {
     zipfile.readEntry();
    }
  });

    zip.once("end", function() {
     //clean up. this never fires if we found our target file.
    });

});




nice to have: better test coverage for ZIP64 error cases

We've only got 1 test currently for ZIP64 and it's a simple passing test. We should also test corner cases such as:

  • file size is reported to be larger than Number.MAX_SAFE_INTEGER.
  • offsets to metadata and file data are out of bounds for the file size.

RangeError in buffer.js due to error at index.js:283

I was getting the following stack trace while unzipping a file:

buffer.js:620
throw new RangeError('index out of range');
^

RangeError: index out of range
at checkOffset (buffer.js:620:11)
at Buffer.readUInt16LE (buffer.js:666:5)
at /usr/local/lib/node_modules/yauzl/index.js:286:41
at /usr/local/lib/node_modules/yauzl/index.js:474:5
at /usr/local/lib/node_modules/yauzl/node_modules/fd-slicer/index.js:32:7
at FSReqWrap.wrapper as oncomplete

I tracked it down to line 283:
while (i < extraFieldBuffer.length) {

which should instead be at least:
while (i+4 < extraFieldBuffer.length) {

to avoid attempting to read past the end of the buffer at line 284 and 285.

However, I think you should add another check before line 289 to ensure the extraFieldBuffer.copy does not fail due to an invalid size field in the zip file. That is, if it is invalid, you should throw a descriptive exception rather than letting it be handled by another RangeCheck error (which doesn't explain the problem very well to the casual user of a damaged zip file).

(Note that there appears to be no easy way to trap this exception since it occurs within FSReqWrap wrapper).

No Streaming (Question / Brain storming)

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.

I really don't know jack about the zip format, but I am just trying to think out of the box here for possibly having a streaming api.

Since the CD is at the end can we not read the file stream in reverse? Thus then detect the CD.

The main use case I am looking is for getting a single large file out of many in a zip using streaming if possible.

unsupport unzip java uploaded .zip file

when unzip a java uploaded .zip file from buffer , throw error "invalid comment length. expected: 2. found: 0", but when we comment the bellow verify code, and the file unzips success:

if (commentLength !== expectedCommentLength) {
        return callback(new Error("invalid comment length. expected: " + expectedCommentLength + ". found: " + commentLength));
      }

about java upload .zip file, the buffer count is less then node.js: http://stackoverflow.com/questions/34643660/missing-bytes-when-uploading-zip-file-with-multipart-form-data-using-java

can we just deprecate the verify code above ?

Support a promise paradigm

Before

    yauzl.open('file.zip', (err, zipfile) => {
      if (err) throw err
      zipfile.on('entry', (entry) => {
        zipfile.openReadStream(entry, (err, readStream) => {
          if (err) throw err
          readStream.pipe(somewhere)
        })
      })
    })

After

    const zipfile = await yauzl.open('file.zip')
    zipfile.on('entry', async (entry) => {
      const readStream = zipfile.openReadStream(entry)
        readStream.pipe(somewhere)
      })
    })

If yauzl.open and zipfile.openReadStream return Promises if no callbacks are provided, it would simplify code for people who choose to use async/await. We could remove all if (err) throw err because unhandled errors would be thrown automatically. And we don't have as deep nesting of the code, which makes things hard to read unless functions are extracted which may otherwise not have needed to be extracted.

Of course, if callbacks are provided then no Promise is returned and code behaves as it currently does, making this change backwards-compatible.

Node v7, which will be released soon, will make native async/await available behind flags. And Node v8 will make them available without any flags. This feature would give the option to write simpler code for anyone using those versions of Node, or any version of Node with Babel (as I'm currently doing it).

CPU usage

Hi!

It seems that at last I found an unzip library that doesn't leak memory, so thank you.

Having said that, I'm observing that yauzl vs unzip command-line utility takes twice the amount of CPU time to decompress an identical ZIP. The code used is the one provided in README by you.

The ZIP file is public: https://downloads.mariadb.org/f/mariadb-10.1.16/winx64-packages/mariadb-10.1.16-winx64.zip

While unzip takes 16 second of CPU, yauzl takes 33.

Is there any room for improvement?

Thank you,

Albert

Zip bomb prevention?

Hi,

How is one supposed to abort processing of zip entry / file while processing entries?

Some background: I want to prevent a zip bomb from hogging CPU/memory resources, and would like to check for actual, cumulative uncompressed size while uncompressing an entry. For that, I implemented my own Writable stream which raises an error (through callback) when it gets too much data. I then catch this error and currently I call .close() for the readStream I got in yauzl's entry callback.

However, this seems to trigger a bug in node's zlib implementation (I tried both 0.10.28 and 0.12.2) and aborts the execution:

Assertion failed: (ctx->mode_ != NONE && "already finalized"), function Write, file ../src/node_zlib.cc, line 147.
Abort trap: 6

While I theoretically could patch my way around this, I naturally wouldn't want to fork both zlib.js and your library. So can I abort the processing of an entry / entire zip file by some other way cleanly, without any excessive CPU or memory usage?

Full sample code available at https://github.com/timotm/node-zip-bomb

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.