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.

324 lines
7.9 KiB

7 years ago
  1. /*!
  2. * ws: a node.js websocket client
  3. * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
  4. * MIT Licensed
  5. */
  6. var events = require('events')
  7. , util = require('util')
  8. , EventEmitter = events.EventEmitter
  9. , ErrorCodes = require('./ErrorCodes')
  10. , bufferUtil = require('./BufferUtil').BufferUtil
  11. , PerMessageDeflate = require('./PerMessageDeflate');
  12. /**
  13. * HyBi Sender implementation
  14. */
  15. function Sender(socket, extensions) {
  16. if (this instanceof Sender === false) {
  17. throw new TypeError("Classes can't be function-called");
  18. }
  19. events.EventEmitter.call(this);
  20. this._socket = socket;
  21. this.extensions = extensions || {};
  22. this.firstFragment = true;
  23. this.compress = false;
  24. this.messageHandlers = [];
  25. this.processing = false;
  26. }
  27. /**
  28. * Inherits from EventEmitter.
  29. */
  30. util.inherits(Sender, events.EventEmitter);
  31. /**
  32. * Sends a close instruction to the remote party.
  33. *
  34. * @api public
  35. */
  36. Sender.prototype.close = function(code, data, mask, cb) {
  37. if (typeof code !== 'undefined') {
  38. if (typeof code !== 'number' ||
  39. !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number');
  40. }
  41. code = code || 1000;
  42. var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0));
  43. writeUInt16BE.call(dataBuffer, code, 0);
  44. if (dataBuffer.length > 2) dataBuffer.write(data, 2);
  45. var self = this;
  46. this.messageHandlers.push(function(callback) {
  47. self.frameAndSend(0x8, dataBuffer, true, mask);
  48. callback();
  49. if (typeof cb == 'function') cb();
  50. });
  51. this.flush();
  52. };
  53. /**
  54. * Sends a ping message to the remote party.
  55. *
  56. * @api public
  57. */
  58. Sender.prototype.ping = function(data, options) {
  59. var mask = options && options.mask;
  60. var self = this;
  61. this.messageHandlers.push(function(callback) {
  62. self.frameAndSend(0x9, data || '', true, mask);
  63. callback();
  64. });
  65. this.flush();
  66. };
  67. /**
  68. * Sends a pong message to the remote party.
  69. *
  70. * @api public
  71. */
  72. Sender.prototype.pong = function(data, options) {
  73. var mask = options && options.mask;
  74. var self = this;
  75. this.messageHandlers.push(function(callback) {
  76. self.frameAndSend(0xa, data || '', true, mask);
  77. callback();
  78. });
  79. this.flush();
  80. };
  81. /**
  82. * Sends text or binary data to the remote party.
  83. *
  84. * @api public
  85. */
  86. Sender.prototype.send = function(data, options, cb) {
  87. var finalFragment = options && options.fin === false ? false : true;
  88. var mask = options && options.mask;
  89. var compress = options && options.compress;
  90. var opcode = options && options.binary ? 2 : 1;
  91. if (this.firstFragment === false) {
  92. opcode = 0;
  93. compress = false;
  94. } else {
  95. this.firstFragment = false;
  96. this.compress = compress;
  97. }
  98. if (finalFragment) this.firstFragment = true
  99. var compressFragment = this.compress;
  100. var self = this;
  101. this.messageHandlers.push(function(callback) {
  102. self.applyExtensions(data, finalFragment, compressFragment, function(err, data) {
  103. if (err) {
  104. if (typeof cb == 'function') cb(err);
  105. else self.emit('error', err);
  106. return;
  107. }
  108. self.frameAndSend(opcode, data, finalFragment, mask, compress, cb);
  109. callback();
  110. });
  111. });
  112. this.flush();
  113. };
  114. /**
  115. * Frames and sends a piece of data according to the HyBi WebSocket protocol.
  116. *
  117. * @api private
  118. */
  119. Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, compressed, cb) {
  120. var canModifyData = false;
  121. if (!data) {
  122. try {
  123. this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb);
  124. }
  125. catch (e) {
  126. if (typeof cb == 'function') cb(e);
  127. else this.emit('error', e);
  128. }
  129. return;
  130. }
  131. if (!Buffer.isBuffer(data)) {
  132. canModifyData = true;
  133. if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) {
  134. data = getArrayBuffer(data);
  135. } else {
  136. //
  137. // If people want to send a number, this would allocate the number in
  138. // bytes as memory size instead of storing the number as buffer value. So
  139. // we need to transform it to string in order to prevent possible
  140. // vulnerabilities / memory attacks.
  141. //
  142. if (typeof data === 'number') data = data.toString();
  143. data = new Buffer(data);
  144. }
  145. }
  146. var dataLength = data.length
  147. , dataOffset = maskData ? 6 : 2
  148. , secondByte = dataLength;
  149. if (dataLength >= 65536) {
  150. dataOffset += 8;
  151. secondByte = 127;
  152. }
  153. else if (dataLength > 125) {
  154. dataOffset += 2;
  155. secondByte = 126;
  156. }
  157. var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData);
  158. var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset;
  159. var outputBuffer = new Buffer(totalLength);
  160. outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode;
  161. if (compressed) outputBuffer[0] |= 0x40;
  162. switch (secondByte) {
  163. case 126:
  164. writeUInt16BE.call(outputBuffer, dataLength, 2);
  165. break;
  166. case 127:
  167. writeUInt32BE.call(outputBuffer, 0, 2);
  168. writeUInt32BE.call(outputBuffer, dataLength, 6);
  169. }
  170. if (maskData) {
  171. outputBuffer[1] = secondByte | 0x80;
  172. var mask = getRandomMask();
  173. outputBuffer[dataOffset - 4] = mask[0];
  174. outputBuffer[dataOffset - 3] = mask[1];
  175. outputBuffer[dataOffset - 2] = mask[2];
  176. outputBuffer[dataOffset - 1] = mask[3];
  177. if (mergeBuffers) {
  178. bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength);
  179. try {
  180. this._socket.write(outputBuffer, 'binary', cb);
  181. }
  182. catch (e) {
  183. if (typeof cb == 'function') cb(e);
  184. else this.emit('error', e);
  185. }
  186. }
  187. else {
  188. bufferUtil.mask(data, mask, data, 0, dataLength);
  189. try {
  190. this._socket.write(outputBuffer, 'binary');
  191. this._socket.write(data, 'binary', cb);
  192. }
  193. catch (e) {
  194. if (typeof cb == 'function') cb(e);
  195. else this.emit('error', e);
  196. }
  197. }
  198. }
  199. else {
  200. outputBuffer[1] = secondByte;
  201. if (mergeBuffers) {
  202. data.copy(outputBuffer, dataOffset);
  203. try {
  204. this._socket.write(outputBuffer, 'binary', cb);
  205. }
  206. catch (e) {
  207. if (typeof cb == 'function') cb(e);
  208. else this.emit('error', e);
  209. }
  210. }
  211. else {
  212. try {
  213. this._socket.write(outputBuffer, 'binary');
  214. this._socket.write(data, 'binary', cb);
  215. }
  216. catch (e) {
  217. if (typeof cb == 'function') cb(e);
  218. else this.emit('error', e);
  219. }
  220. }
  221. }
  222. };
  223. /**
  224. * Execute message handler buffers
  225. *
  226. * @api private
  227. */
  228. Sender.prototype.flush = function() {
  229. if (this.processing) return;
  230. var handler = this.messageHandlers.shift();
  231. if (!handler) return;
  232. this.processing = true;
  233. var self = this;
  234. handler(function() {
  235. self.processing = false;
  236. self.flush();
  237. });
  238. };
  239. /**
  240. * Apply extensions to message
  241. *
  242. * @api private
  243. */
  244. Sender.prototype.applyExtensions = function(data, fin, compress, callback) {
  245. if (compress && data) {
  246. if ((data.buffer || data) instanceof ArrayBuffer) {
  247. data = getArrayBuffer(data);
  248. }
  249. this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback);
  250. } else {
  251. callback(null, data);
  252. }
  253. };
  254. module.exports = Sender;
  255. function writeUInt16BE(value, offset) {
  256. this[offset] = (value & 0xff00)>>8;
  257. this[offset+1] = value & 0xff;
  258. }
  259. function writeUInt32BE(value, offset) {
  260. this[offset] = (value & 0xff000000)>>24;
  261. this[offset+1] = (value & 0xff0000)>>16;
  262. this[offset+2] = (value & 0xff00)>>8;
  263. this[offset+3] = value & 0xff;
  264. }
  265. function getArrayBuffer(data) {
  266. // data is either an ArrayBuffer or ArrayBufferView.
  267. var array = new Uint8Array(data.buffer || data)
  268. , l = data.byteLength || data.length
  269. , o = data.byteOffset || 0
  270. , buffer = new Buffer(l);
  271. for (var i = 0; i < l; ++i) {
  272. buffer[i] = array[o+i];
  273. }
  274. return buffer;
  275. }
  276. function getRandomMask() {
  277. return new Buffer([
  278. ~~(Math.random() * 255),
  279. ~~(Math.random() * 255),
  280. ~~(Math.random() * 255),
  281. ~~(Math.random() * 255)
  282. ]);
  283. }