Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / ws / lib / WebSocketServer.js
1 /*!
2  * ws: a node.js websocket client
3  * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
4  * MIT Licensed
5  */
6
7 var util = require('util')
8   , events = require('events')
9   , http = require('http')
10   , crypto = require('crypto')
11   , Options = require('options')
12   , WebSocket = require('./WebSocket')
13   , tls = require('tls')
14   , url = require('url');
15
16 /**
17  * WebSocket Server implementation
18  */
19
20 function WebSocketServer(options, callback) {
21   options = new Options({
22     host: '0.0.0.0',
23     port: null,
24     server: null,
25     verifyClient: null,
26     handleProtocols: null,
27     path: null,
28     noServer: false,
29     disableHixie: false,
30     clientTracking: true
31   }).merge(options);
32
33   if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) {
34     throw new TypeError('`port` or a `server` must be provided');
35   }
36
37   var self = this;
38
39   if (options.isDefinedAndNonNull('port')) {
40     this._server = http.createServer(function (req, res) {
41       res.writeHead(200, {'Content-Type': 'text/plain'});
42       res.end('Not implemented');
43     });
44     this._server.listen(options.value.port, options.value.host, callback);
45     this._closeServer = function() { if (self._server) self._server.close(); };
46   }
47   else if (options.value.server) {
48     this._server = options.value.server;
49     if (options.value.path) {
50       // take note of the path, to avoid collisions when multiple websocket servers are
51       // listening on the same http server
52       if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) {
53         throw new Error('two instances of WebSocketServer cannot listen on the same http server path');
54       }
55       if (typeof this._server._webSocketPaths !== 'object') {
56         this._server._webSocketPaths = {};
57       }
58       this._server._webSocketPaths[options.value.path] = 1;
59     }
60   }
61   if (this._server) this._server.once('listening', function() { self.emit('listening'); });
62
63   if (typeof this._server != 'undefined') {
64     this._server.on('error', function(error) {
65       self.emit('error', error)
66     });
67     this._server.on('upgrade', function(req, socket, upgradeHead) {
68       //copy upgradeHead to avoid retention of large slab buffers used in node core
69       var head = new Buffer(upgradeHead.length);
70       upgradeHead.copy(head);
71
72       self.handleUpgrade(req, socket, head, function(client) {
73         self.emit('connection'+req.url, client);
74         self.emit('connection', client);
75       });
76     });
77   }
78
79   this.options = options.value;
80   this.path = options.value.path;
81   this.clients = [];
82 }
83
84 /**
85  * Inherits from EventEmitter.
86  */
87
88 util.inherits(WebSocketServer, events.EventEmitter);
89
90 /**
91  * Immediately shuts down the connection.
92  *
93  * @api public
94  */
95
96 WebSocketServer.prototype.close = function() {
97   // terminate all associated clients
98   var error = null;
99   try {
100     for (var i = 0, l = this.clients.length; i < l; ++i) {
101       this.clients[i].terminate();
102     }
103   }
104   catch (e) {
105     error = e;
106   }
107
108   // remove path descriptor, if any
109   if (this.path && this._server._webSocketPaths) {
110     delete this._server._webSocketPaths[this.path];
111     if (Object.keys(this._server._webSocketPaths).length == 0) {
112       delete this._server._webSocketPaths;
113     }
114   }
115
116   // close the http server if it was internally created
117   try {
118     if (typeof this._closeServer !== 'undefined') {
119       this._closeServer();
120     }
121   }
122   finally {
123     delete this._server;
124   }
125   if (error) throw error;
126 }
127
128 /**
129  * Handle a HTTP Upgrade request.
130  *
131  * @api public
132  */
133
134 WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) {
135   // check for wrong path
136   if (this.options.path) {
137     var u = url.parse(req.url);
138     if (u && u.pathname !== this.options.path) return;
139   }
140
141   if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') {
142     abortConnection(socket, 400, 'Bad Request');
143     return;
144   }
145
146   if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments);
147   else handleHybiUpgrade.apply(this, arguments);
148 }
149
150 module.exports = WebSocketServer;
151
152 /**
153  * Entirely private apis,
154  * which may or may not be bound to a sepcific WebSocket instance.
155  */
156
157 function handleHybiUpgrade(req, socket, upgradeHead, cb) {
158   // handle premature socket errors
159   var errorHandler = function() {
160     try { socket.destroy(); } catch (e) {}
161   }
162   socket.on('error', errorHandler);
163
164   // verify key presence
165   if (!req.headers['sec-websocket-key']) {
166     abortConnection(socket, 400, 'Bad Request');
167     return;
168   }
169
170   // verify version
171   var version = parseInt(req.headers['sec-websocket-version']);
172   if ([8, 13].indexOf(version) === -1) {
173     abortConnection(socket, 400, 'Bad Request');
174     return;
175   }
176
177   // verify protocol
178   var protocols = req.headers['sec-websocket-protocol'];
179
180   // verify client
181   var origin = version < 13 ?
182     req.headers['sec-websocket-origin'] :
183     req.headers['origin'];
184
185   // handler to call when the connection sequence completes
186   var self = this;
187   var completeHybiUpgrade2 = function(protocol) {
188
189     // calc key
190     var key = req.headers['sec-websocket-key'];
191     var shasum = crypto.createHash('sha1');
192     shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
193     key = shasum.digest('base64');
194
195     var headers = [
196         'HTTP/1.1 101 Switching Protocols'
197       , 'Upgrade: websocket'
198       , 'Connection: Upgrade'
199       , 'Sec-WebSocket-Accept: ' + key
200     ];
201
202     if (typeof protocol != 'undefined') {
203       headers.push('Sec-WebSocket-Protocol: ' + protocol);
204     }
205
206     // allows external modification/inspection of handshake headers
207     self.emit('headers', headers);
208
209     socket.setTimeout(0);
210     socket.setNoDelay(true);
211     try {
212       socket.write(headers.concat('', '').join('\r\n'));
213     }
214     catch (e) {
215       // if the upgrade write fails, shut the connection down hard
216       try { socket.destroy(); } catch (e) {}
217       return;
218     }
219
220     var client = new WebSocket([req, socket, upgradeHead], {
221       protocolVersion: version,
222       protocol: protocol
223     });
224
225     if (self.options.clientTracking) {
226       self.clients.push(client);
227       client.on('close', function() {
228         var index = self.clients.indexOf(client);
229         if (index != -1) {
230           self.clients.splice(index, 1);
231         }
232       });
233     }
234
235     // signal upgrade complete
236     socket.removeListener('error', errorHandler);
237     cb(client);
238   }
239
240   // optionally call external protocol selection handler before
241   // calling completeHybiUpgrade2
242   var completeHybiUpgrade1 = function() {
243     // choose from the sub-protocols
244     if (typeof self.options.handleProtocols == 'function') {
245         var protList = (protocols || "").split(/, */);
246         var callbackCalled = false;
247         var res = self.options.handleProtocols(protList, function(result, protocol) {
248           callbackCalled = true;
249           if (!result) abortConnection(socket, 404, 'Unauthorized')
250           else completeHybiUpgrade2(protocol);
251         });
252         if (!callbackCalled) {
253             // the handleProtocols handler never called our callback
254             abortConnection(socket, 501, 'Could not process protocols');
255         }
256         return;
257     } else {
258         if (typeof protocols !== 'undefined') {
259             completeHybiUpgrade2(protocols.split(/, */)[0]);
260         }
261         else {
262             completeHybiUpgrade2();
263         }
264     }
265   }
266
267   // optionally call external client verification handler
268   if (typeof this.options.verifyClient == 'function') {
269     var info = {
270       origin: origin,
271       secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
272       req: req
273     };
274     if (this.options.verifyClient.length == 2) {
275       this.options.verifyClient(info, function(result, code, name) {
276         if (typeof code === 'undefined') code = 401;
277         if (typeof name === 'undefined') name = http.STATUS_CODES[code];
278
279         if (!result) abortConnection(socket, code, name);
280         else completeHybiUpgrade1();
281       });
282       return;
283     }
284     else if (!this.options.verifyClient(info)) {
285       abortConnection(socket, 401, 'Unauthorized');
286       return;
287     }
288   }
289
290   completeHybiUpgrade1();
291 }
292
293 function handleHixieUpgrade(req, socket, upgradeHead, cb) {
294   // handle premature socket errors
295   var errorHandler = function() {
296     try { socket.destroy(); } catch (e) {}
297   }
298   socket.on('error', errorHandler);
299
300   // bail if options prevent hixie
301   if (this.options.disableHixie) {
302     abortConnection(socket, 401, 'Hixie support disabled');
303     return;
304   }
305
306   // verify key presence
307   if (!req.headers['sec-websocket-key2']) {
308     abortConnection(socket, 400, 'Bad Request');
309     return;
310   }
311
312   var origin = req.headers['origin']
313     , self = this;
314
315   // setup handshake completion to run after client has been verified
316   var onClientVerified = function() {
317     var wshost;
318     if (!req.headers['x-forwarded-host'])
319         wshost = req.headers.host;
320     else
321         wshost = req.headers['x-forwarded-host'];
322     var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url
323       , protocol = req.headers['sec-websocket-protocol'];
324
325     // handshake completion code to run once nonce has been successfully retrieved
326     var completeHandshake = function(nonce, rest) {
327       // calculate key
328       var k1 = req.headers['sec-websocket-key1']
329         , k2 = req.headers['sec-websocket-key2']
330         , md5 = crypto.createHash('md5');
331
332       [k1, k2].forEach(function (k) {
333         var n = parseInt(k.replace(/[^\d]/g, ''))
334           , spaces = k.replace(/[^ ]/g, '').length;
335         if (spaces === 0 || n % spaces !== 0){
336           abortConnection(socket, 400, 'Bad Request');
337           return;
338         }
339         n /= spaces;
340         md5.update(String.fromCharCode(
341           n >> 24 & 0xFF,
342           n >> 16 & 0xFF,
343           n >> 8  & 0xFF,
344           n       & 0xFF));
345       });
346       md5.update(nonce.toString('binary'));
347
348       var headers = [
349           'HTTP/1.1 101 Switching Protocols'
350         , 'Upgrade: WebSocket'
351         , 'Connection: Upgrade'
352         , 'Sec-WebSocket-Location: ' + location
353       ];
354       if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol);
355       if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin);
356
357       socket.setTimeout(0);
358       socket.setNoDelay(true);
359       try {
360         // merge header and hash buffer
361         var headerBuffer = new Buffer(headers.concat('', '').join('\r\n'));
362         var hashBuffer = new Buffer(md5.digest('binary'), 'binary');
363         var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length);
364         headerBuffer.copy(handshakeBuffer, 0);
365         hashBuffer.copy(handshakeBuffer, headerBuffer.length);
366
367         // do a single write, which - upon success - causes a new client websocket to be setup
368         socket.write(handshakeBuffer, 'binary', function(err) {
369           if (err) return; // do not create client if an error happens
370           var client = new WebSocket([req, socket, rest], {
371             protocolVersion: 'hixie-76',
372             protocol: protocol
373           });
374           if (self.options.clientTracking) {
375             self.clients.push(client);
376             client.on('close', function() {
377               var index = self.clients.indexOf(client);
378               if (index != -1) {
379                 self.clients.splice(index, 1);
380               }
381             });
382           }
383
384           // signal upgrade complete
385           socket.removeListener('error', errorHandler);
386           cb(client);
387         });
388       }
389       catch (e) {
390         try { socket.destroy(); } catch (e) {}
391         return;
392       }
393     }
394
395     // retrieve nonce
396     var nonceLength = 8;
397     if (upgradeHead && upgradeHead.length >= nonceLength) {
398       var nonce = upgradeHead.slice(0, nonceLength);
399       var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null;
400       completeHandshake.call(self, nonce, rest);
401     }
402     else {
403       // nonce not present in upgradeHead, so we must wait for enough data
404       // data to arrive before continuing
405       var nonce = new Buffer(nonceLength);
406       upgradeHead.copy(nonce, 0);
407       var received = upgradeHead.length;
408       var rest = null;
409       var handler = function (data) {
410         var toRead = Math.min(data.length, nonceLength - received);
411         if (toRead === 0) return;
412         data.copy(nonce, received, 0, toRead);
413         received += toRead;
414         if (received == nonceLength) {
415           socket.removeListener('data', handler);
416           if (toRead < data.length) rest = data.slice(toRead);
417           completeHandshake.call(self, nonce, rest);
418         }
419       }
420       socket.on('data', handler);
421     }
422   }
423
424   // verify client
425   if (typeof this.options.verifyClient == 'function') {
426     var info = {
427       origin: origin,
428       secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
429       req: req
430     };
431     if (this.options.verifyClient.length == 2) {
432       var self = this;
433       this.options.verifyClient(info, function(result, code, name) {
434         if (typeof code === 'undefined') code = 401;
435         if (typeof name === 'undefined') name = http.STATUS_CODES[code];
436
437         if (!result) abortConnection(socket, code, name);
438         else onClientVerified.apply(self);
439       });
440       return;
441     }
442     else if (!this.options.verifyClient(info)) {
443       abortConnection(socket, 401, 'Unauthorized');
444       return;
445     }
446   }
447
448   // no client verification required
449   onClientVerified();
450 }
451
452 function abortConnection(socket, code, name) {
453   try {
454     var response = [
455       'HTTP/1.1 ' + code + ' ' + name,
456       'Content-type: text/html'
457     ];
458     socket.write(response.concat('', '').join('\r\n'));
459   }
460   catch (e) { /* ignore errors - we've aborted this connection */ }
461   finally {
462     // ensure that an early aborted connection is shut down completely
463     try { socket.destroy(); } catch (e) {}
464   }
465 }