server.js 27 KB

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