[Pkg-javascript-commits] [node-yazl] 01/02: Imported Upstream version 2.0.1
Andrew Kelley
andrewrk-guest at moszumanska.debian.org
Mon Sep 29 17:28:27 UTC 2014
This is an automated email from the git hooks/post-receive script.
andrewrk-guest pushed a commit to branch master
in repository node-yazl.
commit 87c6fb94a65c8b64c84e653f1b5f7d226ecd2943
Author: Andrew Kelley <superjoe30 at gmail.com>
Date: Mon Sep 29 16:46:33 2014 +0000
Imported Upstream version 2.0.1
---
.gitignore | 1 +
.npmignore | 1 +
LICENSE | 21 ++++
README.md | 219 +++++++++++++++++++++++++++++++++++++
index.js | 351 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package.json | 29 +++++
test/test.js | 57 ++++++++++
test/zip.js | 24 ++++
8 files changed, 703 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..65e3ba2
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+test/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37538d4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Josh Wolfe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3171c41
--- /dev/null
+++ b/README.md
@@ -0,0 +1,219 @@
+# yazl
+
+yet another zip library for node
+
+Design principles:
+
+ * 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.
+ * Prefer to open input files one at a time than all at once.
+ This is slightly suboptimal for time performance,
+ but avoids OS-imposed limits on the number of simultaneously open file handles.
+
+## Usage
+
+```js
+var yazl = require("yazl");
+
+var zipfile = new yazl.ZipFile();
+zipfile.addFile("file1.txt", "file1.txt");
+// (add only files, not directories)
+zipfile.addFile("path/to/file.txt", "path/in/zipfile.txt");
+// pipe() can be called any time after the constructor
+zipfile.outputStream.pipe(fs.createWriteStream("output.zip")).on("close", function() {
+ console.log("done");
+});
+// alternate apis for adding files:
+zipfile.addReadStream(process.stdin, "stdin.txt", {
+ mtime: new Date(),
+ mode: 0100664, // -rw-rw-r--
+});
+zipfile.addBuffer(new Buffer("hello"), "hello.txt", {
+ mtime: new Date(),
+ mode: 0100664, // -rw-rw-r--
+});
+// call end() after all the files have been added
+zipfile.end();
+```
+
+## API
+
+### Class: ZipFile
+
+#### new ZipFile()
+
+No parameters.
+Nothing can go wrong.
+
+#### addFile(realPath, metadataPath, [options])
+
+Adds a file from the file system at `realPath` into the zipfile as `metadataPath`.
+Typically `metadataPath` would be calculated as `path.relative(root, realPath)`.
+Unzip programs would extract the file from the zipfile as `metadataPath`.
+`realPath` is not stored in the zipfile.
+
+This function throws an error if `metadataPath` starts with `"/"` or `/[A-Za-z]:\//`
+or if it contains `".."` path segments or `"\\"`.
+These would be illegal file names according to the spec.
+
+`options` may be omitted or null has the following structure and default values:
+
+```js
+{
+ mtime: stats.mtime, // optional
+ mode: stats.mode, // optional
+ compress: true, // optional
+}
+```
+
+Use `options.mtime` and/or `options.mode` to override the values
+that would normally be obtained by the `fs.Stats` for the `realPath`.
+The mtime and mode (unix permission bits and file type) are stored in the zip file
+in the fields "last mod file time", "last mod file date", and "external file attributes".
+yazl does not store group and user ids in the zip file.
+
+Internally, `fs.stat()` is called immediately in the `addFile` function,
+and `fs.createReadStream()` is used later when the file data is actually required.
+Throughout adding and encoding `n` files with `addFile()`,
+the number of simultaneous open files is `O(1)`, probably just 1 at a time.
+
+#### addReadStream(readStream, metadataPath, options)
+
+Adds a file to the zip file whose content is read from `readStream`.
+See `addFile()` for info about the `metadataPath` parameter.
+`options` is an `Object` and has the following structure:
+
+```js
+{
+ mtime: new Date(), // required
+ mode: 0100664, // required
+ compress: true, // optional (default true)
+ size: 12345, // optional
+}
+```
+
+See `addFile()` for the meaning of `mtime` and `mode`.
+If `size` is given, it will be checked against the actual number of bytes in the `readStream`,
+and an error will be emitted if there is a mismatch.
+
+#### addBuffer(buffer, metadataPath, options)
+
+Adds a file to the zip file whose content is `buffer`.
+See `addFile()` for info about the `metadataPath` parameter.
+`options` is an `Object` and has the following structure:
+
+```js
+{
+ mtime: new Date(), // required
+ mode: 0100664, // required
+ compress: true, // optional (default true)
+}
+```
+
+See `addFile()` for the meaning of `mtime` and `mode`.
+
+#### end([finalSizeCallback])
+
+Indicates that no more files will be added via `addFile()`, `addReadStream()`, or `addBuffer()`.
+Some time after calling this function, `outputStream` will be ended.
+
+If specified and non-null, `finalSizeCallback` is given the parameters `(finalSize)`
+sometime during or after the call to `end()`.
+`finalSize` is of type `Number` and can either be `-1`
+or the guaranteed eventual size in bytes of the output data that can be read from `outputStream`.
+
+If `finalSize` is `-1`, it means means the final size is too hard to guess before processing the input file data.
+This will happen if and only if the `compress` option is `true` on any call to `addFile()`, `addReadStream()`, or `addBuffer()`,
+or if `addReadStream()` is called and the optional `size` option is not given.
+In other words, clients should know whether they're going to get a `-1` or a real value
+by looking at how they are calling this function.
+
+The call to `finalSizeCallback` might be delayed if yazl is still waiting for `fs.Stats` for an `addFile()` entry.
+If `addFile()` was never called, `finalSizeCallback` will be called during the call to `end()`.
+It is not required to start piping data from `outputStream` before `finalSizeCallback` is called.
+`finalSizeCallback` will be called only once, and only if this is the first call to `end()`.
+
+#### outputStream
+
+A readable stream that will produce the contents of the zip file.
+It is typical to pipe this stream to a writable stream created from `fs.createWriteStream()`.
+
+Internally, large amounts of file data are piped to `outputStream` using `pipe()`,
+which means throttling happens appropriately when this stream is piped to a slow destination.
+
+Data becomes available in this stream soon after calling one of `addFile()`, `addReadStream()`, or `addBuffer()`.
+Clients can call `pipe()` on this stream at any time,
+such as immediately after getting a new `ZipFile` instance, or long after calling `end()`.
+
+### dateToDosDateTime(jsDate)
+
+`jsDate` is a `Date` instance.
+Returns `{date: date, time: time}`, where `date` and `time` are unsigned 16-bit integers.
+
+## Output Structure
+
+The Zip File Spec leaves a lot of flexibility up to the zip file creator.
+This section explains and justifies yazl's interpretation and decisions regarding this flexibility.
+
+This section is probably not useful to yazl clients,
+but may be interesting to unzip implementors and zip file enthusiasts.
+
+### Disk Numbers
+
+All values related to disk numbers are `0`,
+because yazl has no multi-disk archive support.
+
+### Version Made By
+
+Always `0x031e`.
+This is the value reported by a Linux build of Info-Zip.
+Instead of experimenting with different values of this field
+to see how different unzip clients would behave,
+yazl mimics Info-Zip, which should work everywhere.
+
+Note that the top byte means "UNIX"
+and has implications in the External File Attributes.
+
+### Version Needed to Extract
+
+Always `0x0014`.
+Without this value, Info-Zip, and possibly other unzip implementations,
+refuse to acknowledge General Purpose Bit `8`, which enables utf8 filename encoding.
+
+### General Purpose Bit Flag
+
+Bit `8` is always set.
+Filenames are always encoded in utf8, even if the result is indistinguishable from ascii.
+
+Bit `3` is set in the Local File Header.
+To support both a streaming input and streaming output api,
+it is impossible to know the crc32 before processing the file data.
+File Descriptors are given after each file data with this information, as per the spec.
+But remember a complete metadata listing is still always available in the central directory record,
+so if unzip implementations are relying on that, like they should,
+none of this paragraph will matter anyway.
+
+All other bits are unset.
+
+### Internal File Attributes
+
+Always `0`.
+The "apparently an ASCII or text file" bit is always unset meaning "apparently binary".
+This kind of determination is outside the scope of yazl,
+and is probably not significant in any modern unzip implementation.
+
+### External File Attributes
+
+Always `stats.mode << 16`.
+This is apparently the convention for "version made by" = `0x03xx` (UNIX).
+
+### Directory Entries
+
+yazl does not record directories themselves as separate entries in the zipfile metadata.
+Instead, file entries with paths (such as "directory/file.txt") imply the need for their parent directories.
+Unzip clients seems to respect this style of pathing,
+and the zip file spec does not specify what is standard in this regard.
+
+Directory entries would be required to archive empty directories (see issue #4).
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..26e8987
--- /dev/null
+++ b/index.js
@@ -0,0 +1,351 @@
+var fs = require("fs");
+var Transform = require("stream").Transform;
+var PassThrough = require("stream").PassThrough;
+var zlib = require("zlib");
+var util = require("util");
+var EventEmitter = require("events").EventEmitter;
+var crc32 = require("buffer-crc32");
+
+exports.ZipFile = ZipFile;
+exports.dateToDosDateTime = dateToDosDateTime;
+
+util.inherits(ZipFile, EventEmitter);
+function ZipFile() {
+ this.outputStream = new PassThrough();
+ this.entries = [];
+ this.outputStreamCursor = 0;
+ this.ended = false;
+}
+
+ZipFile.prototype.addFile = function(realPath, metadataPath, options) {
+ var self = this;
+ validateMetadataPath(metadataPath);
+ if (options == null) options = {};
+
+ var entry = new Entry(metadataPath, options);
+ self.entries.push(entry);
+ fs.stat(realPath, function(err, stats) {
+ if (err) return self.emit("error", err);
+ if (!stats.isFile()) return self.emit("error", new Error("not a file: " + realPath));
+ entry.uncompressedSize = stats.size;
+ if (entry.lastModFileTime == null || entry.lastModFileDate == null) entry.setLastModDate(stats.mtime);
+ if (entry.externalFileAttributes == null) entry.setFileAttributesMode(stats.mode);
+ entry.setFileDataPumpFunction(function() {
+ var readStream = fs.createReadStream(realPath);
+ entry.state = Entry.FILE_DATA_IN_PROGRESS;
+ readStream.on("error", function(err) {
+ self.emit("error", err);
+ });
+ pumpFileDataReadStream(self, entry, readStream);
+ });
+ pumpEntries(self);
+ });
+};
+
+ZipFile.prototype.addReadStream = function(readStream, metadataPath, options) {
+ var self = this;
+ validateMetadataPath(metadataPath);
+ if (options == null) options = {};
+ var entry = new Entry(metadataPath, options);
+ validateFilelessEntryProperties(entry);
+ self.entries.push(entry);
+ entry.setFileDataPumpFunction(function() {
+ entry.state = Entry.FILE_DATA_IN_PROGRESS;
+ pumpFileDataReadStream(self, entry, readStream);
+ pumpEntries(self);
+ });
+ pumpEntries(self);
+};
+
+ZipFile.prototype.addBuffer = function(buffer, metadataPath, options) {
+ var self = this;
+ validateMetadataPath(metadataPath);
+ if (options == null) options = {};
+ var entry = new Entry(metadataPath, options);
+ validateFilelessEntryProperties(entry, buffer.length);
+ entry.uncompressedSize = buffer.length;
+ entry.crc32 = crc32.unsigned(buffer);
+ self.entries.push(entry);
+ if (!entry.compress) {
+ setCompressedBuffer(buffer);
+ } else {
+ zlib.deflateRaw(buffer, function(err, compressedBuffer) {
+ setCompressedBuffer(compressedBuffer);
+ });
+ }
+ function setCompressedBuffer(compressedBuffer) {
+ entry.compressedSize = compressedBuffer.length;
+ entry.setFileDataPumpFunction(function() {
+ writeToOutputStream(self, compressedBuffer);
+ writeToOutputStream(self, entry.getFileDescriptor());
+ entry.state = Entry.FILE_DATA_DONE;
+ pumpEntries(self);
+ });
+ pumpEntries(self);
+ }
+};
+
+ZipFile.prototype.end = function(finalSizeCallback) {
+ if (this.ended) return;
+ this.ended = true;
+ this.finalSizeCallback = finalSizeCallback;
+ pumpEntries(this);
+};
+
+function writeToOutputStream(self, buffer) {
+ self.outputStream.write(buffer);
+ self.outputStreamCursor += buffer.length;
+}
+
+function pumpFileDataReadStream(self, entry, readStream) {
+ var crc32Watcher = new Crc32Watcher();
+ var uncompressedSizeCounter = new ByteCounter();
+ var compressor = entry.compress ? new zlib.DeflateRaw() : new PassThrough();
+ var compressedSizeCounter = new ByteCounter();
+ readStream.pipe(crc32Watcher)
+ .pipe(uncompressedSizeCounter)
+ .pipe(compressor)
+ .pipe(compressedSizeCounter)
+ .pipe(self.outputStream, {end: false});
+ compressedSizeCounter.on("end", function() {
+ entry.crc32 = crc32Watcher.crc32;
+ if (entry.uncompressedSize == null) {
+ entry.uncompressedSize = uncompressedSizeCounter.byteCount;
+ } else {
+ if (entry.uncompressedSize !== uncompressedSizeCounter.byteCount) return self.emit("error", new Error("file data stream has unexpected number of bytes"));
+ }
+ entry.compressedSize = compressedSizeCounter.byteCount;
+ self.outputStreamCursor += entry.compressedSize;
+ writeToOutputStream(self, entry.getFileDescriptor());
+ entry.state = Entry.FILE_DATA_DONE;
+ pumpEntries(self);
+ });
+}
+
+function pumpEntries(self) {
+ // first check if finalSize is finally known
+ if (self.ended && self.finalSizeCallback != null) {
+ var finalSize = calculateFinalSize(self);
+ if (finalSize != null) {
+ // we have an answer
+ self.finalSizeCallback(finalSize);
+ self.finalSizeCallback = null;
+ }
+ }
+
+ // pump entries
+ var entry = getFirstNotDoneEntry();
+ function getFirstNotDoneEntry() {
+ for (var i = 0; i < self.entries.length; i++) {
+ var entry = self.entries[i];
+ if (entry.state < Entry.FILE_DATA_DONE) return entry;
+ }
+ return null;
+ }
+ if (entry != null) {
+ // this entry is not done yet
+ if (entry.state < Entry.READY_TO_PUMP_FILE_DATA) return; // input file not open yet
+ if (entry.state === Entry.FILE_DATA_IN_PROGRESS) return; // we'll get there
+ // start with local file header
+ entry.relativeOffsetOfLocalHeader = self.outputStreamCursor;
+ var localFileHeader = entry.getLocalFileHeader();
+ writeToOutputStream(self, localFileHeader);
+ entry.doFileDataPump();
+ } else {
+ // all cought up on writing entries
+ if (self.ended) {
+ // head for the exit
+ self.offsetOfStartOfCentralDirectory = self.outputStreamCursor;
+ self.entries.forEach(function(entry) {
+ var centralDirectoryRecord = entry.getCentralDirectoryRecord();
+ writeToOutputStream(self, centralDirectoryRecord);
+ });
+ writeToOutputStream(self, getEndOfCentralDirectoryRecord(self));
+ self.outputStream.end();
+ }
+ }
+}
+
+function calculateFinalSize(self) {
+ var result = 0;
+ for (var i = 0; i < self.entries.length; i++) {
+ var entry = self.entries[i];
+ // compression is too hard to predict
+ if (entry.compress) return -1;
+ if (entry.state >= Entry.READY_TO_PUMP_FILE_DATA) {
+ // if addReadStream was called without providing the size, we can't predict the final size
+ if (entry.uncompressedSize == null) return -1;
+ } else {
+ // if we're still waiting for fs.stat, we might learn the size someday
+ if (entry.uncompressedSize == null) return null;
+ }
+ result += LOCAL_FILE_HEADER_FIXED_SIZE + entry.utf8FileName.length +
+ entry.uncompressedSize +
+ FILE_DESCRIPTOR_SIZE +
+ CENTRAL_DIRECTORY_RECORD_FIXED_SIZE + entry.utf8FileName.length;
+ }
+ result += END_OF_CENTRAL_DIRECTORY_RECORD_SIZE;
+ return result;
+}
+
+var END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 22;
+function getEndOfCentralDirectoryRecord(self) {
+ var buffer = new Buffer(END_OF_CENTRAL_DIRECTORY_RECORD_SIZE);
+ buffer.writeUInt32LE(0x06054b50, 0); // end of central dir signature 4 bytes (0x06054b50)
+ buffer.writeUInt16LE(0, 4); // number of this disk 2 bytes
+ buffer.writeUInt16LE(0, 6); // number of the disk with the start of the central directory 2 bytes
+ buffer.writeUInt16LE(self.entries.length, 8); // total number of entries in the central directory on this disk 2 bytes
+ buffer.writeUInt16LE(self.entries.length, 10); // total number of entries in the central directory 2 bytes
+ buffer.writeUInt32LE(self.outputStreamCursor - self.offsetOfStartOfCentralDirectory, 12); // size of the central directory 4 bytes
+ buffer.writeUInt32LE(self.offsetOfStartOfCentralDirectory, 16); // offset of start of central directory with respect to the starting disk number 4 bytes
+ buffer.writeUInt16LE(0, 20); // .ZIP file comment length 2 bytes
+ /* no comment */ // .ZIP file comment (variable size)
+ return buffer;
+}
+
+function validateMetadataPath(metadataPath) {
+ if (metadataPath.indexOf("\\") !== -1) throw new Error("invalid characters in path: " + metadataPath);
+ if (/^[a-zA-Z]:/.test(metadataPath) || /^\//.test(metadataPath)) throw new Error("absolute path: " + metadataPath);
+ if (metadataPath.split("/").indexOf("..") !== -1) throw new Error("invalid relative path: " + metadataPath);
+}
+function validateFilelessEntryProperties(entry, length) {
+ if (entry.lastModFileTime == null || entry.lastModFileDate == null) throw new Error("missing options.mtime");
+ if (entry.externalFileAttributes == null) throw new Error("missing options.mode");
+ if (entry.uncompressedSize != null && length != null && entry.uncompressedSize !== length) throw new Error("invalid options.size");
+}
+
+// this class is not part of the public API
+function Entry(metadataPath, options) {
+ this.utf8FileName = new Buffer(metadataPath);
+ if (this.utf8FileName.length > 0xffff) throw new Error("utf8 file name too long. " + utf8FileName.length + " > " + 0xffff);
+ this.state = Entry.WAITING_FOR_METADATA;
+ if (options.mtime != null) this.setLastModDate(options.mtime);
+ if (options.mode != null) this.setFileAttributesMode(options.mode);
+ this.uncompressedSize = null; // unknown
+ if (options.size != null) this.uncompressedSize = options.size;
+ this.compress = true; // default
+ if (options.compress != null) this.compress = !!options.compress;
+}
+Entry.WAITING_FOR_METADATA = 0;
+Entry.READY_TO_PUMP_FILE_DATA = 1;
+Entry.FILE_DATA_IN_PROGRESS = 2;
+Entry.FILE_DATA_DONE = 3;
+Entry.prototype.setLastModDate = function(date) {
+ var dosDateTime = dateToDosDateTime(date);
+ this.lastModFileTime = dosDateTime.time;
+ this.lastModFileDate = dosDateTime.date;
+};
+Entry.prototype.setFileAttributesMode = function(mode) {
+ if ((mode & 0xffff) !== mode) throw new Error("invalid mode. expected: 0 <= " + mode + " <= " + 0xffff);
+ // http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute/14727#14727
+ this.externalFileAttributes = (mode << 16) >>> 0;
+};
+Entry.prototype.setFileDataPumpFunction = function(doFileDataPump) {
+ this.doFileDataPump = doFileDataPump;
+ this.state = Entry.READY_TO_PUMP_FILE_DATA;
+};
+var LOCAL_FILE_HEADER_FIXED_SIZE = 30;
+// this version enables utf8 filename encoding
+var VERSION_NEEDED_TO_EXTRACT = 0x0014;
+// this is the "version made by" reported by linux info-zip.
+var VERSION_MADE_BY_INFO_ZIP = 0x031e;
+var FILE_NAME_IS_UTF8 = 1 << 11;
+var UNKNOWN_CRC32_AND_FILE_SIZES = 1 << 3;
+Entry.prototype.getLocalFileHeader = function() {
+ var fixedSizeStuff = new Buffer(LOCAL_FILE_HEADER_FIXED_SIZE);
+ var generalPurposeBitFlag = UNKNOWN_CRC32_AND_FILE_SIZES | FILE_NAME_IS_UTF8;
+ fixedSizeStuff.writeUInt32LE(0x04034b50, 0); // local file header signature 4 bytes (0x04034b50)
+ fixedSizeStuff.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT, 4); // version needed to extract 2 bytes
+ fixedSizeStuff.writeUInt16LE(generalPurposeBitFlag, 6); // general purpose bit flag 2 bytes
+ fixedSizeStuff.writeUInt16LE(this.getCompressionMethod(), 8); // compression method 2 bytes
+ fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 10); // last mod file time 2 bytes
+ fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 12); // last mod file date 2 bytes
+ fixedSizeStuff.writeUInt32LE(0, 14); // crc-32 4 bytes
+ fixedSizeStuff.writeUInt32LE(0, 18); // compressed size 4 bytes
+ fixedSizeStuff.writeUInt32LE(0, 22); // uncompressed size 4 bytes
+ fixedSizeStuff.writeUInt16LE(this.utf8FileName.length, 26); // file name length 2 bytes
+ fixedSizeStuff.writeUInt16LE(0, 28); // extra field length 2 bytes
+ return Buffer.concat([
+ fixedSizeStuff,
+ this.utf8FileName, // file name (variable size)
+ /* no extra fields */ // extra field (variable size)
+ ]);
+};
+var FILE_DESCRIPTOR_SIZE = 12
+Entry.prototype.getFileDescriptor = function() {
+ var buffer = new Buffer(FILE_DESCRIPTOR_SIZE);
+ buffer.writeUInt32LE(this.crc32, 0); // crc-32 4 bytes
+ buffer.writeUInt32LE(this.compressedSize, 4); // compressed size 4 bytes
+ buffer.writeUInt32LE(this.uncompressedSize, 8); // uncompressed size 4 bytes
+ return buffer;
+}
+var CENTRAL_DIRECTORY_RECORD_FIXED_SIZE = 46;
+Entry.prototype.getCentralDirectoryRecord = function() {
+ var fixedSizeStuff = new Buffer(CENTRAL_DIRECTORY_RECORD_FIXED_SIZE);
+ fixedSizeStuff.writeUInt32LE(0x02014b50, 0); // central file header signature 4 bytes (0x02014b50)
+ fixedSizeStuff.writeUInt16LE(VERSION_MADE_BY_INFO_ZIP, 4); // version made by 2 bytes
+ fixedSizeStuff.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT, 6); // version needed to extract 2 bytes
+ fixedSizeStuff.writeUInt16LE(FILE_NAME_IS_UTF8, 8); // general purpose bit flag 2 bytes
+ fixedSizeStuff.writeUInt16LE(this.getCompressionMethod(), 10); // compression method 2 bytes
+ fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 12); // last mod file time 2 bytes
+ fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 14); // last mod file date 2 bytes
+ fixedSizeStuff.writeUInt32LE(this.crc32, 16); // crc-32 4 bytes
+ fixedSizeStuff.writeUInt32LE(this.compressedSize, 20); // compressed size 4 bytes
+ fixedSizeStuff.writeUInt32LE(this.uncompressedSize, 24); // uncompressed size 4 bytes
+ fixedSizeStuff.writeUInt16LE(this.utf8FileName.length, 28); // file name length 2 bytes
+ fixedSizeStuff.writeUInt16LE(0, 30); // extra field length 2 bytes
+ fixedSizeStuff.writeUInt16LE(0, 32); // file comment length 2 bytes
+ fixedSizeStuff.writeUInt16LE(0, 34); // disk number start 2 bytes
+ fixedSizeStuff.writeUInt16LE(0, 36); // internal file attributes 2 bytes
+ fixedSizeStuff.writeUInt32LE(this.externalFileAttributes, 38); // external file attributes 4 bytes
+ fixedSizeStuff.writeUInt32LE(this.relativeOffsetOfLocalHeader, 42); // relative offset of local header 4 bytes
+ return Buffer.concat([
+ fixedSizeStuff,
+ this.utf8FileName, // file name (variable size)
+ /* no extra fields */ // extra field (variable size)
+ /* empty comment */ // file comment (variable size)
+ ]);
+};
+Entry.prototype.getCompressionMethod = function() {
+ var NO_COMPRESSION = 0;
+ var DEFLATE_COMPRESSION = 8;
+ return this.compress ? DEFLATE_COMPRESSION : NO_COMPRESSION;
+};
+
+function dateToDosDateTime(jsDate) {
+ var date = 0;
+ date |= jsDate.getDate() & 0x1f; // 1-31
+ date |= ((jsDate.getMonth() + 1) & 0xf) << 5; // 0-11, 1-12
+ date |= ((jsDate.getFullYear() - 1980) & 0x7f) << 9; // 0-128, 1980-2108
+
+ var time = 0;
+ time |= Math.floor(jsDate.getSeconds() / 2); // 0-59, 0-29 (lose odd numbers)
+ time |= (jsDate.getMinutes() & 0x3f) << 5; // 0-59
+ time |= (jsDate.getHours() & 0x1f) << 11; // 0-23
+
+ return {date: date, time: time};
+}
+
+function defaultCallback(err) {
+ if (err) throw err;
+}
+
+util.inherits(ByteCounter, Transform);
+function ByteCounter(options) {
+ Transform.call(this, options);
+ this.byteCount = 0;
+}
+ByteCounter.prototype._transform = function(chunk, encoding, cb) {
+ this.byteCount += chunk.length;
+ cb(null, chunk);
+};
+
+util.inherits(Crc32Watcher, Transform);
+function Crc32Watcher(options) {
+ Transform.call(this, options);
+ this.crc32 = 0;
+}
+Crc32Watcher.prototype._transform = function(chunk, encoding, cb) {
+ this.crc32 = crc32.unsigned(chunk, this.crc32);
+ cb(null, chunk);
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fba070a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "yazl",
+ "version": "2.0.1",
+ "description": "yet another zip library for node",
+ "main": "index.js",
+ "scripts": {
+ "test": "node test/test.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/thejoshwolfe/yazl.git"
+ },
+ "keywords": [
+ "zip"
+ ],
+ "author": "Josh Wolfe <thejoshwolfe at gmail.com>",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/thejoshwolfe/yazl/issues"
+ },
+ "homepage": "https://github.com/thejoshwolfe/yazl",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3"
+ },
+ "devDependencies": {
+ "bl": "~0.9.3",
+ "yauzl": "~2.0.0"
+ }
+}
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..61985a6
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,57 @@
+var fs = require("fs");
+var yazl = require("../");
+var yauzl = require("yauzl");
+var BufferList = require("bl");
+
+(function() {
+ var fileMetadata = {
+ mtime: new Date(),
+ mode: 0100664,
+ };
+ var zipfile = new yazl.ZipFile();
+ zipfile.addFile(__filename, "unicōde.txt");
+ zipfile.addFile(__filename, "without-compression.txt", {compress: false});
+ zipfile.addReadStream(fs.createReadStream(__filename), "readStream.txt", fileMetadata);
+ var expectedContents = fs.readFileSync(__filename);
+ zipfile.addBuffer(expectedContents, "with/directories.txt", fileMetadata);
+ zipfile.end(function(finalSize) {
+ if (finalSize !== -1) throw new Error("finalSize is impossible to know before compression");
+ zipfile.outputStream.pipe(new BufferList(function(err, data) {
+ if (err) throw err;
+ yauzl.fromBuffer(data, function(err, zipfile) {
+ if (err) throw err;
+ zipfile.on("entry", function(entry) {
+ zipfile.openReadStream(entry, function(err, readStream) {
+ if (err) throw err;
+ readStream.pipe(new BufferList(function(err, data) {
+ if (err) throw err;
+ if (expectedContents.toString("binary") !== data.toString("binary")) throw new Error("unexpected contents");
+ console.log(entry.fileName + ": pass");
+ }));
+ });
+ });
+ });
+ }));
+ });
+})();
+
+(function() {
+ var fileMetadata = {
+ mtime: new Date(),
+ mode: 0100664,
+ compress: false,
+ };
+ var zipfile = new yazl.ZipFile();
+ zipfile.addFile(__filename, "asdf.txt", {compress: false});
+ zipfile.addFile(__filename, "fdsa.txt", {compress: false});
+ zipfile.addBuffer(new Buffer("buffer"), "buffer.txt", fileMetadata);
+ fileMetadata.size = "stream".length;
+ zipfile.addReadStream(new BufferList().append("stream"), "stream.txt", fileMetadata);
+ zipfile.end(function(finalSize) {
+ if (finalSize === -1) throw new Error("finalSize should be known");
+ zipfile.outputStream.pipe(new BufferList(function(err, data) {
+ if (data.length !== finalSize) throw new Error("finalSize prediction is wrong. " + finalSize + " !== " + data.length);
+ console.log("finalSize: pass");
+ }));
+ });
+})();
diff --git a/test/zip.js b/test/zip.js
new file mode 100644
index 0000000..a8c6a96
--- /dev/null
+++ b/test/zip.js
@@ -0,0 +1,24 @@
+var usage = "node " + __filename.replace(/.*[\/\\]/, "") + " " +
+ "[FILE | --compress | --no-compress]... -o OUTPUT.zip";
+var yazl = require("../");
+var fs = require("fs");
+
+var zipfile = new yazl.ZipFile();
+var options = {compress: false};
+
+var args = process.argv.slice(2);
+if (Math.max(args.indexOf("-h"), args.indexOf("--help")) !== -1) throw new Error("usage: " + usage);
+var outputFileIndex = args.indexOf("-o");
+if (outputFileIndex === -1) throw new Error("missing -o");
+zipfile.outputStream.pipe(fs.createWriteStream(args[outputFileIndex + 1]));
+args.splice(outputFileIndex, 2);
+args.forEach(function(arg) {
+ if (/--compress/.test(arg)) {
+ options.compress = true;
+ } else if (/--no-compress/.test(arg)) {
+ options.compress = false;
+ } else {
+ zipfile.addFile(arg, arg, options);
+ }
+});
+zipfile.end();
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-yazl.git
More information about the Pkg-javascript-commits
mailing list