PerMessageDeflate.js 8.8 KB

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