PerMessageDeflate.js (9432B)
1 2var zlib = require('zlib'); 3 4var AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; 5var DEFAULT_WINDOW_BITS = 15; 6var DEFAULT_MEM_LEVEL = 8; 7 8PerMessageDeflate.extensionName = 'permessage-deflate'; 9 10/** 11 * Per-message Compression Extensions implementation 12 */ 13 14function PerMessageDeflate(options, isServer,maxPayload) { 15 if (this instanceof PerMessageDeflate === false) { 16 throw new TypeError("Classes can't be function-called"); 17 } 18 19 this._options = options || {}; 20 this._isServer = !!isServer; 21 this._inflate = null; 22 this._deflate = null; 23 this.params = null; 24 this._maxPayload = maxPayload || 0; 25} 26 27/** 28 * Create extension parameters offer 29 * 30 * @api public 31 */ 32 33PerMessageDeflate.prototype.offer = function() { 34 var params = {}; 35 if (this._options.serverNoContextTakeover) { 36 params.server_no_context_takeover = true; 37 } 38 if (this._options.clientNoContextTakeover) { 39 params.client_no_context_takeover = true; 40 } 41 if (this._options.serverMaxWindowBits) { 42 params.server_max_window_bits = this._options.serverMaxWindowBits; 43 } 44 if (this._options.clientMaxWindowBits) { 45 params.client_max_window_bits = this._options.clientMaxWindowBits; 46 } else if (this._options.clientMaxWindowBits == null) { 47 params.client_max_window_bits = true; 48 } 49 return params; 50}; 51 52/** 53 * Accept extension offer 54 * 55 * @api public 56 */ 57 58PerMessageDeflate.prototype.accept = function(paramsList) { 59 paramsList = this.normalizeParams(paramsList); 60 61 var params; 62 if (this._isServer) { 63 params = this.acceptAsServer(paramsList); 64 } else { 65 params = this.acceptAsClient(paramsList); 66 } 67 68 this.params = params; 69 return params; 70}; 71 72/** 73 * Releases all resources used by the extension 74 * 75 * @api public 76 */ 77 78PerMessageDeflate.prototype.cleanup = function() { 79 if (this._inflate) { 80 if (this._inflate.writeInProgress) { 81 this._inflate.pendingClose = true; 82 } else { 83 if (this._inflate.close) this._inflate.close(); 84 this._inflate = null; 85 } 86 } 87 if (this._deflate) { 88 if (this._deflate.writeInProgress) { 89 this._deflate.pendingClose = true; 90 } else { 91 if (this._deflate.close) this._deflate.close(); 92 this._deflate = null; 93 } 94 } 95}; 96 97/** 98 * Accept extension offer from client 99 * 100 * @api private 101 */ 102 103PerMessageDeflate.prototype.acceptAsServer = function(paramsList) { 104 var accepted = {}; 105 var result = paramsList.some(function(params) { 106 accepted = {}; 107 if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { 108 return; 109 } 110 if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) { 111 return; 112 } 113 if (typeof this._options.serverMaxWindowBits === 'number' && 114 typeof params.server_max_window_bits === 'number' && 115 this._options.serverMaxWindowBits > params.server_max_window_bits) { 116 return; 117 } 118 if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) { 119 return; 120 } 121 122 if (this._options.serverNoContextTakeover || params.server_no_context_takeover) { 123 accepted.server_no_context_takeover = true; 124 } 125 if (this._options.clientNoContextTakeover) { 126 accepted.client_no_context_takeover = true; 127 } 128 if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) { 129 accepted.client_no_context_takeover = true; 130 } 131 if (typeof this._options.serverMaxWindowBits === 'number') { 132 accepted.server_max_window_bits = this._options.serverMaxWindowBits; 133 } else if (typeof params.server_max_window_bits === 'number') { 134 accepted.server_max_window_bits = params.server_max_window_bits; 135 } 136 if (typeof this._options.clientMaxWindowBits === 'number') { 137 accepted.client_max_window_bits = this._options.clientMaxWindowBits; 138 } else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') { 139 accepted.client_max_window_bits = params.client_max_window_bits; 140 } 141 return true; 142 }, this); 143 144 if (!result) { 145 throw new Error('Doesn\'t support the offered configuration'); 146 } 147 148 return accepted; 149}; 150 151/** 152 * Accept extension response from server 153 * 154 * @api privaye 155 */ 156 157PerMessageDeflate.prototype.acceptAsClient = function(paramsList) { 158 var params = paramsList[0]; 159 if (this._options.clientNoContextTakeover != null) { 160 if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { 161 throw new Error('Invalid value for "client_no_context_takeover"'); 162 } 163 } 164 if (this._options.clientMaxWindowBits != null) { 165 if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) { 166 throw new Error('Invalid value for "client_max_window_bits"'); 167 } 168 if (typeof this._options.clientMaxWindowBits === 'number' && 169 (!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) { 170 throw new Error('Invalid value for "client_max_window_bits"'); 171 } 172 } 173 return params; 174}; 175 176/** 177 * Normalize extensions parameters 178 * 179 * @api private 180 */ 181 182PerMessageDeflate.prototype.normalizeParams = function(paramsList) { 183 return paramsList.map(function(params) { 184 Object.keys(params).forEach(function(key) { 185 var value = params[key]; 186 if (value.length > 1) { 187 throw new Error('Multiple extension parameters for ' + key); 188 } 189 190 value = value[0]; 191 192 switch (key) { 193 case 'server_no_context_takeover': 194 case 'client_no_context_takeover': 195 if (value !== true) { 196 throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); 197 } 198 params[key] = true; 199 break; 200 case 'server_max_window_bits': 201 case 'client_max_window_bits': 202 if (typeof value === 'string') { 203 value = parseInt(value, 10); 204 if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { 205 throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); 206 } 207 } 208 if (!this._isServer && value === true) { 209 throw new Error('Missing extension parameter value for ' + key); 210 } 211 params[key] = value; 212 break; 213 default: 214 throw new Error('Not defined extension parameter (' + key + ')'); 215 } 216 }, this); 217 return params; 218 }, this); 219}; 220 221/** 222 * Decompress message 223 * 224 * @api public 225 */ 226 227PerMessageDeflate.prototype.decompress = function (data, fin, callback) { 228 var endpoint = this._isServer ? 'client' : 'server'; 229 230 if (!this._inflate) { 231 var maxWindowBits = this.params[endpoint + '_max_window_bits']; 232 this._inflate = zlib.createInflateRaw({ 233 windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS 234 }); 235 } 236 this._inflate.writeInProgress = true; 237 238 var self = this; 239 var buffers = []; 240 var cumulativeBufferLength=0; 241 242 this._inflate.on('error', onError).on('data', onData); 243 this._inflate.write(data); 244 if (fin) { 245 this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); 246 } 247 this._inflate.flush(function() { 248 cleanup(); 249 callback(null, Buffer.concat(buffers)); 250 }); 251 252 function onError(err) { 253 cleanup(); 254 callback(err); 255 } 256 257 function onData(data) { 258 if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ 259 cumulativeBufferLength+=data.length; 260 if(cumulativeBufferLength>self._maxPayload){ 261 buffers=[]; 262 cleanup(); 263 var err={type:1009}; 264 callback(err); 265 return; 266 } 267 } 268 buffers.push(data); 269 } 270 271 function cleanup() { 272 if (!self._inflate) return; 273 self._inflate.removeListener('error', onError); 274 self._inflate.removeListener('data', onData); 275 self._inflate.writeInProgress = false; 276 if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { 277 if (self._inflate.close) self._inflate.close(); 278 self._inflate = null; 279 } 280 } 281}; 282 283/** 284 * Compress message 285 * 286 * @api public 287 */ 288 289PerMessageDeflate.prototype.compress = function (data, fin, callback) { 290 var endpoint = this._isServer ? 'server' : 'client'; 291 292 if (!this._deflate) { 293 var maxWindowBits = this.params[endpoint + '_max_window_bits']; 294 this._deflate = zlib.createDeflateRaw({ 295 flush: zlib.Z_SYNC_FLUSH, 296 windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, 297 memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL 298 }); 299 } 300 this._deflate.writeInProgress = true; 301 302 var self = this; 303 var buffers = []; 304 305 this._deflate.on('error', onError).on('data', onData); 306 this._deflate.write(data); 307 this._deflate.flush(function() { 308 cleanup(); 309 var data = Buffer.concat(buffers); 310 if (fin) { 311 data = data.slice(0, data.length - 4); 312 } 313 callback(null, data); 314 }); 315 316 function onError(err) { 317 cleanup(); 318 callback(err); 319 } 320 321 function onData(data) { 322 buffers.push(data); 323 } 324 325 function cleanup() { 326 if (!self._deflate) return; 327 self._deflate.removeListener('error', onError); 328 self._deflate.removeListener('data', onData); 329 self._deflate.writeInProgress = false; 330 if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { 331 if (self._deflate.close) self._deflate.close(); 332 self._deflate = null; 333 } 334 } 335}; 336 337module.exports = PerMessageDeflate;