Comparing version 2.3.1 to 2.4.0
338
index.js
@@ -8,2 +8,3 @@ var fs = require("fs"); | ||
var PassThrough = require("stream").PassThrough; | ||
var Writable = require("stream").Writable; | ||
@@ -13,10 +14,8 @@ exports.open = open; | ||
exports.fromBuffer = fromBuffer; | ||
exports.fromRandomAccessReader = fromRandomAccessReader; | ||
exports.dosDateTimeToDate = dosDateTimeToDate; | ||
exports.ZipFile = ZipFile; | ||
exports.Entry = Entry; | ||
exports.dosDateTimeToDate = dosDateTimeToDate; | ||
exports.RandomAccessReader = RandomAccessReader; | ||
// cd - Central Directory | ||
// cdr - Central Directory Record | ||
// eocdr - End of Central Directory Record | ||
function open(path, options, callback) { | ||
@@ -27,3 +26,5 @@ if (typeof options === "function") { | ||
} | ||
if (options == null) options = {autoClose: true}; | ||
if (options == null) options = {}; | ||
if (options.autoClose == null) options.autoClose = true; | ||
if (options.lazyEntries == null) options.lazyEntries = false; | ||
if (callback == null) callback = defaultCallback; | ||
@@ -44,20 +45,44 @@ fs.open(path, "r", function(err, fd) { | ||
} | ||
if (options == null) options = {autoClose: false}; | ||
if (options == null) options = {}; | ||
if (options.autoClose == null) options.autoClose = false; | ||
if (options.lazyEntries == null) options.lazyEntries = false; | ||
if (callback == null) callback = defaultCallback; | ||
fs.fstat(fd, function(err, stats) { | ||
if (err) return callback(err); | ||
var fdSlicer = fd_slicer.createFromFd(fd, {autoClose: true}); | ||
// this ref is unreffed in zipfile.close() | ||
fdSlicer.ref(); | ||
fromFdSlicer(fdSlicer, stats.size, options, callback); | ||
var reader = fd_slicer.createFromFd(fd, {autoClose: true}); | ||
fromRandomAccessReader(reader, stats.size, options, callback); | ||
}); | ||
} | ||
function fromBuffer(buffer, callback) { | ||
if (callback == null) callback = defaultCallback; | ||
function fromBuffer(buffer, options, callback) { | ||
if (typeof options === "function") { | ||
callback = options; | ||
options = null; | ||
} | ||
if (options == null) options = {}; | ||
options.autoClose = false; | ||
if (options.lazyEntries == null) options.lazyEntries = false; | ||
// i got your open file right here. | ||
var fdSlicer = fd_slicer.createFromBuffer(buffer); | ||
fromFdSlicer(fdSlicer, buffer.length, {}, callback); | ||
var reader = fd_slicer.createFromBuffer(buffer); | ||
fromRandomAccessReader(reader, buffer.length, options, callback); | ||
} | ||
function fromFdSlicer(fdSlicer, totalSize, options, callback) { | ||
function fromRandomAccessReader(reader, totalSize, options, callback) { | ||
if (typeof options === "function") { | ||
callback = options; | ||
options = null; | ||
} | ||
if (options == null) options = {}; | ||
if (options.autoClose == null) options.autoClose = true; | ||
if (options.lazyEntries == null) options.lazyEntries = false; | ||
if (callback == null) callback = defaultCallback; | ||
if (typeof totalSize !== "number") throw new Error("expected totalSize parameter to be a number"); | ||
if (totalSize > Number.MAX_SAFE_INTEGER) { | ||
throw new Error("zip file too large. only file sizes up to 2^52 are supported due to JavaScript's Number type being an IEEE 754 double."); | ||
} | ||
// the matching unref() call is in zipfile.close() | ||
reader.ref(); | ||
// eocdr means End of Central Directory Record. | ||
// search backwards for the eocdr signature. | ||
@@ -73,3 +98,3 @@ // the last field of the eocdr is a variable-length comment. | ||
var bufferReadStart = totalSize - buffer.length; | ||
readFdSlicerNoEof(fdSlicer, buffer, 0, bufferSize, bufferReadStart, function(err) { | ||
readAndAssertNoEof(reader, buffer, 0, bufferSize, bufferReadStart, function(err) { | ||
if (err) return callback(err); | ||
@@ -91,3 +116,3 @@ for (var i = bufferSize - eocdrWithoutCommentSize; i >= 0; i -= 1) { | ||
// 16 - Offset of start of central directory, relative to start of archive | ||
var cdOffset = eocdrBuffer.readUInt32LE(16); | ||
var centralDirectoryOffset = eocdrBuffer.readUInt32LE(16); | ||
// 20 - Comment length | ||
@@ -102,3 +127,47 @@ var commentLength = eocdrBuffer.readUInt16LE(20); | ||
var comment = bufferToString(eocdrBuffer, 22, eocdrBuffer.length, false); | ||
return callback(null, new ZipFile(fdSlicer, cdOffset, totalSize, entryCount, comment, options.autoClose)); | ||
if (!(entryCount === 0xffff || centralDirectoryOffset === 0xffffffff)) { | ||
return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries)); | ||
} | ||
// ZIP64 format | ||
// ZIP64 Zip64 end of central directory locator | ||
var zip64EocdlBuffer = new Buffer(20); | ||
var zip64EocdlOffset = bufferReadStart + i - zip64EocdlBuffer.length; | ||
readAndAssertNoEof(reader, zip64EocdlBuffer, 0, zip64EocdlBuffer.length, zip64EocdlOffset, function(err) { | ||
if (err) return callback(err); | ||
// 0 - zip64 end of central dir locator signature = 0x07064b50 | ||
if (zip64EocdlBuffer.readUInt32LE(0) !== 0x07064b50) { | ||
return callback(new Error("invalid ZIP64 End of Central Directory Locator signature")); | ||
} | ||
// 4 - number of the disk with the start of the zip64 end of central directory | ||
// 8 - relative offset of the zip64 end of central directory record | ||
var zip64EocdrOffset = readUInt64LE(zip64EocdlBuffer, 8); | ||
// 16 - total number of disks | ||
// ZIP64 end of central directory record | ||
var zip64EocdrBuffer = new Buffer(56); | ||
readAndAssertNoEof(reader, zip64EocdrBuffer, 0, zip64EocdrBuffer.length, zip64EocdrOffset, function(err) { | ||
if (err) return callback(err); | ||
// 0 - zip64 end of central dir signature 4 bytes (0x06064b50) | ||
if (zip64EocdrBuffer.readUInt32LE(0) !== 0x06064b50) return callback(new Error("invalid ZIP64 end of central directory record signature")); | ||
// 4 - size of zip64 end of central directory record 8 bytes | ||
// 12 - version made by 2 bytes | ||
// 14 - version needed to extract 2 bytes | ||
// 16 - number of this disk 4 bytes | ||
// 20 - number of the disk with the start of the central directory 4 bytes | ||
// 24 - total number of entries in the central directory on this disk 8 bytes | ||
// 32 - total number of entries in the central directory 8 bytes | ||
entryCount = readUInt64LE(zip64EocdrBuffer, 32); | ||
// 40 - size of the central directory 8 bytes | ||
// 48 - offset of start of central directory with respect to the starting disk number 8 bytes | ||
centralDirectoryOffset = readUInt64LE(zip64EocdrBuffer, 48); | ||
// 56 - zip64 extensible data sector (variable size) | ||
return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries)); | ||
}); | ||
}); | ||
return; | ||
} | ||
@@ -110,15 +179,15 @@ callback(new Error("end of central directory record signature not found")); | ||
util.inherits(ZipFile, EventEmitter); | ||
function ZipFile(fdSlicer, cdOffset, fileSize, entryCount, comment, autoClose) { | ||
function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries) { | ||
var self = this; | ||
EventEmitter.call(self); | ||
self.fdSlicer = fdSlicer; | ||
self.reader = reader; | ||
// forward close events | ||
self.fdSlicer.on("error", function(err) { | ||
self.reader.on("error", function(err) { | ||
// error closing the fd | ||
emitError(self, err); | ||
}); | ||
self.fdSlicer.once("close", function() { | ||
self.reader.once("close", function() { | ||
self.emit("close"); | ||
}); | ||
self.readEntryCursor = cdOffset; | ||
self.readEntryCursor = centralDirectoryOffset; | ||
self.fileSize = fileSize; | ||
@@ -129,6 +198,7 @@ self.entryCount = entryCount; | ||
self.autoClose = !!autoClose; | ||
self.lazyEntries = !!lazyEntries; | ||
self.isOpen = true; | ||
self.emittedError = false; | ||
// make sure events don't fire outta here until the client has a chance to attach listeners | ||
setImmediate(function() { readEntries(self); }); | ||
if (!self.lazyEntries) self.readEntry(); | ||
} | ||
@@ -138,3 +208,3 @@ ZipFile.prototype.close = function() { | ||
this.isOpen = false; | ||
this.fdSlicer.unref(); | ||
this.reader.unref(); | ||
}; | ||
@@ -152,12 +222,16 @@ | ||
function readEntries(self) { | ||
ZipFile.prototype.readEntry = function() { | ||
var self = this; | ||
if (self.entryCount === self.entriesRead) { | ||
// done with metadata | ||
if (self.autoClose) self.close(); | ||
if (self.emittedError) return; | ||
return self.emit("end"); | ||
setImmediate(function() { | ||
if (self.autoClose) self.close(); | ||
if (self.emittedError) return; | ||
self.emit("end"); | ||
}); | ||
return; | ||
} | ||
if (self.emittedError) return; | ||
var buffer = new Buffer(46); | ||
readFdSlicerNoEof(self.fdSlicer, buffer, 0, buffer.length, self.readEntryCursor, function(err) { | ||
readAndAssertNoEof(self.reader, buffer, 0, buffer.length, self.readEntryCursor, function(err) { | ||
if (err) return emitErrorAndAutoClose(self, err); | ||
@@ -203,10 +277,4 @@ if (self.emittedError) return; | ||
// validate file size | ||
if (entry.compressionMethod === 0) { | ||
var msg = "compressed/uncompressed size mismatch for stored file: " + entry.compressedSize + " != " + entry.uncompressedSize; | ||
if (entry.compressedSize !== entry.uncompressedSize) return emitErrorAndAutoClose(self, new Error(msg)); | ||
} | ||
buffer = new Buffer(entry.fileNameLength + entry.extraFieldLength + entry.fileCommentLength); | ||
readFdSlicerNoEof(self.fdSlicer, buffer, 0, buffer.length, self.readEntryCursor, function(err) { | ||
readAndAssertNoEof(self.reader, buffer, 0, buffer.length, self.readEntryCursor, function(err) { | ||
if (err) return emitErrorAndAutoClose(self, err); | ||
@@ -251,2 +319,46 @@ if (self.emittedError) return; | ||
if (entry.uncompressedSize === 0xffffffff || | ||
entry.compressedSize === 0xffffffff || | ||
entry.relativeOffsetOfLocalHeader === 0xffffffff) { | ||
// ZIP64 format | ||
// find the Zip64 Extended Information Extra Field | ||
var zip64EiefBuffer = null; | ||
for (var i = 0; i < entry.extraFields.length; i++) { | ||
var extraField = entry.extraFields[i]; | ||
if (extraField.id === 0x0001) { | ||
zip64EiefBuffer = extraField.data; | ||
break; | ||
} | ||
} | ||
if (zip64EiefBuffer == null) return emitErrorAndAutoClose(self, new Error("expected Zip64 Extended Information Extra Field")); | ||
var index = 0; | ||
// 0 - Original Size 8 bytes | ||
if (entry.uncompressedSize === 0xffffffff) { | ||
if (index + 8 > zip64EiefBuffer.length) return emitErrorAndAutoClose(self, new Error("Zip64 Extended Information Extra Field does not include Original Size")); | ||
entry.uncompressedSize = readUInt64LE(zip64EiefBuffer, index); | ||
index += 8; | ||
} | ||
// 8 - Compressed Size 8 bytes | ||
if (entry.compressedSize === 0xffffffff) { | ||
if (index + 8 > zip64EiefBuffer.length) return emitErrorAndAutoClose(self, new Error("Zip64 Extended Information Extra Field does not include Compressed Size")); | ||
entry.compressedSize = readUInt64LE(zip64EiefBuffer, index); | ||
index += 8; | ||
} | ||
// 16 - Relative Header Offset 8 bytes | ||
if (entry.relativeOffsetOfLocalHeader === 0xffffffff) { | ||
if (index + 8 > zip64EiefBuffer.length) return emitErrorAndAutoClose(self, new Error("Zip64 Extended Information Extra Field does not include Relative Header Offset")); | ||
entry.relativeOffsetOfLocalHeader = readUInt64LE(zip64EiefBuffer, index); | ||
index += 8; | ||
} | ||
// 24 - Disk Start Number 4 bytes | ||
} | ||
// validate file size | ||
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)); | ||
} | ||
} | ||
// validate file name | ||
@@ -257,6 +369,7 @@ if (entry.fileName.indexOf("\\") !== -1) return emitErrorAndAutoClose(self, new Error("invalid characters in fileName: " + entry.fileName)); | ||
self.emit("entry", entry); | ||
readEntries(self); | ||
if (!self.lazyEntries) self.readEntry(); | ||
}); | ||
}); | ||
} | ||
}; | ||
@@ -267,5 +380,5 @@ ZipFile.prototype.openReadStream = function(entry, callback) { | ||
// make sure we don't lose the fd before we open the actual read stream | ||
self.fdSlicer.ref(); | ||
self.reader.ref(); | ||
var buffer = new Buffer(30); | ||
readFdSlicerNoEof(self.fdSlicer, buffer, 0, buffer.length, entry.relativeOffsetOfLocalHeader, function(err) { | ||
readAndAssertNoEof(self.reader, buffer, 0, buffer.length, entry.relativeOffsetOfLocalHeader, function(err) { | ||
try { | ||
@@ -313,15 +426,28 @@ if (err) return callback(err); | ||
} | ||
var stream = self.fdSlicer.createReadStream({start: fileDataStart, end: fileDataEnd}); | ||
var readStream = self.reader.createReadStream({start: fileDataStart, end: fileDataEnd}); | ||
var endpointStream = readStream; | ||
if (compressed) { | ||
var deflateFilter = zlib.createInflateRaw(); | ||
var destroyed = false; | ||
var inflateFilter = zlib.createInflateRaw(); | ||
readStream.on("error", function(err) { | ||
if (!destroyed) inflateFilter.emit("error", err); | ||
}); | ||
var checkerStream = new AssertByteCountStream(entry.uncompressedSize); | ||
deflateFilter.on("error", function(err) { | ||
inflateFilter.on("error", function(err) { | ||
// forward zlib errors to the client-visible stream | ||
checkerStream.emit("error", err); | ||
if (!destroyed) checkerStream.emit("error", err); | ||
}); | ||
stream = stream.pipe(deflateFilter).pipe(checkerStream); | ||
checkerStream.destroy = function() { | ||
destroyed = true; | ||
inflateFilter.unpipe(checkerStream); | ||
readStream.unpipe(inflateFilter); | ||
// TODO: the inflateFilter now causes a memory leak. see Issue #27. | ||
readStream.destroy(); | ||
}; | ||
endpointStream = readStream.pipe(inflateFilter).pipe(checkerStream); | ||
} | ||
callback(null, stream); | ||
callback(null, endpointStream); | ||
} finally { | ||
self.fdSlicer.unref(); | ||
self.reader.unref(); | ||
} | ||
@@ -350,3 +476,3 @@ }); | ||
function readFdSlicerNoEof(fdSlicer, buffer, offset, length, position, callback) { | ||
function readAndAssertNoEof(reader, buffer, offset, length, position, callback) { | ||
if (length === 0) { | ||
@@ -356,3 +482,3 @@ // fs.read will throw an out-of-bounds error if you try to read 0 bytes from a 0 byte file | ||
} | ||
fdSlicer.read(buffer, offset, length, position, function(err, bytesRead) { | ||
reader.read(buffer, offset, length, position, function(err, bytesRead) { | ||
if (err) return callback(err); | ||
@@ -386,2 +512,98 @@ if (bytesRead < length) return callback(new Error("unexpected EOF")); | ||
util.inherits(RandomAccessReader, EventEmitter); | ||
function RandomAccessReader() { | ||
EventEmitter.call(this); | ||
this.refCount = 0; | ||
} | ||
RandomAccessReader.prototype.ref = function() { | ||
this.refCount += 1; | ||
}; | ||
RandomAccessReader.prototype.unref = function() { | ||
var self = this; | ||
self.refCount -= 1; | ||
if (self.refCount > 0) return; | ||
if (self.refCount < 0) throw new Error("invalid unref"); | ||
self.close(onCloseDone); | ||
function onCloseDone(err) { | ||
if (err) return self.emit('error', err); | ||
self.emit('close'); | ||
} | ||
}; | ||
RandomAccessReader.prototype.createReadStream = function(options) { | ||
var start = options.start; | ||
var end = options.end; | ||
if (start === end) { | ||
var emptyStream = new PassThrough(); | ||
setImmediate(function() { | ||
emptyStream.end(); | ||
}); | ||
return emptyStream; | ||
} | ||
var stream = this._readStreamForRange(start, end); | ||
var destroyed = false; | ||
var refUnrefFilter = new RefUnrefFilter(this); | ||
stream.on("error", function(err) { | ||
if (!destroyed) refUnrefFilter.emit("error", err); | ||
}); | ||
refUnrefFilter.destroy = function() { | ||
stream.unpipe(refUnrefFilter); | ||
refUnrefFilter.unref(); | ||
stream.destroy(); | ||
}; | ||
var byteCounter = new AssertByteCountStream(end - start); | ||
refUnrefFilter.on("error", function(err) { | ||
if (!destroyed) byteCounter.emit("error", err); | ||
}); | ||
byteCounter.destroy = function() { | ||
destroyed = true; | ||
refUnrefFilter.unpipe(byteCounter); | ||
refUnrefFilter.destroy(); | ||
}; | ||
return stream.pipe(refUnrefFilter).pipe(byteCounter); | ||
}; | ||
RandomAccessReader.prototype._readStreamForRange = function(start, end) { | ||
throw new Error("not implemented"); | ||
}; | ||
RandomAccessReader.prototype.read = function(buffer, offset, length, position, callback) { | ||
var readStream = this.createReadStream({start: position, end: position + length}); | ||
var writeStream = new Writable(); | ||
var written = 0; | ||
writeStream._write = function(chunk, encoding, cb) { | ||
chunk.copy(buffer, offset + written, 0, chunk.length); | ||
written += chunk.length; | ||
cb(); | ||
}; | ||
writeStream.on("finish", callback); | ||
readStream.on("error", function(error) { | ||
callback(error); | ||
}); | ||
readStream.pipe(writeStream); | ||
}; | ||
RandomAccessReader.prototype.close = function(callback) { | ||
setImmediate(callback); | ||
}; | ||
util.inherits(RefUnrefFilter, PassThrough); | ||
function RefUnrefFilter(context) { | ||
PassThrough.call(this); | ||
this.context = context; | ||
this.context.ref(); | ||
this.unreffedYet = false; | ||
} | ||
RefUnrefFilter.prototype._flush = function(cb) { | ||
this.unref(); | ||
cb(); | ||
}; | ||
RefUnrefFilter.prototype.unref = function(cb) { | ||
if (this.unreffedYet) return; | ||
this.unreffedYet = true; | ||
this.context.unref(); | ||
}; | ||
var cp437 = '\u0000☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ '; | ||
@@ -400,4 +622,16 @@ function bufferToString(buffer, start, end, isUtf8) { | ||
function readUInt64LE(buffer, offset) { | ||
// there is no native function for this, because we can't actually store 64-bit integers precisely. | ||
// after 53 bits, JavaScript's Number type (IEEE 754 double) can't store individual integers anymore. | ||
// but since 53 bits is a whole lot more than 32 bits, we do our best anyway. | ||
var lower32 = buffer.readUInt32LE(offset); | ||
var upper32 = buffer.readUInt32LE(offset + 4); | ||
// we can't use bitshifting here, because JavaScript bitshifting only works on 32-bit integers. | ||
return upper32 * 0x100000000 + lower32; | ||
// as long as we're bounds checking the result of this function against the total file size, | ||
// we'll catch any overflow errors, because we already made sure the total file size was within reason. | ||
} | ||
function defaultCallback(err) { | ||
if (err) throw err; | ||
} |
{ | ||
"name": "yauzl", | ||
"version": "2.3.1", | ||
"version": "2.4.0", | ||
"description": "yet another unzip library for node", | ||
@@ -29,8 +29,9 @@ "main": "index.js", | ||
"dependencies": { | ||
"fd-slicer": "~1.0.1", | ||
"pend": "~1.2.0" | ||
"fd-slicer": "~1.0.1" | ||
}, | ||
"devDependencies": { | ||
"istanbul": "~0.3.4" | ||
"bl": "~1.0.0", | ||
"istanbul": "~0.3.4", | ||
"pend": "~1.2.0" | ||
} | ||
} |
223
README.md
@@ -30,15 +30,29 @@ # yauzl | ||
var fs = require("fs"); | ||
var path = require("path"); | ||
var mkdirp = require("mkdirp"); // or similar | ||
yauzl.open("path/to/file.zip", function(err, zipfile) { | ||
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 '/' | ||
return; | ||
mkdirp(entry.fileName, function(err) { | ||
if (err) throw err; | ||
zipfile.readEntry(); | ||
}); | ||
} else { | ||
// file entry | ||
zipfile.openReadStream(entry, function(err, readStream) { | ||
if (err) throw err; | ||
// ensure parent directory exists | ||
mkdirp(path.dirname(entry.fileName), function(err) { | ||
if (err) throw err; | ||
readStream.pipe(fs.createWriteStream(entry.fileName)); | ||
readStream.on("end", function() { | ||
zipfile.readEntry(); | ||
}); | ||
}); | ||
}); | ||
} | ||
zipfile.openReadStream(entry, function(err, readStream) { | ||
if (err) throw err; | ||
// ensure parent directory exists, and then: | ||
readStream.pipe(fs.createWriteStream(entry.fileName)); | ||
}); | ||
}); | ||
@@ -50,3 +64,3 @@ }); | ||
The default for every `callback` parameter is: | ||
The default for every optional `callback` parameter is: | ||
@@ -63,4 +77,20 @@ ```js | ||
`options` may be omitted or `null` and defaults to `{autoClose: true}`. | ||
`options` may be omitted or `null`. The defaults are `{autoClose: true, lazyEntries: false}`. | ||
`autoClose` is effectively equivalent to: | ||
```js | ||
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](https://github.com/thejoshwolfe/yauzl/issues/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. | ||
### fromFd(fd, [options], [callback]) | ||
@@ -77,13 +107,7 @@ | ||
`options` may be omitted or `null` and defaults to `{autoClose: false}`. | ||
`autoClose` is effectively equivalent to: | ||
`options` may be omitted or `null`. The defaults are `{autoClose: false, lazyEntries: false}`. | ||
See `open()` for the meaning of the options. | ||
```js | ||
zipfile.once("end", function() { | ||
zipfile.close(); | ||
}); | ||
``` | ||
### fromBuffer(buffer, [options], [callback]) | ||
### fromBuffer(buffer, [callback]) | ||
Like `fromFd()`, but reads from a RAM buffer instead of an open file. | ||
@@ -97,2 +121,18 @@ `buffer` is a `Buffer`. | ||
`options` may be omitted or `null`. The defaults are `{lazyEntries: false}`. | ||
See `open()` for the meaning of the options. | ||
The `autoClose` option is ignored for this method. | ||
### fromRandomAccessReader(reader, totalSize, [options], [callback]) | ||
This method of creating 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](#class-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}`. | ||
See `open()` for the meaning of the options. | ||
### dosDateTimeToDate(date, time) | ||
@@ -108,3 +148,3 @@ | ||
The constructor for the class is not part of the public API. | ||
Use `open()`, `fromFd()`, or `fromBuffer()` instead. | ||
Use `open()`, `fromFd()`, `fromBuffer()`, or `fromRandomAccessReader()` instead. | ||
@@ -114,2 +154,3 @@ #### Event: "entry" | ||
Callback gets `(entry)`, which is an `Entry`. | ||
See `open()` and `readEntry()` for when this event is emitted. | ||
@@ -119,2 +160,3 @@ #### Event: "end" | ||
Emitted after the last `entry` event has been emitted. | ||
See `open()` and `readEntry()` for more info on when this event is emitted. | ||
@@ -125,6 +167,9 @@ #### Event: "close" | ||
This is after calling `close()` (or after the `end` event when `autoClose` is `true`), | ||
and after all streams created from `openReadStream()` have emitted their `end` events. | ||
and after all stream pipelines created from `openReadStream()` have finished reading data from the fd. | ||
This event is never emitted if this `ZipFile` was acquired from `fromBuffer()`. | ||
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" | ||
@@ -137,4 +182,18 @@ | ||
#### openReadStream(entry, [callback]) | ||
#### 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, callback) | ||
`entry` must be an `Entry` object from this `ZipFile`. | ||
@@ -146,3 +205,3 @@ `callback` gets `(err, readStream)`, where `readStream` is a `Readable Stream`. | ||
It's possible for the `readStream` to emit errors for several reasons. | ||
It's possible for the `readStream` it to emit errors for several reasons. | ||
For example, if zlib cannot decompress the data, the zlib error will be emitted from the `readStream`. | ||
@@ -160,2 +219,9 @@ Two more error cases are if the decompressed data has too many or too few actual bytes | ||
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 (see `randomAccessReader._readStreamForRange()`) | ||
in order for calls to `readStream.destroy()` to work in this context. | ||
#### close() | ||
@@ -165,7 +231,14 @@ | ||
and closes the fd after all streams created by `openReadStream()` have emitted their `end` events. | ||
If this object's `end` event has not been emitted yet, this function causes undefined behavior. | ||
If `autoClose` is `true` in the original `open()` or `fromFd()` call, | ||
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. | ||
#### isOpen | ||
@@ -219,3 +292,5 @@ | ||
where `id` is a `Number` and `data` is a `Buffer`. | ||
None of the extra fields are considered significant by this library. | ||
This library looks for and reads the ZIP64 Extended Information Extra Field (0x0001) | ||
in order to support ZIP64 format zip files. | ||
None of the other fields are considered significant by this library. | ||
@@ -234,2 +309,49 @@ #### comment | ||
### 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. | ||
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()`). | ||
The returned stream *must* implement a method `.destroy()` | ||
if you call `readStream.destroy()` on streams you get from `openReadStream()`. | ||
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 | ||
@@ -242,3 +364,3 @@ | ||
* 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()`, or `fromBuffer()`. | ||
* 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()`. | ||
@@ -262,2 +384,16 @@ | ||
### Limitted 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 | ||
@@ -269,3 +405,3 @@ | ||
If the "number of this disk" field in the End of Central Directory Record is not `0`, | ||
the `open()`, `fromFd()`, or `fromBuffer()` `callback` will receive an `err`. | ||
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: | ||
@@ -306,6 +442,2 @@ | ||
### No ZIP64 Support | ||
A ZIP64 file will probably cause undefined behavior. | ||
### Data Descriptors Are Ignored | ||
@@ -326,1 +458,28 @@ | ||
This library makes no attempt to interpret the Language Encoding Flag. | ||
## Change History | ||
* 2.4.0 | ||
* Add ZIP64 support. [issue #6](https://github.com/thejoshwolfe/yazl/issues/6) | ||
* Add `lazyEntries` option. [issue #22](https://github.com/thejoshwolfe/yazl/issues/22) | ||
* Add `readStream.destroy()` method. [issue #26](https://github.com/thejoshwolfe/yazl/issues/26) | ||
* Add `fromRandomAccessReader()`. [issue #14](https://github.com/thejoshwolfe/yazl/issues/14) | ||
* Add `examples/unzip.js`. | ||
* 2.3.1 | ||
* Documentation updates. | ||
* 2.3.0 | ||
* Check that `uncompressedSize` is correct, or else emit an error. [issue #13](https://github.com/thejoshwolfe/yazl/issues/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. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46753
1
573
466
3
- Removedpend@~1.2.0