[zlib] added dictionary support
authorFedor Indutny <fedor.indutny@gmail.com>
Fri, 2 Dec 2011 07:53:56 +0000 (11:53 +0400)
committerisaacs <i@izs.me>
Tue, 6 Dec 2011 01:58:31 +0000 (17:58 -0800)
doc/api/zlib.markdown
lib/zlib.js
src/node_zlib.cc
test/simple/test-zlib-dictionary.js [new file with mode: 0644]

index 475723e88ba0993c8742dc89bff6218f90b76f19..6bd2c095dda646a4218c45caeeb401e82c95ce41 100644 (file)
@@ -215,6 +215,7 @@ relevant when compressing, and are ignored by the decompression classes.
 * level (compression only)
 * memLevel (compression only)
 * strategy (compression only)
+* dictionary (deflate/inflate only, empty dictionary by default)
 
 See the description of `deflateInit2` and `inflateInit2` at
 <http://zlib.net/manual.html#Advanced> for more information on these.
index 840927b770d217d0eb0a2c7d41b1f8803f917aa3..eccb9806fe6c40cdba009a09717b3b17eaf276b3 100644 (file)
@@ -259,11 +259,18 @@ function Zlib(opts, Binding) {
     }
   }
 
+  if (opts.dictionary) {
+    if (!Buffer.isBuffer(opts.dictionary)) {
+      throw new Error('Invalid dictionary: it should be a Buffer instance');
+    }
+  }
+
   this._binding = new Binding();
   this._binding.init(opts.windowBits || exports.Z_DEFAULT_WINDOWBITS,
                      opts.level || exports.Z_DEFAULT_COMPRESSION,
                      opts.memLevel || exports.Z_DEFAULT_MEMLEVEL,
-                     opts.strategy || exports.Z_DEFAULT_STRATEGY);
+                     opts.strategy || exports.Z_DEFAULT_STRATEGY,
+                     opts.dictionary);
 
   this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK;
   this._buffer = new Buffer(this._chunkSize);
index 7c07ef3949e7b9f3799edf0f126f92da550a629d..3c9621aaaa3272e7a93171503ec1411b2ce5cab3 100644 (file)
@@ -64,6 +64,7 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
  public:
 
   ZCtx() : ObjectWrap() {
+    dictionary_ = NULL;
   }
 
   ~ZCtx() {
@@ -72,6 +73,8 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
     } else if (mode == INFLATE || mode == GUNZIP || mode == INFLATERAW) {
       (void)inflateEnd(&strm_);
     }
+
+    if (dictionary_ != NULL) delete[] dictionary_;
   }
 
   // write(flush, in, in_off, in_len, out, out_off, out_len)
@@ -163,6 +166,22 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
       case GUNZIP:
       case INFLATERAW:
         err = inflate(&(ctx->strm_), ctx->flush_);
+
+        // If data was encoded with dictionary
+        if (err == Z_NEED_DICT) {
+          assert(ctx->dictionary_ != NULL && "Stream has no dictionary");
+
+          // Load it
+          err = inflateSetDictionary(
+              &(ctx->strm_),
+              ctx->dictionary_,
+              ctx->dictionary_len_
+          );
+          assert(err == Z_OK && "Failed to set dictionary");
+
+          // And try to decode again
+          err = inflate(&(ctx->strm_), ctx->flush_);
+        }
         break;
       default:
         assert(0 && "wtf?");
@@ -206,8 +225,8 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
   Init(const Arguments& args) {
     HandleScope scope;
 
-    assert(args.Length() == 4 &&
-           "init(windowBits, level, memLevel, strategy)");
+    assert((args.Length() == 4 || args.Length() == 5) &&
+           "init(windowBits, level, memLevel, strategy, [dictionary])");
 
     ZCtx<mode> *ctx = ObjectWrap::Unwrap< ZCtx<mode> >(args.This());
 
@@ -227,7 +246,19 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
             strategy == Z_FIXED ||
             strategy == Z_DEFAULT_STRATEGY) && "invalid strategy");
 
-    Init(ctx, level, windowBits, memLevel, strategy);
+    char* dictionary = NULL;
+    size_t dictionary_len = 0;
+    if (args.Length() >= 5 && Buffer::HasInstance(args[4])) {
+      Local<Object> dictionary_ = args[4]->ToObject();
+
+      dictionary_len = Buffer::Length(dictionary_);
+      dictionary = new char[dictionary_len];
+
+      memcpy(dictionary, Buffer::Data(dictionary_), dictionary_len);
+    }
+
+    Init(ctx, level, windowBits, memLevel, strategy,
+         dictionary, dictionary_len);
     return Undefined();
   }
 
@@ -236,7 +267,9 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
        int level,
        int windowBits,
        int memLevel,
-       int strategy) {
+       int strategy,
+       char* dictionary,
+       size_t dictionary_len) {
     ctx->level_ = level;
     ctx->windowBits_ = windowBits;
     ctx->memLevel_ = memLevel;
@@ -282,8 +315,29 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
         assert(0 && "wtf?");
     }
 
-    ctx->init_done_ = true;
     assert(err == Z_OK);
+
+    ctx->dictionary_ = reinterpret_cast<Bytef *>(dictionary);
+    ctx->dictionary_len_ = dictionary_len;
+
+    if (dictionary != NULL) {
+      switch (mode) {
+        case DEFLATE:
+        case DEFLATERAW:
+          err = deflateSetDictionary(
+              &(ctx->strm_),
+              ctx->dictionary_,
+              dictionary_len
+          );
+          break;
+        default:
+          break;
+      }
+
+      assert(err == Z_OK && "Failed to set dictionary");
+    }
+
+    ctx->init_done_ = true;
   }
 
  private:
@@ -296,6 +350,9 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
   int memLevel_;
   int strategy_;
 
+  Bytef* dictionary_;
+  size_t dictionary_len_;
+
   int flush_;
 
   int chunk_size_;
diff --git a/test/simple/test-zlib-dictionary.js b/test/simple/test-zlib-dictionary.js
new file mode 100644 (file)
index 0000000..d16e415
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+// test compression/decompresion with dictionary
+
+var common = require('../common.js');
+var assert = require('assert');
+var zlib = require('zlib');
+var path = require('path');
+
+var spdyDict = new Buffer([
+  'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
+  'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
+  'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
+  '-agent10010120020120220320420520630030130230330430530630740040140240340440',
+  '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
+  'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
+  'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
+  'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
+  'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
+  'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
+  'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
+  'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
+  '.1statusversionurl\0'
+].join(''));
+
+var deflate = zlib.createDeflate({ dictionary: spdyDict });
+var inflate = zlib.createInflate({ dictionary: spdyDict });
+
+var input = [
+  'HTTP/1.1 200 Ok',
+  'Server: node.js',
+  'Content-Length: 0',
+  ''
+].join('\r\n');
+
+// Put data into deflate stream
+deflate.on('data', function(chunk) {
+  inflate.write(chunk);
+});
+deflate.on('end', function() {
+  inflate.end();
+});
+
+// Get data from inflate stream
+var output = [];
+inflate.on('data', function(chunk) {
+  output.push(chunk);
+});
+inflate.on('end', function() {
+  assert.equal(output.join(''), input);
+});
+
+deflate.write(input);
+deflate.end();