From e609195202d79221edc037309c3ef459514e3ff4 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Fri, 2 Dec 2011 11:53:56 +0400 Subject: [PATCH] [zlib] added dictionary support --- doc/api/zlib.markdown | 1 + lib/zlib.js | 9 +++- src/node_zlib.cc | 67 ++++++++++++++++++++++++-- test/simple/test-zlib-dictionary.js | 73 +++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 test/simple/test-zlib-dictionary.js diff --git a/doc/api/zlib.markdown b/doc/api/zlib.markdown index 475723e88ba..6bd2c095dda 100644 --- a/doc/api/zlib.markdown +++ b/doc/api/zlib.markdown @@ -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 for more information on these. diff --git a/lib/zlib.js b/lib/zlib.js index 840927b770d..eccb9806fe6 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -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); diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 7c07ef3949e..3c9621aaaa3 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -64,6 +64,7 @@ template class ZCtx : public ObjectWrap { public: ZCtx() : ObjectWrap() { + dictionary_ = NULL; } ~ZCtx() { @@ -72,6 +73,8 @@ template 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 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 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 *ctx = ObjectWrap::Unwrap< ZCtx >(args.This()); @@ -227,7 +246,19 @@ template 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 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 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 class ZCtx : public ObjectWrap { assert(0 && "wtf?"); } - ctx->init_done_ = true; assert(err == Z_OK); + + ctx->dictionary_ = reinterpret_cast(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 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 index 00000000000..d16e415d101 --- /dev/null +++ b/test/simple/test-zlib-dictionary.js @@ -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();