Deflate: added custom gzip headers support

This commit is contained in:
Vitaly Puzrin 2014-04-13 20:45:13 +04:00
parent e4593facbb
commit b165c9c945
3 changed files with 207 additions and 13 deletions

View file

@ -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);
}
};
/**

View file

@ -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;
*/

View file

@ -10,6 +10,7 @@ var assert = require('assert');
var pako_utils = require('../lib/utils/common');
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'));