From b165c9c9454223e91b5d5f746bd5806024159314 Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Sun, 13 Apr 2014 20:45:13 +0400 Subject: [PATCH] Deflate: added custom gzip headers support --- lib/deflate.js | 12 +++ lib/zlib/deflate.js | 169 +++++++++++++++++++++++++++++++++++++++--- test/gzip_specials.js | 39 +++++++++- 3 files changed, 207 insertions(+), 13 deletions(-) diff --git a/lib/deflate.js b/lib/deflate.js index b5364fc..ffdd753 100644 --- a/lib/deflate.js +++ b/lib/deflate.js @@ -86,6 +86,14 @@ var Z_DEFLATED = 8; * - `gzip` (Boolean) - create gzip wrapper * - `to` (String) - if equal to 'string', then result will be "binary string" * (each char code [0..255]) + * - `header` (Object) - custom header for gzip + * - `text` (Boolean) - true if compressed data believed to be text + * - `time` (Number) - modification time, unix timestamp + * - `os` (Number) - operation system code + * - `extra` (Array) - array of bytes with extra data (max 65536) + * - `name` (String) - file name (binary string) + * - `comment` (String) - comment (binary string) + * - `hcrc` (Boolean) - true if header crc should be added * * ##### Example: * @@ -146,6 +154,10 @@ var Deflate = function(options) { if (status !== Z_OK) { throw new Error(msg[status]); } + + if (opt.header) { + zlib_deflate.deflateSetHeader(this.strm, opt.header); + } }; /** diff --git a/lib/zlib/deflate.js b/lib/zlib/deflate.js index 0a6dfb0..9761d0d 100644 --- a/lib/zlib/deflate.js +++ b/lib/zlib/deflate.js @@ -1179,13 +1179,6 @@ function DeflateState() { zero(this.dyn_dtree); zero(this.bl_tree); -// struct tree_desc_s l_desc; /* desc. for literal tree */ -// struct tree_desc_s d_desc; /* desc. for distance tree */ -// struct tree_desc_s bl_desc; /* desc. for bit length tree */ - -// Seems to init better from `tree` with direct structures, -// (?) with separate constructor for bl_desc or not? -// Make sure objects have the same hidden class if needed this.l_desc = null; /* desc. for literal tree */ this.d_desc = null; /* desc. for distance tree */ this.bl_desc = null; /* desc. for bit length tree */ @@ -1264,6 +1257,7 @@ function DeflateState() { */ } + function deflateResetKeep(strm) { var s; @@ -1292,6 +1286,7 @@ function deflateResetKeep(strm) { return Z_OK; } + function deflateReset(strm) { var ret = deflateResetKeep(strm); if (ret === Z_OK) { @@ -1300,6 +1295,15 @@ function deflateReset(strm) { return ret; } + +function deflateSetHeader(strm, head) { + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; } + strm.state.gzhead = head; + return Z_OK; +} + + function deflateInit2(strm, level, method, windowBits, memLevel, strategy) { if (!strm) { // === Z_NULL return err(strm, Z_STREAM_ERROR); @@ -1378,6 +1382,7 @@ function deflateInit(strm, level) { function deflate(strm, flush) { var old_flush, s; + var beg, val; // for gzip header write only if (!strm || !strm.state || flush > Z_BLOCK || flush < 0) { @@ -1417,7 +1422,29 @@ function deflate(strm, flush) { s.status = BUSY_STATE; } else { - throw new Error('Custom GZIP headers not supported'); + put_byte(s, (s.gzhead.text ? 1 : 0) + + (s.gzhead.hcrc ? 2 : 0) + + (!s.gzhead.extra ? 0 : 4) + + (!s.gzhead.name ? 0 : 8) + + (!s.gzhead.comment ? 0 : 16) + ); + put_byte(s, s.gzhead.time & 0xff); + put_byte(s, (s.gzhead.time >> 8) & 0xff); + put_byte(s, (s.gzhead.time >> 16) & 0xff); + put_byte(s, (s.gzhead.time >> 24) & 0xff); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, s.gzhead.os & 0xff); + if (s.gzhead.extra && s.gzhead.extra.length) { + put_byte(s, s.gzhead.extra.length & 0xff); + put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); + } + if (s.gzhead.hcrc) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0); + } + s.gzindex = 0; + s.status = EXTRA_STATE; } } else // DEFLATE header @@ -1450,6 +1477,130 @@ function deflate(strm, flush) { } } +//#ifdef GZIP + if (s.status === EXTRA_STATE) { + if (s.gzhead.extra/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + + while (s.gzindex < (s.gzhead.extra.length & 0xffff)) { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + break; + } + } + put_byte(s, s.gzhead.extra[s.gzindex] & 0xff); + s.gzindex++; + } + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (s.gzindex === s.gzhead.extra.length) { + s.gzindex = 0; + s.status = NAME_STATE; + } + } + else { + s.status = NAME_STATE; + } + } + if (s.status === NAME_STATE) { + if (s.gzhead.name/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.name.length) { + val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg){ + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.gzindex = 0; + s.status = COMMENT_STATE; + } + } + else { + s.status = COMMENT_STATE; + } + } + if (s.status === COMMENT_STATE) { + if (s.gzhead.comment/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.comment.length) { + val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.status = HCRC_STATE; + } + } + else { + s.status = HCRC_STATE; + } + } + if (s.status === HCRC_STATE) { + if (s.gzhead.hcrc) { + if (s.pending + 2 > s.pending_buf_size) { + flush_pending(strm); + } + if (s.pending + 2 <= s.pending_buf_size) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + strm.adler = 0; //crc32(0L, Z_NULL, 0); + s.status = BUSY_STATE; + } + } + else { + s.status = BUSY_STATE; + } + } +//#endif + /* Flush as much pending output as possible */ if (s.pending !== 0) { flush_pending(strm); @@ -1597,6 +1748,7 @@ function deflateEnd(strm) { exports.deflateInit = deflateInit; exports.deflateInit2 = deflateInit2; exports.deflateReset = deflateReset; +exports.deflateSetHeader = deflateSetHeader; exports.deflate = deflate; exports.deflateEnd = deflateEnd; exports.deflateInfo = 'pako deflate (from Nodeca project)'; @@ -1604,7 +1756,6 @@ exports.deflateInfo = 'pako deflate (from Nodeca project)'; /* Not implemented exports.deflateSetDictionary = deflateSetDictionary; exports.deflateParams = deflateParams; -exports.deflateSetHeader = deflateSetHeader; exports.deflateBound = deflateBound; exports.deflatePending = deflatePending; */ \ No newline at end of file diff --git a/test/gzip_specials.js b/test/gzip_specials.js index 415624b..1c116bc 100644 --- a/test/gzip_specials.js +++ b/test/gzip_specials.js @@ -4,12 +4,13 @@ 'use strict'; -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); var pako_utils = require('../lib/utils/common'); -var pako = require('../index'); +var pako = require('../index'); +var cmp = require('./helpers').cmpBuf; function a2s(array) { @@ -29,6 +30,36 @@ describe('Gzip special cases', function() { assert.equal(a2s(inflator.header.extra), 'test extra'); }); + it('Write custom headers', function() { + var data = ' '; + + var deflator = new pako.Deflate({ + gzip: true, + header: { + hcrc: true, + time: 1234567, + os: 15, + name: 'test name', + comment: 'test comment', + extra: [4,5,6] + } + }); + deflator.push(data, true); + + var inflator = new pako.Inflate({ to: 'string' }); + inflator.push(deflator.result, true); + + assert.equal(inflator.err, 0); + assert.equal(inflator.result, data); + + var header = inflator.header; + assert.equal(header.time, 1234567); + assert.equal(header.os, 15); + assert.equal(header.name, 'test name'); + assert.equal(header.comment, 'test comment'); + assert(cmp(header.extra, [4,5,6])); + }); + it('Read stream with SYNC marks', function() { var inflator, strm, _in, len, pos = 0, i = 0; var data = fs.readFileSync(path.join(__dirname, 'fixtures/gzip-joined.gz'));