server.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Server = exports.BaseServer = void 0;
  4. const base64id = require("base64id");
  5. const transports_1 = require("./transports");
  6. const events_1 = require("events");
  7. const socket_1 = require("./socket");
  8. const debug_1 = require("debug");
  9. const cookie_1 = require("cookie");
  10. const ws_1 = require("ws");
  11. const webtransport_1 = require("./transports/webtransport");
  12. const engine_io_parser_1 = require("engine.io-parser");
  13. const debug = (0, debug_1.default)("engine");
  14. const kResponseHeaders = Symbol("responseHeaders");
  15. function parseSessionId(data) {
  16. try {
  17. const parsed = JSON.parse(data);
  18. if (typeof parsed.sid === "string") {
  19. return parsed.sid;
  20. }
  21. }
  22. catch (e) { }
  23. }
  24. class BaseServer extends events_1.EventEmitter {
  25. /**
  26. * Server constructor.
  27. *
  28. * @param {Object} opts - options
  29. */
  30. constructor(opts = {}) {
  31. super();
  32. this.middlewares = [];
  33. this.clients = {};
  34. this.clientsCount = 0;
  35. this.opts = Object.assign({
  36. wsEngine: ws_1.Server,
  37. pingTimeout: 20000,
  38. pingInterval: 25000,
  39. upgradeTimeout: 10000,
  40. maxHttpBufferSize: 1e6,
  41. transports: ["polling", "websocket"], // WebTransport is disabled by default
  42. allowUpgrades: true,
  43. httpCompression: {
  44. threshold: 1024,
  45. },
  46. cors: false,
  47. allowEIO3: false,
  48. }, opts);
  49. if (opts.cookie) {
  50. this.opts.cookie = Object.assign({
  51. name: "io",
  52. path: "/",
  53. // @ts-ignore
  54. httpOnly: opts.cookie.path !== false,
  55. sameSite: "lax",
  56. }, opts.cookie);
  57. }
  58. if (this.opts.cors) {
  59. this.use(require("cors")(this.opts.cors));
  60. }
  61. if (opts.perMessageDeflate) {
  62. this.opts.perMessageDeflate = Object.assign({
  63. threshold: 1024,
  64. }, opts.perMessageDeflate);
  65. }
  66. this.init();
  67. }
  68. /**
  69. * Compute the pathname of the requests that are handled by the server
  70. * @param options
  71. * @protected
  72. */
  73. _computePath(options) {
  74. let path = (options.path || "/engine.io").replace(/\/$/, "");
  75. if (options.addTrailingSlash !== false) {
  76. // normalize path
  77. path += "/";
  78. }
  79. return path;
  80. }
  81. /**
  82. * Returns a list of available transports for upgrade given a certain transport.
  83. */
  84. upgrades(transport) {
  85. if (!this.opts.allowUpgrades)
  86. return [];
  87. return transports_1.default[transport].upgradesTo || [];
  88. }
  89. /**
  90. * Verifies a request.
  91. *
  92. * @param {EngineRequest} req
  93. * @param upgrade - whether it's an upgrade request
  94. * @param fn
  95. * @protected
  96. * @return whether the request is valid
  97. */
  98. verify(req, upgrade, fn) {
  99. // transport check
  100. const transport = req._query.transport;
  101. // WebTransport does not go through the verify() method, see the onWebTransportSession() method
  102. if (!~this.opts.transports.indexOf(transport) ||
  103. transport === "webtransport") {
  104. debug('unknown transport "%s"', transport);
  105. return fn(Server.errors.UNKNOWN_TRANSPORT, { transport });
  106. }
  107. // 'Origin' header check
  108. const isOriginInvalid = checkInvalidHeaderChar(req.headers.origin);
  109. if (isOriginInvalid) {
  110. const origin = req.headers.origin;
  111. req.headers.origin = null;
  112. debug("origin header invalid");
  113. return fn(Server.errors.BAD_REQUEST, {
  114. name: "INVALID_ORIGIN",
  115. origin,
  116. });
  117. }
  118. // sid check
  119. const sid = req._query.sid;
  120. if (sid) {
  121. if (!this.clients.hasOwnProperty(sid)) {
  122. debug('unknown sid "%s"', sid);
  123. return fn(Server.errors.UNKNOWN_SID, {
  124. sid,
  125. });
  126. }
  127. const previousTransport = this.clients[sid].transport.name;
  128. if (!upgrade && previousTransport !== transport) {
  129. debug("bad request: unexpected transport without upgrade");
  130. return fn(Server.errors.BAD_REQUEST, {
  131. name: "TRANSPORT_MISMATCH",
  132. transport,
  133. previousTransport,
  134. });
  135. }
  136. }
  137. else {
  138. // handshake is GET only
  139. if ("GET" !== req.method) {
  140. return fn(Server.errors.BAD_HANDSHAKE_METHOD, {
  141. method: req.method,
  142. });
  143. }
  144. if (transport === "websocket" && !upgrade) {
  145. debug("invalid transport upgrade");
  146. return fn(Server.errors.BAD_REQUEST, {
  147. name: "TRANSPORT_HANDSHAKE_ERROR",
  148. });
  149. }
  150. if (!this.opts.allowRequest)
  151. return fn();
  152. return this.opts.allowRequest(req, (message, success) => {
  153. if (!success) {
  154. return fn(Server.errors.FORBIDDEN, {
  155. message,
  156. });
  157. }
  158. fn();
  159. });
  160. }
  161. fn();
  162. }
  163. /**
  164. * Adds a new middleware.
  165. *
  166. * @example
  167. * import helmet from "helmet";
  168. *
  169. * engine.use(helmet());
  170. *
  171. * @param fn
  172. */
  173. use(fn) {
  174. this.middlewares.push(fn);
  175. }
  176. /**
  177. * Apply the middlewares to the request.
  178. *
  179. * @param req
  180. * @param res
  181. * @param callback
  182. * @protected
  183. */
  184. _applyMiddlewares(req, res, callback) {
  185. if (this.middlewares.length === 0) {
  186. debug("no middleware to apply, skipping");
  187. return callback();
  188. }
  189. const apply = (i) => {
  190. debug("applying middleware n°%d", i + 1);
  191. this.middlewares[i](req, res, (err) => {
  192. if (err) {
  193. return callback(err);
  194. }
  195. if (i + 1 < this.middlewares.length) {
  196. apply(i + 1);
  197. }
  198. else {
  199. callback();
  200. }
  201. });
  202. };
  203. apply(0);
  204. }
  205. /**
  206. * Closes all clients.
  207. */
  208. close() {
  209. debug("closing all open clients");
  210. for (let i in this.clients) {
  211. if (this.clients.hasOwnProperty(i)) {
  212. this.clients[i].close(true);
  213. }
  214. }
  215. this.cleanup();
  216. return this;
  217. }
  218. /**
  219. * generate a socket id.
  220. * Overwrite this method to generate your custom socket id
  221. *
  222. * @param {IncomingMessage} req - the request object
  223. */
  224. generateId(req) {
  225. return base64id.generateId();
  226. }
  227. /**
  228. * Handshakes a new client.
  229. *
  230. * @param {String} transportName
  231. * @param {Object} req - the request object
  232. * @param {Function} closeConnection
  233. *
  234. * @protected
  235. */
  236. async handshake(transportName, req, closeConnection) {
  237. const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
  238. if (protocol === 3 && !this.opts.allowEIO3) {
  239. debug("unsupported protocol version");
  240. this.emit("connection_error", {
  241. req,
  242. code: Server.errors.UNSUPPORTED_PROTOCOL_VERSION,
  243. message: Server.errorMessages[Server.errors.UNSUPPORTED_PROTOCOL_VERSION],
  244. context: {
  245. protocol,
  246. },
  247. });
  248. closeConnection(Server.errors.UNSUPPORTED_PROTOCOL_VERSION);
  249. return;
  250. }
  251. let id;
  252. try {
  253. id = await this.generateId(req);
  254. }
  255. catch (e) {
  256. debug("error while generating an id");
  257. this.emit("connection_error", {
  258. req,
  259. code: Server.errors.BAD_REQUEST,
  260. message: Server.errorMessages[Server.errors.BAD_REQUEST],
  261. context: {
  262. name: "ID_GENERATION_ERROR",
  263. error: e,
  264. },
  265. });
  266. closeConnection(Server.errors.BAD_REQUEST);
  267. return;
  268. }
  269. debug('handshaking client "%s"', id);
  270. try {
  271. var transport = this.createTransport(transportName, req);
  272. if ("polling" === transportName) {
  273. transport.maxHttpBufferSize = this.opts.maxHttpBufferSize;
  274. transport.httpCompression = this.opts.httpCompression;
  275. }
  276. else if ("websocket" === transportName) {
  277. transport.perMessageDeflate = this.opts.perMessageDeflate;
  278. }
  279. }
  280. catch (e) {
  281. debug('error handshaking to transport "%s"', transportName);
  282. this.emit("connection_error", {
  283. req,
  284. code: Server.errors.BAD_REQUEST,
  285. message: Server.errorMessages[Server.errors.BAD_REQUEST],
  286. context: {
  287. name: "TRANSPORT_HANDSHAKE_ERROR",
  288. error: e,
  289. },
  290. });
  291. closeConnection(Server.errors.BAD_REQUEST);
  292. return;
  293. }
  294. const socket = new socket_1.Socket(id, this, transport, req, protocol);
  295. transport.on("headers", (headers, req) => {
  296. const isInitialRequest = !req._query.sid;
  297. if (isInitialRequest) {
  298. if (this.opts.cookie) {
  299. headers["Set-Cookie"] = [
  300. // @ts-ignore
  301. (0, cookie_1.serialize)(this.opts.cookie.name, id, this.opts.cookie),
  302. ];
  303. }
  304. this.emit("initial_headers", headers, req);
  305. }
  306. this.emit("headers", headers, req);
  307. });
  308. transport.onRequest(req);
  309. this.clients[id] = socket;
  310. this.clientsCount++;
  311. socket.once("close", () => {
  312. delete this.clients[id];
  313. this.clientsCount--;
  314. });
  315. this.emit("connection", socket);
  316. return transport;
  317. }
  318. async onWebTransportSession(session) {
  319. const timeout = setTimeout(() => {
  320. debug("the client failed to establish a bidirectional stream in the given period");
  321. session.close();
  322. }, this.opts.upgradeTimeout);
  323. const streamReader = session.incomingBidirectionalStreams.getReader();
  324. const result = await streamReader.read();
  325. if (result.done) {
  326. debug("session is closed");
  327. return;
  328. }
  329. const stream = result.value;
  330. const transformStream = (0, engine_io_parser_1.createPacketDecoderStream)(this.opts.maxHttpBufferSize, "nodebuffer");
  331. const reader = stream.readable.pipeThrough(transformStream).getReader();
  332. // reading the first packet of the stream
  333. const { value, done } = await reader.read();
  334. if (done) {
  335. debug("stream is closed");
  336. return;
  337. }
  338. clearTimeout(timeout);
  339. if (value.type !== "open") {
  340. debug("invalid WebTransport handshake");
  341. return session.close();
  342. }
  343. if (value.data === undefined) {
  344. const transport = new webtransport_1.WebTransport(session, stream, reader);
  345. // note: we cannot use "this.generateId()", because there is no "req" argument
  346. const id = base64id.generateId();
  347. debug('handshaking client "%s" (WebTransport)', id);
  348. const socket = new socket_1.Socket(id, this, transport, null, 4);
  349. this.clients[id] = socket;
  350. this.clientsCount++;
  351. socket.once("close", () => {
  352. delete this.clients[id];
  353. this.clientsCount--;
  354. });
  355. this.emit("connection", socket);
  356. return;
  357. }
  358. const sid = parseSessionId(value.data);
  359. if (!sid) {
  360. debug("invalid WebTransport handshake");
  361. return session.close();
  362. }
  363. const client = this.clients[sid];
  364. if (!client) {
  365. debug("upgrade attempt for closed client");
  366. session.close();
  367. }
  368. else if (client.upgrading) {
  369. debug("transport has already been trying to upgrade");
  370. session.close();
  371. }
  372. else if (client.upgraded) {
  373. debug("transport had already been upgraded");
  374. session.close();
  375. }
  376. else {
  377. debug("upgrading existing transport");
  378. const transport = new webtransport_1.WebTransport(session, stream, reader);
  379. client._maybeUpgrade(transport);
  380. }
  381. }
  382. }
  383. exports.BaseServer = BaseServer;
  384. /**
  385. * Protocol errors mappings.
  386. */
  387. BaseServer.errors = {
  388. UNKNOWN_TRANSPORT: 0,
  389. UNKNOWN_SID: 1,
  390. BAD_HANDSHAKE_METHOD: 2,
  391. BAD_REQUEST: 3,
  392. FORBIDDEN: 4,
  393. UNSUPPORTED_PROTOCOL_VERSION: 5,
  394. };
  395. BaseServer.errorMessages = {
  396. 0: "Transport unknown",
  397. 1: "Session ID unknown",
  398. 2: "Bad handshake method",
  399. 3: "Bad request",
  400. 4: "Forbidden",
  401. 5: "Unsupported protocol version",
  402. };
  403. /**
  404. * Exposes a subset of the http.ServerResponse interface, in order to be able to apply the middlewares to an upgrade
  405. * request.
  406. *
  407. * @see https://nodejs.org/api/http.html#class-httpserverresponse
  408. */
  409. class WebSocketResponse {
  410. constructor(req, socket) {
  411. this.req = req;
  412. this.socket = socket;
  413. // temporarily store the response headers on the req object (see the "headers" event)
  414. req[kResponseHeaders] = {};
  415. }
  416. setHeader(name, value) {
  417. this.req[kResponseHeaders][name] = value;
  418. }
  419. getHeader(name) {
  420. return this.req[kResponseHeaders][name];
  421. }
  422. removeHeader(name) {
  423. delete this.req[kResponseHeaders][name];
  424. }
  425. write() { }
  426. writeHead() { }
  427. end() {
  428. // we could return a proper error code, but the WebSocket client will emit an "error" event anyway.
  429. this.socket.destroy();
  430. }
  431. }
  432. /**
  433. * An Engine.IO server based on Node.js built-in HTTP server and the `ws` package for WebSocket connections.
  434. */
  435. class Server extends BaseServer {
  436. /**
  437. * Initialize websocket server
  438. *
  439. * @protected
  440. */
  441. init() {
  442. if (!~this.opts.transports.indexOf("websocket"))
  443. return;
  444. if (this.ws)
  445. this.ws.close();
  446. this.ws = new this.opts.wsEngine({
  447. noServer: true,
  448. clientTracking: false,
  449. perMessageDeflate: this.opts.perMessageDeflate,
  450. maxPayload: this.opts.maxHttpBufferSize,
  451. });
  452. if (typeof this.ws.on === "function") {
  453. this.ws.on("headers", (headersArray, req) => {
  454. // note: 'ws' uses an array of headers, while Engine.IO uses an object (response.writeHead() accepts both formats)
  455. // we could also try to parse the array and then sync the values, but that will be error-prone
  456. const additionalHeaders = req[kResponseHeaders] || {};
  457. delete req[kResponseHeaders];
  458. const isInitialRequest = !req._query.sid;
  459. if (isInitialRequest) {
  460. this.emit("initial_headers", additionalHeaders, req);
  461. }
  462. this.emit("headers", additionalHeaders, req);
  463. debug("writing headers: %j", additionalHeaders);
  464. Object.keys(additionalHeaders).forEach((key) => {
  465. headersArray.push(`${key}: ${additionalHeaders[key]}`);
  466. });
  467. });
  468. }
  469. }
  470. cleanup() {
  471. if (this.ws) {
  472. debug("closing webSocketServer");
  473. this.ws.close();
  474. // don't delete this.ws because it can be used again if the http server starts listening again
  475. }
  476. }
  477. /**
  478. * Prepares a request by processing the query string.
  479. *
  480. * @private
  481. */
  482. prepare(req) {
  483. // try to leverage pre-existing `req._query` (e.g: from connect)
  484. if (!req._query) {
  485. const url = new URL(req.url, "https://socket.io");
  486. req._query = Object.fromEntries(url.searchParams.entries());
  487. }
  488. }
  489. createTransport(transportName, req) {
  490. // @ts-expect-error 'polling' is a plain function used as constructor
  491. return new transports_1.default[transportName](req);
  492. }
  493. /**
  494. * Handles an Engine.IO HTTP request.
  495. *
  496. * @param {EngineRequest} req
  497. * @param {ServerResponse} res
  498. */
  499. handleRequest(req, res) {
  500. debug('handling "%s" http request "%s"', req.method, req.url);
  501. this.prepare(req);
  502. req.res = res;
  503. const callback = (errorCode, errorContext) => {
  504. if (errorCode !== undefined) {
  505. this.emit("connection_error", {
  506. req,
  507. code: errorCode,
  508. message: Server.errorMessages[errorCode],
  509. context: errorContext,
  510. });
  511. abortRequest(res, errorCode, errorContext);
  512. return;
  513. }
  514. if (req._query.sid) {
  515. debug("setting new request for existing client");
  516. this.clients[req._query.sid].transport.onRequest(req);
  517. }
  518. else {
  519. const closeConnection = (errorCode, errorContext) => abortRequest(res, errorCode, errorContext);
  520. this.handshake(req._query.transport, req, closeConnection);
  521. }
  522. };
  523. this._applyMiddlewares(req, res, (err) => {
  524. if (err) {
  525. callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
  526. }
  527. else {
  528. this.verify(req, false, callback);
  529. }
  530. });
  531. }
  532. /**
  533. * Handles an Engine.IO HTTP Upgrade.
  534. */
  535. handleUpgrade(req, socket, upgradeHead) {
  536. this.prepare(req);
  537. const res = new WebSocketResponse(req, socket);
  538. const callback = (errorCode, errorContext) => {
  539. if (errorCode !== undefined) {
  540. this.emit("connection_error", {
  541. req,
  542. code: errorCode,
  543. message: Server.errorMessages[errorCode],
  544. context: errorContext,
  545. });
  546. abortUpgrade(socket, errorCode, errorContext);
  547. return;
  548. }
  549. const head = Buffer.from(upgradeHead);
  550. upgradeHead = null;
  551. // some middlewares (like express-session) wait for the writeHead() call to flush their headers
  552. // see https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L220-L244
  553. res.writeHead();
  554. // delegate to ws
  555. this.ws.handleUpgrade(req, socket, head, (websocket) => {
  556. this.onWebSocket(req, socket, websocket);
  557. });
  558. };
  559. this._applyMiddlewares(req, res, (err) => {
  560. if (err) {
  561. callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
  562. }
  563. else {
  564. this.verify(req, true, callback);
  565. }
  566. });
  567. }
  568. /**
  569. * Called upon a ws.io connection.
  570. * @param req
  571. * @param socket
  572. * @param websocket
  573. * @private
  574. */
  575. onWebSocket(req, socket, websocket) {
  576. websocket.on("error", onUpgradeError);
  577. if (transports_1.default[req._query.transport] !== undefined &&
  578. !transports_1.default[req._query.transport].prototype.handlesUpgrades) {
  579. debug("transport doesnt handle upgraded requests");
  580. websocket.close();
  581. return;
  582. }
  583. // get client id
  584. const id = req._query.sid;
  585. // keep a reference to the ws.Socket
  586. req.websocket = websocket;
  587. if (id) {
  588. const client = this.clients[id];
  589. if (!client) {
  590. debug("upgrade attempt for closed client");
  591. websocket.close();
  592. }
  593. else if (client.upgrading) {
  594. debug("transport has already been trying to upgrade");
  595. websocket.close();
  596. }
  597. else if (client.upgraded) {
  598. debug("transport had already been upgraded");
  599. websocket.close();
  600. }
  601. else {
  602. debug("upgrading existing transport");
  603. // transport error handling takes over
  604. websocket.removeListener("error", onUpgradeError);
  605. const transport = this.createTransport(req._query.transport, req);
  606. // @ts-expect-error this option is only for WebSocket impl
  607. transport.perMessageDeflate = this.opts.perMessageDeflate;
  608. client._maybeUpgrade(transport);
  609. }
  610. }
  611. else {
  612. const closeConnection = (errorCode, errorContext) => abortUpgrade(socket, errorCode, errorContext);
  613. this.handshake(req._query.transport, req, closeConnection);
  614. }
  615. function onUpgradeError() {
  616. debug("websocket error before upgrade");
  617. // websocket.close() not needed
  618. }
  619. }
  620. /**
  621. * Captures upgrade requests for a http.Server.
  622. *
  623. * @param {http.Server} server
  624. * @param {Object} options
  625. */
  626. attach(server, options = {}) {
  627. const path = this._computePath(options);
  628. const destroyUpgradeTimeout = options.destroyUpgradeTimeout || 1000;
  629. function check(req) {
  630. // TODO use `path === new URL(...).pathname` in the next major release (ref: https://nodejs.org/api/url.html)
  631. return path === req.url.slice(0, path.length);
  632. }
  633. // cache and clean up listeners
  634. const listeners = server.listeners("request").slice(0);
  635. server.removeAllListeners("request");
  636. server.on("close", this.close.bind(this));
  637. server.on("listening", this.init.bind(this));
  638. // add request handler
  639. server.on("request", (req, res) => {
  640. if (check(req)) {
  641. debug('intercepting request for path "%s"', path);
  642. this.handleRequest(req, res);
  643. }
  644. else {
  645. let i = 0;
  646. const l = listeners.length;
  647. for (; i < l; i++) {
  648. listeners[i].call(server, req, res);
  649. }
  650. }
  651. });
  652. if (~this.opts.transports.indexOf("websocket")) {
  653. server.on("upgrade", (req, socket, head) => {
  654. if (check(req)) {
  655. this.handleUpgrade(req, socket, head);
  656. }
  657. else if (false !== options.destroyUpgrade) {
  658. // default node behavior is to disconnect when no handlers
  659. // but by adding a handler, we prevent that
  660. // and if no eio thing handles the upgrade
  661. // then the socket needs to die!
  662. setTimeout(function () {
  663. // @ts-ignore
  664. if (socket.writable && socket.bytesWritten <= 0) {
  665. socket.on("error", (e) => {
  666. debug("error while destroying upgrade: %s", e.message);
  667. });
  668. return socket.end();
  669. }
  670. }, destroyUpgradeTimeout);
  671. }
  672. });
  673. }
  674. }
  675. }
  676. exports.Server = Server;
  677. /**
  678. * Close the HTTP long-polling request
  679. *
  680. * @param res - the response object
  681. * @param errorCode - the error code
  682. * @param errorContext - additional error context
  683. *
  684. * @private
  685. */
  686. function abortRequest(res, errorCode, errorContext) {
  687. const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400;
  688. const message = errorContext && errorContext.message
  689. ? errorContext.message
  690. : Server.errorMessages[errorCode];
  691. res.writeHead(statusCode, { "Content-Type": "application/json" });
  692. res.end(JSON.stringify({
  693. code: errorCode,
  694. message,
  695. }));
  696. }
  697. /**
  698. * Close the WebSocket connection
  699. *
  700. * @param {net.Socket} socket
  701. * @param {string} errorCode - the error code
  702. * @param {object} errorContext - additional error context
  703. */
  704. function abortUpgrade(socket, errorCode, errorContext = {}) {
  705. socket.on("error", () => {
  706. debug("ignoring error from closed connection");
  707. });
  708. if (socket.writable) {
  709. const message = errorContext.message || Server.errorMessages[errorCode];
  710. const length = Buffer.byteLength(message);
  711. socket.write("HTTP/1.1 400 Bad Request\r\n" +
  712. "Connection: close\r\n" +
  713. "Content-type: text/html\r\n" +
  714. "Content-Length: " +
  715. length +
  716. "\r\n" +
  717. "\r\n" +
  718. message);
  719. }
  720. socket.destroy();
  721. }
  722. /* eslint-disable */
  723. /**
  724. * From https://github.com/nodejs/node/blob/v8.4.0/lib/_http_common.js#L303-L354
  725. *
  726. * True if val contains an invalid field-vchar
  727. * field-value = *( field-content / obs-fold )
  728. * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
  729. * field-vchar = VCHAR / obs-text
  730. *
  731. * checkInvalidHeaderChar() is currently designed to be inlinable by v8,
  732. * so take care when making changes to the implementation so that the source
  733. * code size does not exceed v8's default max_inlined_source_size setting.
  734. **/
  735. // prettier-ignore
  736. const validHdrChars = [
  737. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 - 15
  738. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
  739. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 - 47
  740. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 63
  741. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
  742. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 95
  743. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
  744. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 - 127
  745. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 ...
  746. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  747. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  748. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  749. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  750. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  751. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  752. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // ... 255
  753. ];
  754. function checkInvalidHeaderChar(val) {
  755. val += "";
  756. if (val.length < 1)
  757. return false;
  758. if (!validHdrChars[val.charCodeAt(0)]) {
  759. debug('invalid header, index 0, char "%s"', val.charCodeAt(0));
  760. return true;
  761. }
  762. if (val.length < 2)
  763. return false;
  764. if (!validHdrChars[val.charCodeAt(1)]) {
  765. debug('invalid header, index 1, char "%s"', val.charCodeAt(1));
  766. return true;
  767. }
  768. if (val.length < 3)
  769. return false;
  770. if (!validHdrChars[val.charCodeAt(2)]) {
  771. debug('invalid header, index 2, char "%s"', val.charCodeAt(2));
  772. return true;
  773. }
  774. if (val.length < 4)
  775. return false;
  776. if (!validHdrChars[val.charCodeAt(3)]) {
  777. debug('invalid header, index 3, char "%s"', val.charCodeAt(3));
  778. return true;
  779. }
  780. for (let i = 4; i < val.length; ++i) {
  781. if (!validHdrChars[val.charCodeAt(i)]) {
  782. debug('invalid header, index "%i", char "%s"', i, val.charCodeAt(i));
  783. return true;
  784. }
  785. }
  786. return false;
  787. }