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