1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23const common = require('../common'); 24const assert = require('assert'); 25const zlib = require('zlib'); 26const stream = require('stream'); 27const fs = require('fs'); 28const fixtures = require('../common/fixtures'); 29 30// Should not segfault. 31assert.throws(() => zlib.gzipSync(Buffer.alloc(0), { windowBits: 8 }), { 32 code: 'ERR_OUT_OF_RANGE', 33 name: 'RangeError', 34 message: 'The value of "options.windowBits" is out of range. ' + 35 'It must be >= 9 and <= 15. Received 8', 36}); 37 38let zlibPairs = [ 39 [zlib.Deflate, zlib.Inflate], 40 [zlib.Gzip, zlib.Gunzip], 41 [zlib.Deflate, zlib.Unzip], 42 [zlib.Gzip, zlib.Unzip], 43 [zlib.DeflateRaw, zlib.InflateRaw], 44 [zlib.BrotliCompress, zlib.BrotliDecompress], 45]; 46 47// How fast to trickle through the slowstream 48let trickle = [128, 1024, 1024 * 1024]; 49 50// Tunable options for zlib classes. 51 52// several different chunk sizes 53let chunkSize = [128, 1024, 1024 * 16, 1024 * 1024]; 54 55// This is every possible value. 56let level = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 57let windowBits = [8, 9, 10, 11, 12, 13, 14, 15]; 58let memLevel = [1, 2, 3, 4, 5, 6, 7, 8, 9]; 59let strategy = [0, 1, 2, 3, 4]; 60 61// It's nice in theory to test every combination, but it 62// takes WAY too long. Maybe a pummel test could do this? 63if (!process.env.PUMMEL) { 64 trickle = [1024]; 65 chunkSize = [1024 * 16]; 66 level = [6]; 67 memLevel = [8]; 68 windowBits = [15]; 69 strategy = [0]; 70} 71 72let testFiles = ['person.jpg', 'elipses.txt', 'empty.txt']; 73 74if (process.env.FAST) { 75 zlibPairs = [[zlib.Gzip, zlib.Unzip]]; 76 testFiles = ['person.jpg']; 77} 78 79const tests = {}; 80testFiles.forEach(common.mustCall((file) => { 81 tests[file] = fixtures.readSync(file); 82}, testFiles.length)); 83 84 85// Stream that saves everything 86class BufferStream extends stream.Stream { 87 constructor() { 88 super(); 89 this.chunks = []; 90 this.length = 0; 91 this.writable = true; 92 this.readable = true; 93 } 94 95 write(c) { 96 this.chunks.push(c); 97 this.length += c.length; 98 return true; 99 } 100 101 end(c) { 102 if (c) this.write(c); 103 // flatten 104 const buf = Buffer.allocUnsafe(this.length); 105 let i = 0; 106 this.chunks.forEach((c) => { 107 c.copy(buf, i); 108 i += c.length; 109 }); 110 this.emit('data', buf); 111 this.emit('end'); 112 return true; 113 } 114} 115 116class SlowStream extends stream.Stream { 117 constructor(trickle) { 118 super(); 119 this.trickle = trickle; 120 this.offset = 0; 121 this.readable = this.writable = true; 122 } 123 124 write() { 125 throw new Error('not implemented, just call ss.end(chunk)'); 126 } 127 128 pause() { 129 this.paused = true; 130 this.emit('pause'); 131 } 132 133 resume() { 134 const emit = () => { 135 if (this.paused) return; 136 if (this.offset >= this.length) { 137 this.ended = true; 138 return this.emit('end'); 139 } 140 const end = Math.min(this.offset + this.trickle, this.length); 141 const c = this.chunk.slice(this.offset, end); 142 this.offset += c.length; 143 this.emit('data', c); 144 process.nextTick(emit); 145 }; 146 147 if (this.ended) return; 148 this.emit('resume'); 149 if (!this.chunk) return; 150 this.paused = false; 151 emit(); 152 } 153 154 end(chunk) { 155 // Walk over the chunk in blocks. 156 this.chunk = chunk; 157 this.length = chunk.length; 158 this.resume(); 159 return this.ended; 160 } 161} 162 163// windowBits: 8 shouldn't throw 164zlib.createDeflateRaw({ windowBits: 8 }); 165 166{ 167 const node = fs.createReadStream(fixtures.path('person.jpg')); 168 const raw = []; 169 const reinflated = []; 170 node.on('data', (chunk) => raw.push(chunk)); 171 172 // Usually, the inflate windowBits parameter needs to be at least the 173 // value of the matching deflate’s windowBits. However, inflate raw with 174 // windowBits = 8 should be able to handle compressed data from a source 175 // that does not know about the silent 8-to-9 upgrade of windowBits 176 // that most versions of zlib/Node perform, and which *still* results in 177 // a valid 8-bit-window zlib stream. 178 node.pipe(zlib.createDeflateRaw({ windowBits: 9 })) 179 .pipe(zlib.createInflateRaw({ windowBits: 8 })) 180 .on('data', (chunk) => reinflated.push(chunk)) 181 .on('end', common.mustCall( 182 () => assert(Buffer.concat(raw).equals(Buffer.concat(reinflated))))) 183 .on('close', common.mustCall(1)); 184} 185 186// For each of the files, make sure that compressing and 187// decompressing results in the same data, for every combination 188// of the options set above. 189 190const testKeys = Object.keys(tests); 191testKeys.forEach(common.mustCall((file) => { 192 const test = tests[file]; 193 chunkSize.forEach(common.mustCall((chunkSize) => { 194 trickle.forEach(common.mustCall((trickle) => { 195 windowBits.forEach(common.mustCall((windowBits) => { 196 level.forEach(common.mustCall((level) => { 197 memLevel.forEach(common.mustCall((memLevel) => { 198 strategy.forEach(common.mustCall((strategy) => { 199 zlibPairs.forEach(common.mustCall((pair) => { 200 const Def = pair[0]; 201 const Inf = pair[1]; 202 const opts = { level, windowBits, memLevel, strategy }; 203 204 const def = new Def(opts); 205 const inf = new Inf(opts); 206 const ss = new SlowStream(trickle); 207 const buf = new BufferStream(); 208 209 // Verify that the same exact buffer comes out the other end. 210 buf.on('data', common.mustCall((c) => { 211 const msg = `${file} ${chunkSize} ${ 212 JSON.stringify(opts)} ${Def.name} -> ${Inf.name}`; 213 let i; 214 for (i = 0; i < Math.max(c.length, test.length); i++) { 215 if (c[i] !== test[i]) { 216 assert.fail(msg); 217 break; 218 } 219 } 220 })); 221 222 // The magic happens here. 223 ss.pipe(def).pipe(inf).pipe(buf); 224 ss.end(test); 225 }, zlibPairs.length)); 226 }, strategy.length)); 227 }, memLevel.length)); 228 }, level.length)); 229 }, windowBits.length)); 230 }, trickle.length)); 231 }, chunkSize.length)); 232}, testKeys.length)); 233