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 * - `gzip` (Boolean) - create gzip wrapper
* - `to` (String) - if equal to 'string', then result will be "binary string" * - `to` (String) - if equal to 'string', then result will be "binary string"
* (each char code [0..255]) * (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: * ##### Example:
* *
@ -146,6 +154,10 @@ var Deflate = function(options) {
if (status !== Z_OK) { if (status !== Z_OK) {
throw new Error(msg[status]); 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.dyn_dtree);
zero(this.bl_tree); 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.l_desc = null; /* desc. for literal tree */
this.d_desc = null; /* desc. for distance tree */ this.d_desc = null; /* desc. for distance tree */
this.bl_desc = null; /* desc. for bit length tree */ this.bl_desc = null; /* desc. for bit length tree */
@ -1264,6 +1257,7 @@ function DeflateState() {
*/ */
} }
function deflateResetKeep(strm) { function deflateResetKeep(strm) {
var s; var s;
@ -1292,6 +1286,7 @@ function deflateResetKeep(strm) {
return Z_OK; return Z_OK;
} }
function deflateReset(strm) { function deflateReset(strm) {
var ret = deflateResetKeep(strm); var ret = deflateResetKeep(strm);
if (ret === Z_OK) { if (ret === Z_OK) {
@ -1300,6 +1295,15 @@ function deflateReset(strm) {
return ret; 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) { function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
if (!strm) { // === Z_NULL if (!strm) { // === Z_NULL
return err(strm, Z_STREAM_ERROR); return err(strm, Z_STREAM_ERROR);
@ -1378,6 +1382,7 @@ function deflateInit(strm, level) {
function deflate(strm, flush) { function deflate(strm, flush) {
var old_flush, s; var old_flush, s;
var beg, val; // for gzip header write only
if (!strm || !strm.state || if (!strm || !strm.state ||
flush > Z_BLOCK || flush < 0) { flush > Z_BLOCK || flush < 0) {
@ -1417,7 +1422,29 @@ function deflate(strm, flush) {
s.status = BUSY_STATE; s.status = BUSY_STATE;
} }
else { 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 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 */ /* Flush as much pending output as possible */
if (s.pending !== 0) { if (s.pending !== 0) {
flush_pending(strm); flush_pending(strm);
@ -1597,6 +1748,7 @@ function deflateEnd(strm) {
exports.deflateInit = deflateInit; exports.deflateInit = deflateInit;
exports.deflateInit2 = deflateInit2; exports.deflateInit2 = deflateInit2;
exports.deflateReset = deflateReset; exports.deflateReset = deflateReset;
exports.deflateSetHeader = deflateSetHeader;
exports.deflate = deflate; exports.deflate = deflate;
exports.deflateEnd = deflateEnd; exports.deflateEnd = deflateEnd;
exports.deflateInfo = 'pako deflate (from Nodeca project)'; exports.deflateInfo = 'pako deflate (from Nodeca project)';
@ -1604,7 +1756,6 @@ exports.deflateInfo = 'pako deflate (from Nodeca project)';
/* Not implemented /* Not implemented
exports.deflateSetDictionary = deflateSetDictionary; exports.deflateSetDictionary = deflateSetDictionary;
exports.deflateParams = deflateParams; exports.deflateParams = deflateParams;
exports.deflateSetHeader = deflateSetHeader;
exports.deflateBound = deflateBound; exports.deflateBound = deflateBound;
exports.deflatePending = deflatePending; exports.deflatePending = deflatePending;
*/ */

View file

@ -4,12 +4,13 @@
'use strict'; 'use strict';
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var assert = require('assert'); var assert = require('assert');
var pako_utils = require('../lib/utils/common'); var pako_utils = require('../lib/utils/common');
var pako = require('../index'); var pako = require('../index');
var cmp = require('./helpers').cmpBuf;
function a2s(array) { function a2s(array) {
@ -29,6 +30,36 @@ describe('Gzip special cases', function() {
assert.equal(a2s(inflator.header.extra), 'test extra'); 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() { it('Read stream with SYNC marks', function() {
var inflator, strm, _in, len, pos = 0, i = 0; var inflator, strm, _in, len, pos = 0, i = 0;
var data = fs.readFileSync(path.join(__dirname, 'fixtures/gzip-joined.gz')); var data = fs.readFileSync(path.join(__dirname, 'fixtures/gzip-joined.gz'));