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}