server.js 27 KB

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