[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