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.

401 lines
11 KiB

4 years ago
  1. 'use strict';
  2. const EventEmitter = require('events');
  3. const crypto = require('crypto');
  4. const http = require('http');
  5. const PerMessageDeflate = require('./permessage-deflate');
  6. const extension = require('./extension');
  7. const WebSocket = require('./websocket');
  8. const { GUID } = require('./constants');
  9. const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
  10. /**
  11. * Class representing a WebSocket server.
  12. *
  13. * @extends EventEmitter
  14. */
  15. class WebSocketServer extends EventEmitter {
  16. /**
  17. * Create a `WebSocketServer` instance.
  18. *
  19. * @param {Object} options Configuration options
  20. * @param {Number} options.backlog The maximum length of the queue of pending
  21. * connections
  22. * @param {Boolean} options.clientTracking Specifies whether or not to track
  23. * clients
  24. * @param {Function} options.handleProtocols An hook to handle protocols
  25. * @param {String} options.host The hostname where to bind the server
  26. * @param {Number} options.maxPayload The maximum allowed message size
  27. * @param {Boolean} options.noServer Enable no server mode
  28. * @param {String} options.path Accept only connections matching this path
  29. * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable
  30. * permessage-deflate
  31. * @param {Number} options.port The port where to bind the server
  32. * @param {http.Server} options.server A pre-created HTTP/S server to use
  33. * @param {Function} options.verifyClient An hook to reject connections
  34. * @param {Function} callback A listener for the `listening` event
  35. */
  36. constructor(options, callback) {
  37. super();
  38. options = Object.assign(
  39. {
  40. maxPayload: 100 * 1024 * 1024,
  41. perMessageDeflate: false,
  42. handleProtocols: null,
  43. clientTracking: true,
  44. verifyClient: null,
  45. noServer: false,
  46. backlog: null, // use default (511 as implemented in net.js)
  47. server: null,
  48. host: null,
  49. path: null,
  50. port: null
  51. },
  52. options
  53. );
  54. if (options.port == null && !options.server && !options.noServer) {
  55. throw new TypeError(
  56. 'One of the "port", "server", or "noServer" options must be specified'
  57. );
  58. }
  59. if (options.port != null) {
  60. this._server = http.createServer((req, res) => {
  61. const body = http.STATUS_CODES[426];
  62. res.writeHead(426, {
  63. 'Content-Length': body.length,
  64. 'Content-Type': 'text/plain'
  65. });
  66. res.end(body);
  67. });
  68. this._server.listen(
  69. options.port,
  70. options.host,
  71. options.backlog,
  72. callback
  73. );
  74. } else if (options.server) {
  75. this._server = options.server;
  76. }
  77. if (this._server) {
  78. this._removeListeners = addListeners(this._server, {
  79. listening: this.emit.bind(this, 'listening'),
  80. error: this.emit.bind(this, 'error'),
  81. upgrade: (req, socket, head) => {
  82. this.handleUpgrade(req, socket, head, (ws) => {
  83. this.emit('connection', ws, req);
  84. });
  85. }
  86. });
  87. }
  88. if (options.perMessageDeflate === true) options.perMessageDeflate = {};
  89. if (options.clientTracking) this.clients = new Set();
  90. this.options = options;
  91. }
  92. /**
  93. * Returns the bound address, the address family name, and port of the server
  94. * as reported by the operating system if listening on an IP socket.
  95. * If the server is listening on a pipe or UNIX domain socket, the name is
  96. * returned as a string.
  97. *
  98. * @return {(Object|String|null)} The address of the server
  99. * @public
  100. */
  101. address() {
  102. if (this.options.noServer) {
  103. throw new Error('The server is operating in "noServer" mode');
  104. }
  105. if (!this._server) return null;
  106. return this._server.address();
  107. }
  108. /**
  109. * Close the server.
  110. *
  111. * @param {Function} cb Callback
  112. * @public
  113. */
  114. close(cb) {
  115. if (cb) this.once('close', cb);
  116. //
  117. // Terminate all associated clients.
  118. //
  119. if (this.clients) {
  120. for (const client of this.clients) client.terminate();
  121. }
  122. const server = this._server;
  123. if (server) {
  124. this._removeListeners();
  125. this._removeListeners = this._server = null;
  126. //
  127. // Close the http server if it was internally created.
  128. //
  129. if (this.options.port != null) {
  130. server.close(() => this.emit('close'));
  131. return;
  132. }
  133. }
  134. process.nextTick(emitClose, this);
  135. }
  136. /**
  137. * See if a given request should be handled by this server instance.
  138. *
  139. * @param {http.IncomingMessage} req Request object to inspect
  140. * @return {Boolean} `true` if the request is valid, else `false`
  141. * @public
  142. */
  143. shouldHandle(req) {
  144. if (this.options.path) {
  145. const index = req.url.indexOf('?');
  146. const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
  147. if (pathname !== this.options.path) return false;
  148. }
  149. return true;
  150. }
  151. /**
  152. * Handle a HTTP Upgrade request.
  153. *
  154. * @param {http.IncomingMessage} req The request object
  155. * @param {net.Socket} socket The network socket between the server and client
  156. * @param {Buffer} head The first packet of the upgraded stream
  157. * @param {Function} cb Callback
  158. * @public
  159. */
  160. handleUpgrade(req, socket, head, cb) {
  161. socket.on('error', socketOnError);
  162. const key =
  163. req.headers['sec-websocket-key'] !== undefined
  164. ? req.headers['sec-websocket-key'].trim()
  165. : false;
  166. const version = +req.headers['sec-websocket-version'];
  167. const extensions = {};
  168. if (
  169. req.method !== 'GET' ||
  170. req.headers.upgrade.toLowerCase() !== 'websocket' ||
  171. !key ||
  172. !keyRegex.test(key) ||
  173. (version !== 8 && version !== 13) ||
  174. !this.shouldHandle(req)
  175. ) {
  176. return abortHandshake(socket, 400);
  177. }
  178. if (this.options.perMessageDeflate) {
  179. const perMessageDeflate = new PerMessageDeflate(
  180. this.options.perMessageDeflate,
  181. true,
  182. this.options.maxPayload
  183. );
  184. try {
  185. const offers = extension.parse(req.headers['sec-websocket-extensions']);
  186. if (offers[PerMessageDeflate.extensionName]) {
  187. perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
  188. extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
  189. }
  190. } catch (err) {
  191. return abortHandshake(socket, 400);
  192. }
  193. }
  194. //
  195. // Optionally call external client verification handler.
  196. //
  197. if (this.options.verifyClient) {
  198. const info = {
  199. origin:
  200. req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
  201. secure: !!(req.connection.authorized || req.connection.encrypted),
  202. req
  203. };
  204. if (this.options.verifyClient.length === 2) {
  205. this.options.verifyClient(info, (verified, code, message, headers) => {
  206. if (!verified) {
  207. return abortHandshake(socket, code || 401, message, headers);
  208. }
  209. this.completeUpgrade(key, extensions, req, socket, head, cb);
  210. });
  211. return;
  212. }
  213. if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
  214. }
  215. this.completeUpgrade(key, extensions, req, socket, head, cb);
  216. }
  217. /**
  218. * Upgrade the connection to WebSocket.
  219. *
  220. * @param {String} key The value of the `Sec-WebSocket-Key` header
  221. * @param {Object} extensions The accepted extensions
  222. * @param {http.IncomingMessage} req The request object
  223. * @param {net.Socket} socket The network socket between the server and client
  224. * @param {Buffer} head The first packet of the upgraded stream
  225. * @param {Function} cb Callback
  226. * @private
  227. */
  228. completeUpgrade(key, extensions, req, socket, head, cb) {
  229. //
  230. // Destroy the socket if the client has already sent a FIN packet.
  231. //
  232. if (!socket.readable || !socket.writable) return socket.destroy();
  233. const digest = crypto
  234. .createHash('sha1')
  235. .update(key + GUID)
  236. .digest('base64');
  237. const headers = [
  238. 'HTTP/1.1 101 Switching Protocols',
  239. 'Upgrade: websocket',
  240. 'Connection: Upgrade',
  241. `Sec-WebSocket-Accept: ${digest}`
  242. ];
  243. const ws = new WebSocket(null);
  244. var protocol = req.headers['sec-websocket-protocol'];
  245. if (protocol) {
  246. protocol = protocol.trim().split(/ *, */);
  247. //
  248. // Optionally call external protocol selection handler.
  249. //
  250. if (this.options.handleProtocols) {
  251. protocol = this.options.handleProtocols(protocol, req);
  252. } else {
  253. protocol = protocol[0];
  254. }
  255. if (protocol) {
  256. headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
  257. ws.protocol = protocol;
  258. }
  259. }
  260. if (extensions[PerMessageDeflate.extensionName]) {
  261. const params = extensions[PerMessageDeflate.extensionName].params;
  262. const value = extension.format({
  263. [PerMessageDeflate.extensionName]: [params]
  264. });
  265. headers.push(`Sec-WebSocket-Extensions: ${value}`);
  266. ws._extensions = extensions;
  267. }
  268. //
  269. // Allow external modification/inspection of handshake headers.
  270. //
  271. this.emit('headers', headers, req);
  272. socket.write(headers.concat('\r\n').join('\r\n'));
  273. socket.removeListener('error', socketOnError);
  274. ws.setSocket(socket, head, this.options.maxPayload);
  275. if (this.clients) {
  276. this.clients.add(ws);
  277. ws.on('close', () => this.clients.delete(ws));
  278. }
  279. cb(ws);
  280. }
  281. }
  282. module.exports = WebSocketServer;
  283. /**
  284. * Add event listeners on an `EventEmitter` using a map of <event, listener>
  285. * pairs.
  286. *
  287. * @param {EventEmitter} server The event emitter
  288. * @param {Object.<String, Function>} map The listeners to add
  289. * @return {Function} A function that will remove the added listeners when called
  290. * @private
  291. */
  292. function addListeners(server, map) {
  293. for (const event of Object.keys(map)) server.on(event, map[event]);
  294. return function removeListeners() {
  295. for (const event of Object.keys(map)) {
  296. server.removeListener(event, map[event]);
  297. }
  298. };
  299. }
  300. /**
  301. * Emit a `'close'` event on an `EventEmitter`.
  302. *
  303. * @param {EventEmitter} server The event emitter
  304. * @private
  305. */
  306. function emitClose(server) {
  307. server.emit('close');
  308. }
  309. /**
  310. * Handle premature socket errors.
  311. *
  312. * @private
  313. */
  314. function socketOnError() {
  315. this.destroy();
  316. }
  317. /**
  318. * Close the connection when preconditions are not fulfilled.
  319. *
  320. * @param {net.Socket} socket The socket of the upgrade request
  321. * @param {Number} code The HTTP response status code
  322. * @param {String} [message] The HTTP response body
  323. * @param {Object} [headers] Additional HTTP response headers
  324. * @private
  325. */
  326. function abortHandshake(socket, code, message, headers) {
  327. if (socket.writable) {
  328. message = message || http.STATUS_CODES[code];
  329. headers = Object.assign(
  330. {
  331. Connection: 'close',
  332. 'Content-type': 'text/html',
  333. 'Content-Length': Buffer.byteLength(message)
  334. },
  335. headers
  336. );
  337. socket.write(
  338. `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
  339. Object.keys(headers)
  340. .map((h) => `${h}: ${headers[h]}`)
  341. .join('\r\n') +
  342. '\r\n\r\n' +
  343. message
  344. );
  345. }
  346. socket.removeListener('error', socketOnError);
  347. socket.destroy();
  348. }