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.

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