unzip-stream
Advanced tools
Comparing version 0.2.1 to 0.2.2
@@ -7,3 +7,3 @@ var Transform = require('stream').Transform; | ||
if (!(this instanceof ParserStream)) { | ||
return new ParserStream(); | ||
return new ParserStream(opts); | ||
} | ||
@@ -15,3 +15,3 @@ | ||
this.opts = opts || {}; | ||
this.unzipStream = new UnzipStream(); | ||
this.unzipStream = new UnzipStream(this.opts); | ||
@@ -53,2 +53,2 @@ var self = this; | ||
module.exports = ParserStream; | ||
module.exports = ParserStream; |
@@ -9,12 +9,13 @@ var binary = require('binary'); | ||
const states = { | ||
START: 0, | ||
LOCAL_FILE_HEADER: 1, | ||
LOCAL_FILE_HEADER_SUFFIX: 2, | ||
FILE_DATA: 3, | ||
FILE_DATA_END: 4, | ||
DATA_DESCRIPTOR: 5, | ||
CENTRAL_DIRECTORY_FILE_HEADER: 6, | ||
CENTRAL_DIRECTORY_FILE_HEADER_SUFFIX: 7, | ||
CENTRAL_DIRECTORY_END: 8, | ||
CENTRAL_DIRECTORY_END_COMMENT: 9, | ||
STREAM_START: 0, | ||
START: 1, | ||
LOCAL_FILE_HEADER: 2, | ||
LOCAL_FILE_HEADER_SUFFIX: 3, | ||
FILE_DATA: 4, | ||
FILE_DATA_END: 5, | ||
DATA_DESCRIPTOR: 6, | ||
CENTRAL_DIRECTORY_FILE_HEADER: 7, | ||
CENTRAL_DIRECTORY_FILE_HEADER_SUFFIX: 8, | ||
CENTRAL_DIRECTORY_END: 9, | ||
CENTRAL_DIRECTORY_END_COMMENT: 10, | ||
@@ -31,5 +32,5 @@ ERROR: 99 | ||
function UnzipStream() { | ||
function UnzipStream(options) { | ||
if (!(this instanceof UnzipStream)) { | ||
return new UnzipStream(); | ||
return new UnzipStream(options); | ||
} | ||
@@ -39,4 +40,5 @@ | ||
this.options = options || {}; | ||
this.data = new Buffer(''); | ||
this.state = states.START; | ||
this.state = states.STREAM_START; | ||
this.parsedEntity = null; | ||
@@ -52,2 +54,3 @@ this.outStreamInfo = {}; | ||
switch (this.state) { | ||
case states.STREAM_START: | ||
case states.START: | ||
@@ -88,2 +91,3 @@ requiredLength = 4; | ||
switch (this.state) { | ||
case states.STREAM_START: | ||
case states.START: | ||
@@ -101,4 +105,12 @@ switch (chunk.readUInt32LE(0)) { | ||
default: | ||
var isStreamStart = this.state === states.STREAM_START; | ||
this.state = states.ERROR; | ||
this.emit("error", new Error("Invalid signature in zip file")); | ||
var errMsg = isStreamStart ? "Not a valid zip file" : "Invalid signature in zip file"; | ||
if (this.options.debug) { | ||
var sig = chunk.readUInt32LE(0); | ||
var asString; | ||
try { asString = chunk.slice(0, 4).toString(); } catch (e) {} | ||
console.log("Unexpected signature in zip file: 0x" + sig.toString(16), '"' + asString + '"'); | ||
} | ||
this.emit("error", new Error(errMsg)); | ||
return chunk.length; | ||
@@ -116,4 +128,19 @@ } | ||
var entry = new Entry(); | ||
entry.path = chunk.slice(0, this.parsedEntity.fileNameLength).toString(); | ||
this.parsedEntity.extra = this._readExtraFields(chunk.slice(this.parsedEntity.fileNameLength, this.parsedEntity.fileNameLength + this.parsedEntity.extraFieldLength)); | ||
var isUtf8 = (this.parsedEntity.flags & 0x800) !== 0; | ||
entry.path = this._decodeString(chunk.slice(0, this.parsedEntity.fileNameLength), isUtf8); | ||
var extraDataBuffer = chunk.slice(this.parsedEntity.fileNameLength, this.parsedEntity.fileNameLength + this.parsedEntity.extraFieldLength); | ||
var extra = this._readExtraFields(extraDataBuffer); | ||
if (extra && extra.parsed && extra.parsed.path && !isUtf8) { | ||
entry.path = extra.parsed.path; | ||
} | ||
this.parsedEntity.extra = extra.parsed; | ||
if (this.options.debug) { | ||
const debugObj = Object.assign({}, this.parsedEntity, { | ||
path: entry.path, | ||
flags: '0x' + this.parsedEntity.flags.toString(16), | ||
extraFields: extra && extra.debug | ||
}); | ||
console.log("decoded local file entry:", JSON.stringify(debugObj, null, 2)); | ||
} | ||
this._prepareOutStream(this.parsedEntity, entry); | ||
@@ -135,2 +162,28 @@ | ||
// got file name in chunk[0..] | ||
var isUtf8 = (this.parsedEntity.flags & 0x800) !== 0; | ||
var path = this._decodeString(chunk.slice(0, this.parsedEntity.fileNameLength), isUtf8); | ||
var extraDataBuffer = chunk.slice(this.parsedEntity.fileNameLength, this.parsedEntity.fileNameLength + this.parsedEntity.extraFieldLength); | ||
var extra = this._readExtraFields(extraDataBuffer); | ||
if (extra && extra.parsed && extra.parsed.path && !isUtf8) { | ||
path = extra.parsed.path; | ||
} | ||
this.parsedEntity.extra = extra.parsed; | ||
var isUnix = ((this.parsedEntity.versionMadeBy & 0xff00) >> 8) === 3; | ||
var unixAttrs, isSymlink; | ||
if (isUnix) { | ||
unixAttrs = this.parsedEntity.externalFileAttributes >>> 16; | ||
var fileType = unixAttrs >>> 12; | ||
isSymlink = (fileType & 012) === 012; // __S_IFLNK | ||
} | ||
if (this.options.debug) { | ||
const debugObj = Object.assign({}, this.parsedEntity, { | ||
path: path, | ||
flags: '0x' + this.parsedEntity.flags.toString(16), | ||
unixAttrs: unixAttrs && '0' + unixAttrs.toString(8), | ||
isSymlink: isSymlink, | ||
extraFields: extra.debug, | ||
}); | ||
console.log("decoded central directory file entry:", JSON.stringify(debugObj, null, 2)); | ||
} | ||
this.state = states.START; | ||
@@ -267,2 +320,6 @@ | ||
var extra = {}; | ||
var result = { parsed: extra }; | ||
if (this.options.debug) { | ||
result.debug = []; | ||
} | ||
var index = 0; | ||
@@ -278,4 +335,9 @@ while (index < data.length) { | ||
var fieldType = undefined; | ||
switch (vars.extraId) { | ||
case 0x000a: | ||
fieldType = "NTFS extra field"; | ||
break; | ||
case 0x5455: | ||
fieldType = "extended timestamp"; | ||
var timestampFields = data.readUInt8(index); | ||
@@ -295,13 +357,107 @@ var offset = 1; | ||
break; | ||
case 0x7075: | ||
fieldType = "Info-ZIP Unicode Path Extra Field"; | ||
var fieldVer = data.readUInt8(index); | ||
if (fieldVer === 1) { | ||
var offset = 1; | ||
// TODO: should be checking this against our path buffer | ||
var nameCrc32 = data.readUInt32LE(index + offset); | ||
offset += 4; | ||
var pathBuffer = data.slice(index + offset); | ||
extra.path = pathBuffer.toString(); | ||
} | ||
break; | ||
case 0x000d: | ||
case 0x5855: | ||
fieldType = vars.extraId === 0x000d ? "PKWARE Unix" : "Info-ZIP UNIX (type 1)"; | ||
var offset = 0; | ||
if (vars.extraSize >= 8) { | ||
var atime = new Date(data.readUInt32LE(index + offset) * 1000); | ||
offset += 4; | ||
var mtime = new Date(data.readUInt32LE(index + offset) * 1000); | ||
offset += 4; | ||
extra.atime = atime; | ||
extra.mtime = mtime; | ||
if (vars.extraSize >= 12) { | ||
var uid = data.readUInt16LE(index + offset); | ||
offset += 2; | ||
var gid = data.readUInt16LE(index + offset); | ||
offset += 2; | ||
extra.uid = uid; | ||
extra.gid = gid; | ||
} | ||
} | ||
break; | ||
case 0x7855: | ||
fieldType = "Info-ZIP UNIX (type 2)"; | ||
var offset = 0; | ||
if (vars.extraSize >= 4) { | ||
var uid = data.readUInt16LE(index + offset); | ||
offset += 2; | ||
var gid = data.readUInt16LE(index + offset); | ||
offset += 2; | ||
extra.uid = uid; | ||
extra.gid = gid; | ||
} | ||
break; | ||
case 0x7875: | ||
/* TODO: handle | ||
var uidSize = data.readUInt8(index + 1); | ||
var gidSize = data.readUInt8(index + 1 + uidSize); | ||
*/ | ||
fieldType = "Info-ZIP New Unix"; | ||
var offset = 0; | ||
var extraVer = data.readUInt8(index); | ||
offset += 1; | ||
if (extraVer === 1) { | ||
var uidSize = data.readUInt8(index + offset); | ||
offset += 1; | ||
if (uidSize <= 6) { | ||
extra.uid = data.readUIntLE(index + offset, uidSize); | ||
} | ||
offset += uidSize; | ||
var gidSize = data.readUInt8(index + offset); | ||
offset += 1; | ||
if (gidSize <= 6) { | ||
extra.gid = data.readUIntLE(index + offset, gidSize); | ||
} | ||
} | ||
break; | ||
case 0x756e: | ||
fieldType = "ASi Unix"; | ||
var offset = 0; | ||
if (vars.extraSize >= 14) { | ||
var crc = data.readUInt32LE(index + offset); | ||
offset += 4; | ||
var mode = data.readUInt16LE(index + offset); | ||
offset += 2; | ||
var sizdev = data.readUInt32LE(index + offset); | ||
offset += 4; | ||
var uid = data.readUInt16LE(index + offset); | ||
offset += 2; | ||
var gid = data.readUInt16LE(index + offset); | ||
offset += 2; | ||
extra.mode = mode; | ||
extra.uid = uid; | ||
extra.gid = gid; | ||
if (vars.extraSize > 14) { | ||
var start = index + offset; | ||
var end = index + vars.extraSize - 14; | ||
var symlinkName = this._decodeString(data.slice(start, end)); | ||
extra.symlink = symlinkName; | ||
} | ||
} | ||
break; | ||
} | ||
if (this.options.debug) { | ||
result.debug.push({ | ||
extraId: '0x' + vars.extraId.toString(16), | ||
description: fieldType, | ||
data: data.slice(index, index + vars.extraSize).inspect() | ||
}); | ||
} | ||
index += vars.extraSize; | ||
} | ||
return extra; | ||
return result; | ||
} | ||
@@ -357,2 +513,19 @@ | ||
const cp437 = '\u0000☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ '; | ||
UnzipStream.prototype._decodeString = function (buffer, isUtf8) { | ||
if (isUtf8) { | ||
return buffer.toString('utf8'); | ||
} | ||
// allow passing custom decoder | ||
if (this.options.decodeString) { | ||
return this.options.decodeString(buffer); | ||
} | ||
let result = ""; | ||
for (var i=0; i<buffer.length; i++) { | ||
result += cp437[buffer[i]]; | ||
} | ||
return result; | ||
} | ||
UnzipStream.prototype._parseOrOutput = function (encoding, cb) { | ||
@@ -359,0 +532,0 @@ var consume; |
{ | ||
"name": "unzip-stream", | ||
"version": "0.2.1", | ||
"version": "0.2.2", | ||
"description": "Process zip files using streaming API", | ||
@@ -5,0 +5,0 @@ "author": "Michal Hruby <michal.mhr@gmail.com>", |
@@ -74,4 +74,11 @@ # unzip-stream | ||
### Extra options | ||
The `Parse` and `Extract` methods allow passing an object with `decodeString` property which will be used to decode non-utf8 file names in the archive. If not specified a fallback will be used. | ||
```javascript | ||
let parser = unzip.Parse({ decodeString: (buffer) => { return iconvLite.decode(buffer, 'iso-8859-2'); } }); | ||
input.pipe(parser).pipe(...); | ||
``` | ||
### What's missing? | ||
Currently only ZIP files up to version 2.1 are supported - which means no Zip64 support. There's also no support for encrypted (password protected) zips, or symlinks. |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
High entropy strings
Supply chain riskContains high entropy strings. This could be a sign of encrypted data, leaked secrets or obfuscated code.
Found 1 instance in 1 package
54185
925
84
7
1