You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

337 lines
9.2 KiB

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