Inital Commit
[oweals/finalsclub.git] / bruml / node_modules / socket.io / lib / transports / websocket.js
1
2 /*!
3  * socket.io-node
4  * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
5  * MIT Licensed
6  */
7
8 /**
9  * Module requirements.
10  */
11
12 var Transport = require('../transport')
13   , EventEmitter = process.EventEmitter
14   , crypto = require('crypto')
15   , parser = require('../parser');
16
17 /**
18  * Export the constructor.
19  */
20
21 exports = module.exports = WebSocket;
22
23 /**
24  * HTTP interface constructor. Interface compatible with all transports that
25  * depend on request-response cycles.
26  *
27  * @api public
28  */
29
30 function WebSocket (mng, data, req) {
31   // parser
32   var self = this;
33
34   this.parser = new Parser();
35   this.parser.on('data', function (packet) {
36     self.log.debug(self.name + ' received data packet', packet);
37     self.onMessage(parser.decodePacket(packet));
38   });
39   this.parser.on('close', function () {
40     self.end();
41   });
42   this.parser.on('error', function () {
43     self.end();
44   });
45
46   Transport.call(this, mng, data, req);
47 };
48
49 /**
50  * Inherits from Transport.
51  */
52
53 WebSocket.prototype.__proto__ = Transport.prototype;
54
55 /**
56  * Transport name
57  *
58  * @api public
59  */
60
61 WebSocket.prototype.name = 'websocket';
62
63 /**
64  * Called when the socket connects.
65  *
66  * @api private
67  */
68
69 WebSocket.prototype.onSocketConnect = function () {
70   var self = this;
71
72   this.socket.setNoDelay(true);
73
74   this.buffer = true;
75   this.buffered = [];
76
77   if (this.req.headers.upgrade !== 'WebSocket') {
78     this.log.warn(this.name + ' connection invalid');
79     this.end();
80     return;
81   }
82
83   var origin = this.req.headers.origin
84     , location = (this.socket.encrypted ? 'wss' : 'ws')
85                + '://' + this.req.headers.host + this.req.url
86     , waitingForNonce = false;
87
88   if (this.req.headers['sec-websocket-key1']) {
89     // If we don't have the nonce yet, wait for it (HAProxy compatibility).
90     if (! (this.req.head && this.req.head.length >= 8)) {
91       waitingForNonce = true;
92     }
93
94     var headers = [
95         'HTTP/1.1 101 WebSocket Protocol Handshake'
96       , 'Upgrade: WebSocket'
97       , 'Connection: Upgrade'
98       , 'Sec-WebSocket-Origin: ' + origin
99       , 'Sec-WebSocket-Location: ' + location
100     ];
101
102     if (this.req.headers['sec-websocket-protocol']){
103       headers.push('Sec-WebSocket-Protocol: '
104           + this.req.headers['sec-websocket-protocol']);
105     }
106   } else {
107     var headers = [
108         'HTTP/1.1 101 Web Socket Protocol Handshake'
109       , 'Upgrade: WebSocket'
110       , 'Connection: Upgrade'
111       , 'WebSocket-Origin: ' + origin
112       , 'WebSocket-Location: ' + location
113     ];
114   }
115
116   try {
117     this.socket.write(headers.concat('', '').join('\r\n'));
118     this.socket.setTimeout(0);
119     this.socket.setNoDelay(true);
120     this.socket.setEncoding('utf8');
121   } catch (e) {
122     this.end();
123     return;
124   }
125
126   if (waitingForNonce) {
127     this.socket.setEncoding('binary');
128   } else if (this.proveReception(headers)) {
129     self.flush();
130   }
131
132   var headBuffer = '';
133
134   this.socket.on('data', function (data) {
135     if (waitingForNonce) {
136       headBuffer += data;
137
138       if (headBuffer.length < 8) {
139         return;
140       }
141
142       // Restore the connection to utf8 encoding after receiving the nonce
143       self.socket.setEncoding('utf8');
144       waitingForNonce = false;
145
146       // Stuff the nonce into the location where it's expected to be
147       self.req.head = headBuffer.substr(0, 8);
148       headBuffer = '';
149
150       if (self.proveReception(headers)) {
151         self.flush();
152       }
153
154       return;
155     }
156
157     self.parser.add(data);
158   });
159 };
160
161 /**
162  * Writes to the socket.
163  *
164  * @api private
165  */
166
167 WebSocket.prototype.write = function (data) {
168   if (this.open) {
169     this.drained = false;
170
171     if (this.buffer) {
172       this.buffered.push(data);
173       return this;
174     }
175
176     var length = Buffer.byteLength(data)
177       , buffer = new Buffer(2 + length);
178
179     buffer.write('\u0000', 'binary');
180     buffer.write(data, 1, 'utf8');
181     buffer.write('\uffff', 1 + length, 'binary');
182
183     try {
184       if (this.socket.write(buffer)) {
185         this.drained = true;
186       }
187     } catch (e) {
188       this.end();
189     }
190
191     this.log.debug(this.name + ' writing', data);
192   }
193 };
194
195 /**
196  * Flushes the internal buffer
197  *
198  * @api private
199  */
200
201 WebSocket.prototype.flush = function () {
202   this.buffer = false;
203
204   for (var i = 0, l = this.buffered.length; i < l; i++) {
205     this.write(this.buffered.splice(0, 1)[0]);
206   }
207 };
208
209 /**
210  * Finishes the handshake.
211  *
212  * @api private
213  */
214
215 WebSocket.prototype.proveReception = function (headers) {
216   var self = this
217     , k1 = this.req.headers['sec-websocket-key1']
218     , k2 = this.req.headers['sec-websocket-key2'];
219
220   if (k1 && k2){
221     var md5 = crypto.createHash('md5');
222
223     [k1, k2].forEach(function (k) {
224       var n = parseInt(k.replace(/[^\d]/g, ''))
225         , spaces = k.replace(/[^ ]/g, '').length;
226
227       if (spaces === 0 || n % spaces !== 0){
228         self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
229         self.end();
230         return false;
231       }
232
233       n /= spaces;
234
235       md5.update(String.fromCharCode(
236         n >> 24 & 0xFF,
237         n >> 16 & 0xFF,
238         n >> 8  & 0xFF,
239         n       & 0xFF));
240     });
241
242     md5.update(this.req.head.toString('binary'));
243
244     try {
245       this.socket.write(md5.digest('binary'), 'binary');
246     } catch (e) {
247       this.end();
248     }
249   }
250
251   return true;
252 };
253
254 /**
255  * Writes a payload.
256  *
257  * @api private
258  */
259
260 WebSocket.prototype.payload = function (msgs) {
261   for (var i = 0, l = msgs.length; i < l; i++) {
262     this.write(msgs[i]);
263   }
264
265   return this;
266 };
267
268 /**
269  * Closes the connection.
270  *
271  * @api private
272  */
273
274 WebSocket.prototype.doClose = function () {
275   this.socket.end();
276 };
277
278 /**
279  * WebSocket parser
280  *
281  * @api public
282  */
283
284 function Parser () {
285   this.buffer = '';
286   this.i = 0;
287 };
288
289 /**
290  * Inherits from EventEmitter.
291  */
292
293 Parser.prototype.__proto__ = EventEmitter.prototype;
294
295 /**
296  * Adds data to the buffer.
297  *
298  * @api public
299  */
300
301 Parser.prototype.add = function (data) {
302   this.buffer += data;
303   this.parse();
304 };
305
306 /**
307  * Parses the buffer.
308  *
309  * @api private
310  */
311
312 Parser.prototype.parse = function () {
313   for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
314     chr = this.buffer[i];
315
316     if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
317       this.emit('close');
318       this.buffer = '';
319       this.i = 0;
320       return;
321     }
322
323     if (i === 0){
324       if (chr != '\u0000')
325         this.error('Bad framing. Expected null byte as first frame');
326       else
327         continue;
328     }
329
330     if (chr == '\ufffd'){
331       this.emit('data', this.buffer.substr(1, i - 1));
332       this.buffer = this.buffer.substr(i + 1);
333       this.i = 0;
334       return this.parse();
335     }
336   }
337 };
338
339 /**
340  * Handles an error
341  *
342  * @api private
343  */
344
345 Parser.prototype.error = function (reason) {
346   this.buffer = '';
347   this.i = 0;
348   this.emit('error', reason);
349   return this;
350 };