Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

yauzl

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

yauzl - npm Package Compare versions

Comparing version 0.0.0 to 1.0.0

205

index.js
var fs = require("fs");
var zlib = require("zlib");
var FdSlicer = require("fd-slicer");
var util = require("util");
var EventEmitter = require("events").EventEmitter;
var Iconv = require("iconv").Iconv;

@@ -14,7 +17,12 @@ exports.open = open;

function open(path, callback) {
function open(path, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (options == null) options = {autoClose: true};
if (callback == null) callback = defaultCallback;
fs.open(path, "r", function(err, fd) {
if (err) return callback(err);
fopen(fd, function(err, zipfile) {
fopen(fd, options, function(err, zipfile) {
if (err) fs.close(fd, defaultCallback);

@@ -26,3 +34,8 @@ callback(err, zipfile);

function fopen(fd, callback) {
function fopen(fd, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (options == null) options = {autoClose: false};
if (callback == null) callback = defaultCallback;

@@ -66,7 +79,5 @@ fs.fstat(fd, function(err, stats) {

// 22 - Comment
var comment = new Buffer(commentLength);
// the comment length is typcially 0.
// copy from the original buffer to make sure we're not pinning it from being GC'ed.
eocdrBuffer.copy(comment, 0, 22, eocdrBuffer.length);
return callback(null, new ZipFile(fd, cdOffset, entryCount, comment));
// the encoding is always cp437.
var comment = bufferToString(eocdrBuffer, 22, eocdrBuffer.length, false);
return callback(null, new ZipFile(fd, cdOffset, entryCount, comment, options.autoClose));
}

@@ -78,44 +89,49 @@ callback(new Error("end of central directory record signature not found"));

function ZipFile(fd, cdOffset, entryCount, comment) {
this.fdSlicer = new FdSlicer(fd);
this.readEntryCursor = cdOffset;
this.entryCount = entryCount;
this.comment = comment;
this.entriesRead = 0;
this.isReadingEntry = false;
util.inherits(ZipFile, EventEmitter);
function ZipFile(fd, cdOffset, entryCount, comment, autoClose) {
var self = this;
EventEmitter.call(self);
self.fdSlicer = new FdSlicer(fd, {autoClose: true});
self.fdSlicer.ref();
// forward close events
self.fdSlicer.on("error", function(err) {
// error closing the fd
self.emit("error", err);
});
self.fdSlicer.once("close", function() {
self.emit("close");
});
self.readEntryCursor = cdOffset;
self.entryCount = entryCount;
self.comment = comment;
self.entriesRead = 0;
self.autoClose = !!autoClose;
self.isOpen = true;
// make sure events don't fire outta here until the client has a chance to attach listeners
setImmediate(function() { readEntries(self); });
}
ZipFile.prototype.close = function(callback) {
if (callback == null) callback = defaultCallback;
fs.close(this.fdSlicer.fd, callback);
ZipFile.prototype.close = function() {
if (!this.isOpen) return;
this.isOpen = false;
this.fdSlicer.unref();
};
ZipFile.prototype.readEntries = function(callback) {
var self = this;
if (callback == null) callback = defaultCallback;
self.entries = [];
// setImmediate here to make sure callback is called asynchronously even if there are 0 entries left.
setImmediate(keepReading);
function keepReading() {
if (self.entriesRemaining() === 0) return callback(null, self.entries);
self.readEntry(function(err, entry) {
if (err) return callback(err);
self.entries.push(entry);
keepReading();
});
function emitErrorAndAutoClose(self, err) {
if (self.autoClose) self.close();
self.emit("error", err);
}
function readEntries(self) {
if (self.entryCount === self.entriesRead) {
// done with metadata
if (self.autoClose) self.close();
return self.emit("end");
}
};
ZipFile.prototype.entriesRemaining = function() {
return this.entryCount - this.entriesRead;
};
ZipFile.prototype.readEntry = function(callback) {
var self = this;
if (self.isReadingEntry) throw new Error("readEntry already in progress");
self.isReadingEntry = true;
if (callback == null) callback = defaultCallback;
var buffer = new Buffer(46);
readFdSlicerNoEof(this.fdSlicer, buffer, 0, buffer.length, this.readEntryCursor, function(err) {
if (err) return callback(err);
readFdSlicerNoEof(self.fdSlicer, buffer, 0, buffer.length, self.readEntryCursor, function(err) {
if (err) return emitErrorAndAutoClose(self, err);
var entry = new Entry();
// 0 - Central directory file header signature
var signature = buffer.readUInt32LE(0);
if (signature !== 0x02014b50) return callback(new Error("invalid central directory file header signature: 0x" + signature.toString(16)));
if (signature !== 0x02014b50) return emitErrorAndAutoClose(self, new Error("invalid central directory file header signature: 0x" + signature.toString(16)));
// 4 - Version made by

@@ -157,7 +173,6 @@ entry.versionMadeBy = buffer.readUInt16LE(4);

readFdSlicerNoEof(self.fdSlicer, buffer, 0, buffer.length, self.readEntryCursor, function(err) {
if (err) return callback(err);
if (err) return emitErrorAndAutoClose(self, err);
// 46 - File name
var encoding = entry.generalPurposeBitFlag & 0x800 ? "utf8" : "ascii";
// TODO: replace ascii with CP437 using https://github.com/bnoordhuis/node-iconv
entry.fileName = buffer.toString(encoding, 0, entry.fileNameLength);
var isUtf8 = entry.generalPurposeBitFlag & 0x800
entry.fileName = bufferToString(buffer, 0, entry.fileNameLength);

@@ -184,53 +199,63 @@ // 46+n - Extra field

// 46+n+m - File comment
entry.fileComment = buffer.toString(encoding, fileCommentStart, fileCommentStart + entry.fileCommentLength);
entry.fileComment = bufferToString(buffer, fileCommentStart, fileCommentStart + entry.fileCommentLength);
self.readEntryCursor += buffer.length;
self.entriesRead += 1;
self.isReadingEntry = false;
callback(null, entry);
// validate file name
if (entry.fileName.indexOf("\\") !== -1) return emitErrorAndAutoClose(self, new Error("invalid characters in fileName: " + entry.fileName));
if (/^[a-zA-Z]:/.exec(entry.fileName) || /^\//.exec(entry.fileName)) return emitErrorAndAutoClose(self, new Error("absolute path: " + entry.fileName));
self.emit("entry", entry);
readEntries(self);
});
});
};
}
ZipFile.prototype.openReadStream = function(entry, callback) {
var self = this;
if (!self.isOpen) return self.emit("error", new Error("closed"));
// make sure we don't lose the fd before we open the actual read stream
self.fdSlicer.ref();
var buffer = new Buffer(30);
readFdSlicerNoEof(self.fdSlicer, buffer, 0, buffer.length, entry.relativeOffsetOfLocalHeader, function(err) {
if (err) return callback(err);
// 0 - Local file header signature = 0x04034b50
var signature = buffer.readUInt32LE(0);
if (signature !== 0x04034b50) return callback(new Error("invalid local file header signature: 0x" + signature.toString(16)));
// all this should be redundant
// 4 - Version needed to extract (minimum)
// 6 - General purpose bit flag
// 8 - Compression method
// 10 - File last modification time
// 12 - File last modification date
// 14 - CRC-32
// 18 - Compressed size
// 22 - Uncompressed size
// 26 - File name length (n)
var fileNameLength = buffer.readUInt16LE(26);
// 28 - Extra field length (m)
var extraFieldLength = buffer.readUInt16LE(28);
// 30 - File name
// 30+n - Extra field
var localFileHeaderEnd = entry.relativeOffsetOfLocalHeader + buffer.length + fileNameLength + extraFieldLength;
var filterStream = null;
if (entry.compressionMethod === 0) {
// 0 - The file is stored (no compression)
} else if (entry.compressionMethod === 8) {
// 8 - The file is Deflated
filterStream = zlib.createInflateRaw();
} else {
return callback(new Error("unsupported compression method: " + entry.compressionMethod));
try {
if (err) return callback(err);
// 0 - Local file header signature = 0x04034b50
var signature = buffer.readUInt32LE(0);
if (signature !== 0x04034b50) return callback(new Error("invalid local file header signature: 0x" + signature.toString(16)));
// all this should be redundant
// 4 - Version needed to extract (minimum)
// 6 - General purpose bit flag
// 8 - Compression method
// 10 - File last modification time
// 12 - File last modification date
// 14 - CRC-32
// 18 - Compressed size
// 22 - Uncompressed size
// 26 - File name length (n)
var fileNameLength = buffer.readUInt16LE(26);
// 28 - Extra field length (m)
var extraFieldLength = buffer.readUInt16LE(28);
// 30 - File name
// 30+n - Extra field
var localFileHeaderEnd = entry.relativeOffsetOfLocalHeader + buffer.length + fileNameLength + extraFieldLength;
var filterStream = null;
if (entry.compressionMethod === 0) {
// 0 - The file is stored (no compression)
} else if (entry.compressionMethod === 8) {
// 8 - The file is Deflated
filterStream = zlib.createInflateRaw();
} else {
return callback(new Error("unsupported compression method: " + entry.compressionMethod));
}
var fileDataStart = localFileHeaderEnd;
var fileDataEnd = fileDataStart + entry.compressedSize;
var stream = self.fdSlicer.createReadStream({start: fileDataStart, end: fileDataEnd});
if (filterStream != null) {
stream = stream.pipe(filterStream);
}
callback(null, stream);
} finally {
self.fdSlicer.unref();
}
var fileDataStart = localFileHeaderEnd;
var fileDataEnd = fileDataStart + entry.compressedSize;
var stream = self.fdSlicer.createReadStream({start: fileDataStart, end: fileDataEnd});
if (filterStream != null) {
stream = stream.pipe(filterStream);
}
callback(null, stream);
});

@@ -256,4 +281,12 @@ };

}
var cp437_to_utf8 = new Iconv("cp437", "utf8");
function bufferToString(buffer, start, end, isUtf8) {
if (isUtf8) {
return buffer.toString("utf8", start, end);
} else {
return cp437_to_utf8.convert(buffer.slice(start, end)).toString("utf8");
}
}
function defaultCallback(err) {
if (err) throw err;
}
{
"name": "yauzl",
"version": "0.0.0",
"version": "1.0.0",
"description": "yet another unzip library for node",
"main": "index.js",
"scripts": {
"test": "node test/test.js"
},
"repository": {

@@ -20,4 +23,6 @@ "type": "git",

"dependencies": {
"fd-slicer": "~0.2.1"
"fd-slicer": "~0.2.1",
"iconv": "~2.1.4",
"pend": "~1.1.3"
}
}

@@ -11,3 +11,3 @@ # yauzl

* Don't block the JavaScript thread.
Use and provide async apis.
Use and provide async APIs.
* Keep memory usage under control.

@@ -20,13 +20,15 @@ Don't attempt to buffer entire files in RAM at once.

var yauzl = require("yauzl");
var fs = require('fs');
var fs = require("fs");
yauzl.open("path/to/file.zip", function(err, zipfile) {
if (err) throw err;
zipfile.readEntries(function(err, entries) {
if (err) throw err;
entries.forEach(function(entry) {
zipfile.openReadStream(entry, function(err, readStream) {
if (err) throw err;
readStream.pipe(fs.createWriteStream(entry.fileName));
});
zipfile.on("entry", function(entry) {
if (/\/$/.exec(entry.fileName)) {
// directory file names end with '/'
return;
}
zipfile.openReadStream(entry, function(err, readStream) {
if (err) throw err;
// ensure parent directory exists, and then:
readStream.pipe(fs.createWriteStream(entry.fileName));
});

@@ -47,8 +49,10 @@ });

### open(path, [callback])
### open(path, [options], [callback])
Calls `fs.open(path, "r")` and gives the `fd` and `callback` to `fopen` below.
Calls `fs.open(path, "r")` and gives the `fd`, `options`, and `callback` to `fopen` below.
### fopen(fd, [callback])
`options` may be omitted or `null` and defaults to `{autoClose: true}`.
### fopen(fd, [options], [callback])
Reads from the fd, which is presumed to be an open .zip file.

@@ -63,15 +67,30 @@ Note that random access is required by the zip file specification,

### class ZipFile
`options` may be omitted or `null` and defaults to `{autoClose: false}`.
`autoClose` is effectively equivalent to:
```js
zipfile.once("end", function() {
zipfile.close();
});
```
### Class: ZipFile
The constructor for the class is not part of the public API.
Use `open` or `fopen` instead.
#### close([callback])
#### Event: "entry"
Calls `fs.close(fd, callback)`.
Callback gets `(entry)`, which is an `Entry`.
#### readEntries([callback])
#### Event: "end"
`callback` gets `(err, entries)`, where `entries` is an `Array` of `Entry` objects.
Emitted after the last `entry` event has been 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 streams created from `openReadStream` have emitted their `end` events.
#### openReadStream(entry, [callback])

@@ -83,18 +102,17 @@

the read stream provides the decompressed data.
If this zipfile is already closed (see `close`), the `callback` will receive an `err`.
#### entriesRemaining()
#### close([callback])
Returns the number of entries in this `ZipFile` that have not yet been returned by `readEntry`.
Causes all future calls to `openReadStream` to fail,
and calls `fs.close(fd, callback)` 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.
#### readEntry([callback])
If `autoClose` is `true` in the original `open` or `fopen` call,
this function will be called automatically effectively in response to this object's `end` event.
Most clients should use the `readEntries` function.
`readEntry` and `entriesRemaining` provide low-level access for reading one entry at a time.
This can be useful if the index were very large, and you wanted to start reading entries right away.
#### isOpen
Calling this function directly sabotages the `readEntries` function.
You must not call this function before any previous call to this function completes by calling its `callback`.
`Boolean`. `true` until `close` is called; then it's `false`.
TODO: really? This API is super sketchy.
#### entryCount

@@ -106,5 +124,5 @@

`Buffer`. TODO: decode with `cp473`.
`String`. Always decoded with `CP437` per the spec.
### class Entry
### Class: Entry

@@ -114,3 +132,3 @@ Objects of this class represent Central Directory Records.

These fields are numbers:
These fields are of type `Number`:

@@ -126,5 +144,5 @@ * `versionMadeBy`

* `uncompressedSize`
* `fileNameLength`
* `extraFieldLength`
* `fileCommentLength`
* `fileNameLength` (bytes)
* `extraFieldLength` (bytes)
* `fileCommentLength` (bytes)
* `internalFileAttributes`

@@ -137,9 +155,9 @@ * `externalFileAttributes`

`String`.
The bytes in the file are decoded with `utf8` if `generalPurposeBitFlag & 0x800`, as per the spec.
Otherwise, the file name is decoded with `ascii`, which is technically not correct.
The correct default encoding is `cp473`.
Following the spec, the bytes for the file name are decoded with
`utf8` if `generalPurposeBitFlag & 0x800`, otherwise with `CP437`.
#### extraFields
`Array` with each entry in the form `{id: id, data: data}`, where `id` is a `Number` and `data` is a `Buffer`.
`Array` with each entry in the form `{id: id, data: data}`,
where `id` is a `Number` and `data` is a `Buffer`.
None of the extra fields are considered significant by this library.

@@ -153,10 +171,7 @@

### Default Charset Should Be CP437
Currently, the default charset is `ascii`, but it should be `cp473`.
### No Multi-Disk Archive Support
This library does not support multi-disk zip files.
This API was intended for a zip file to span multiple floppy disks, which probably never happens now.
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`,

@@ -163,0 +178,0 @@ the `open` or `fopen` `callback` will receive an `err`.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc