source: trip-planner-front/node_modules/engine.io/build/server.js@ 1ad8e64

Last change on this file since 1ad8e64 was e29cc2e, checked in by Ema <ema_spirova@…>, 3 years ago

primeNG components

  • Property mode set to 100644
File size: 20.9 KB
Line 
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Server = exports.BaseServer = void 0;
4const qs = require("querystring");
5const url_1 = require("url");
6const base64id = require("base64id");
7const transports_1 = require("./transports");
8const events_1 = require("events");
9const socket_1 = require("./socket");
10const debug_1 = require("debug");
11const cookie_1 = require("cookie");
12const ws_1 = require("ws");
13const debug = (0, debug_1.default)("engine");
14class BaseServer extends events_1.EventEmitter {
15 /**
16 * Server constructor.
17 *
18 * @param {Object} opts - options
19 * @api public
20 */
21 constructor(opts = {}) {
22 super();
23 this.clients = {};
24 this.clientsCount = 0;
25 this.opts = Object.assign({
26 wsEngine: ws_1.Server,
27 pingTimeout: 20000,
28 pingInterval: 25000,
29 upgradeTimeout: 10000,
30 maxHttpBufferSize: 1e6,
31 transports: Object.keys(transports_1.default),
32 allowUpgrades: true,
33 httpCompression: {
34 threshold: 1024
35 },
36 cors: false,
37 allowEIO3: false
38 }, opts);
39 if (opts.cookie) {
40 this.opts.cookie = Object.assign({
41 name: "io",
42 path: "/",
43 // @ts-ignore
44 httpOnly: opts.cookie.path !== false,
45 sameSite: "lax"
46 }, opts.cookie);
47 }
48 if (this.opts.cors) {
49 this.corsMiddleware = require("cors")(this.opts.cors);
50 }
51 if (opts.perMessageDeflate) {
52 this.opts.perMessageDeflate = Object.assign({
53 threshold: 1024
54 }, opts.perMessageDeflate);
55 }
56 this.init();
57 }
58 /**
59 * Returns a list of available transports for upgrade given a certain transport.
60 *
61 * @return {Array}
62 * @api public
63 */
64 upgrades(transport) {
65 if (!this.opts.allowUpgrades)
66 return [];
67 return transports_1.default[transport].upgradesTo || [];
68 }
69 /**
70 * Verifies a request.
71 *
72 * @param {http.IncomingMessage}
73 * @return {Boolean} whether the request is valid
74 * @api private
75 */
76 verify(req, upgrade, fn) {
77 // transport check
78 const transport = req._query.transport;
79 if (!~this.opts.transports.indexOf(transport)) {
80 debug('unknown transport "%s"', transport);
81 return fn(Server.errors.UNKNOWN_TRANSPORT, { transport });
82 }
83 // 'Origin' header check
84 const isOriginInvalid = checkInvalidHeaderChar(req.headers.origin);
85 if (isOriginInvalid) {
86 const origin = req.headers.origin;
87 req.headers.origin = null;
88 debug("origin header invalid");
89 return fn(Server.errors.BAD_REQUEST, {
90 name: "INVALID_ORIGIN",
91 origin
92 });
93 }
94 // sid check
95 const sid = req._query.sid;
96 if (sid) {
97 if (!this.clients.hasOwnProperty(sid)) {
98 debug('unknown sid "%s"', sid);
99 return fn(Server.errors.UNKNOWN_SID, {
100 sid
101 });
102 }
103 const previousTransport = this.clients[sid].transport.name;
104 if (!upgrade && previousTransport !== transport) {
105 debug("bad request: unexpected transport without upgrade");
106 return fn(Server.errors.BAD_REQUEST, {
107 name: "TRANSPORT_MISMATCH",
108 transport,
109 previousTransport
110 });
111 }
112 }
113 else {
114 // handshake is GET only
115 if ("GET" !== req.method) {
116 return fn(Server.errors.BAD_HANDSHAKE_METHOD, {
117 method: req.method
118 });
119 }
120 if (!this.opts.allowRequest)
121 return fn();
122 return this.opts.allowRequest(req, (message, success) => {
123 if (!success) {
124 return fn(Server.errors.FORBIDDEN, {
125 message
126 });
127 }
128 fn();
129 });
130 }
131 fn();
132 }
133 /**
134 * Closes all clients.
135 *
136 * @api public
137 */
138 close() {
139 debug("closing all open clients");
140 for (let i in this.clients) {
141 if (this.clients.hasOwnProperty(i)) {
142 this.clients[i].close(true);
143 }
144 }
145 this.cleanup();
146 return this;
147 }
148 /**
149 * generate a socket id.
150 * Overwrite this method to generate your custom socket id
151 *
152 * @param {Object} request object
153 * @api public
154 */
155 generateId(req) {
156 return base64id.generateId();
157 }
158 /**
159 * Handshakes a new client.
160 *
161 * @param {String} transport name
162 * @param {Object} request object
163 * @param {Function} closeConnection
164 *
165 * @api protected
166 */
167 async handshake(transportName, req, closeConnection) {
168 const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
169 if (protocol === 3 && !this.opts.allowEIO3) {
170 debug("unsupported protocol version");
171 this.emit("connection_error", {
172 req,
173 code: Server.errors.UNSUPPORTED_PROTOCOL_VERSION,
174 message: Server.errorMessages[Server.errors.UNSUPPORTED_PROTOCOL_VERSION],
175 context: {
176 protocol
177 }
178 });
179 closeConnection(Server.errors.UNSUPPORTED_PROTOCOL_VERSION);
180 return;
181 }
182 let id;
183 try {
184 id = await this.generateId(req);
185 }
186 catch (e) {
187 debug("error while generating an id");
188 this.emit("connection_error", {
189 req,
190 code: Server.errors.BAD_REQUEST,
191 message: Server.errorMessages[Server.errors.BAD_REQUEST],
192 context: {
193 name: "ID_GENERATION_ERROR",
194 error: e
195 }
196 });
197 closeConnection(Server.errors.BAD_REQUEST);
198 return;
199 }
200 debug('handshaking client "%s"', id);
201 try {
202 var transport = this.createTransport(transportName, req);
203 if ("polling" === transportName) {
204 transport.maxHttpBufferSize = this.opts.maxHttpBufferSize;
205 transport.httpCompression = this.opts.httpCompression;
206 }
207 else if ("websocket" === transportName) {
208 transport.perMessageDeflate = this.opts.perMessageDeflate;
209 }
210 if (req._query && req._query.b64) {
211 transport.supportsBinary = false;
212 }
213 else {
214 transport.supportsBinary = true;
215 }
216 }
217 catch (e) {
218 debug('error handshaking to transport "%s"', transportName);
219 this.emit("connection_error", {
220 req,
221 code: Server.errors.BAD_REQUEST,
222 message: Server.errorMessages[Server.errors.BAD_REQUEST],
223 context: {
224 name: "TRANSPORT_HANDSHAKE_ERROR",
225 error: e
226 }
227 });
228 closeConnection(Server.errors.BAD_REQUEST);
229 return;
230 }
231 const socket = new socket_1.Socket(id, this, transport, req, protocol);
232 transport.on("headers", (headers, req) => {
233 const isInitialRequest = !req._query.sid;
234 if (isInitialRequest) {
235 if (this.opts.cookie) {
236 headers["Set-Cookie"] = [
237 // @ts-ignore
238 (0, cookie_1.serialize)(this.opts.cookie.name, id, this.opts.cookie)
239 ];
240 }
241 this.emit("initial_headers", headers, req);
242 }
243 this.emit("headers", headers, req);
244 });
245 transport.onRequest(req);
246 this.clients[id] = socket;
247 this.clientsCount++;
248 socket.once("close", () => {
249 delete this.clients[id];
250 this.clientsCount--;
251 });
252 this.emit("connection", socket);
253 return transport;
254 }
255}
256exports.BaseServer = BaseServer;
257/**
258 * Protocol errors mappings.
259 */
260BaseServer.errors = {
261 UNKNOWN_TRANSPORT: 0,
262 UNKNOWN_SID: 1,
263 BAD_HANDSHAKE_METHOD: 2,
264 BAD_REQUEST: 3,
265 FORBIDDEN: 4,
266 UNSUPPORTED_PROTOCOL_VERSION: 5
267};
268BaseServer.errorMessages = {
269 0: "Transport unknown",
270 1: "Session ID unknown",
271 2: "Bad handshake method",
272 3: "Bad request",
273 4: "Forbidden",
274 5: "Unsupported protocol version"
275};
276class Server extends BaseServer {
277 /**
278 * Initialize websocket server
279 *
280 * @api protected
281 */
282 init() {
283 if (!~this.opts.transports.indexOf("websocket"))
284 return;
285 if (this.ws)
286 this.ws.close();
287 this.ws = new this.opts.wsEngine({
288 noServer: true,
289 clientTracking: false,
290 perMessageDeflate: this.opts.perMessageDeflate,
291 maxPayload: this.opts.maxHttpBufferSize
292 });
293 if (typeof this.ws.on === "function") {
294 this.ws.on("headers", (headersArray, req) => {
295 // note: 'ws' uses an array of headers, while Engine.IO uses an object (response.writeHead() accepts both formats)
296 // we could also try to parse the array and then sync the values, but that will be error-prone
297 const additionalHeaders = {};
298 const isInitialRequest = !req._query.sid;
299 if (isInitialRequest) {
300 this.emit("initial_headers", additionalHeaders, req);
301 }
302 this.emit("headers", additionalHeaders, req);
303 Object.keys(additionalHeaders).forEach(key => {
304 headersArray.push(`${key}: ${additionalHeaders[key]}`);
305 });
306 });
307 }
308 }
309 cleanup() {
310 if (this.ws) {
311 debug("closing webSocketServer");
312 this.ws.close();
313 // don't delete this.ws because it can be used again if the http server starts listening again
314 }
315 }
316 /**
317 * Prepares a request by processing the query string.
318 *
319 * @api private
320 */
321 prepare(req) {
322 // try to leverage pre-existing `req._query` (e.g: from connect)
323 if (!req._query) {
324 req._query = ~req.url.indexOf("?") ? qs.parse((0, url_1.parse)(req.url).query) : {};
325 }
326 }
327 createTransport(transportName, req) {
328 return new transports_1.default[transportName](req);
329 }
330 /**
331 * Handles an Engine.IO HTTP request.
332 *
333 * @param {http.IncomingMessage} request
334 * @param {http.ServerResponse|http.OutgoingMessage} response
335 * @api public
336 */
337 handleRequest(req, res) {
338 debug('handling "%s" http request "%s"', req.method, req.url);
339 this.prepare(req);
340 req.res = res;
341 const callback = (errorCode, errorContext) => {
342 if (errorCode !== undefined) {
343 this.emit("connection_error", {
344 req,
345 code: errorCode,
346 message: Server.errorMessages[errorCode],
347 context: errorContext
348 });
349 abortRequest(res, errorCode, errorContext);
350 return;
351 }
352 if (req._query.sid) {
353 debug("setting new request for existing client");
354 this.clients[req._query.sid].transport.onRequest(req);
355 }
356 else {
357 const closeConnection = (errorCode, errorContext) => abortRequest(res, errorCode, errorContext);
358 this.handshake(req._query.transport, req, closeConnection);
359 }
360 };
361 if (this.corsMiddleware) {
362 this.corsMiddleware.call(null, req, res, () => {
363 this.verify(req, false, callback);
364 });
365 }
366 else {
367 this.verify(req, false, callback);
368 }
369 }
370 /**
371 * Handles an Engine.IO HTTP Upgrade.
372 *
373 * @api public
374 */
375 handleUpgrade(req, socket, upgradeHead) {
376 this.prepare(req);
377 this.verify(req, true, (errorCode, errorContext) => {
378 if (errorCode) {
379 this.emit("connection_error", {
380 req,
381 code: errorCode,
382 message: Server.errorMessages[errorCode],
383 context: errorContext
384 });
385 abortUpgrade(socket, errorCode, errorContext);
386 return;
387 }
388 const head = Buffer.from(upgradeHead); // eslint-disable-line node/no-deprecated-api
389 upgradeHead = null;
390 // delegate to ws
391 this.ws.handleUpgrade(req, socket, head, websocket => {
392 this.onWebSocket(req, socket, websocket);
393 });
394 });
395 }
396 /**
397 * Called upon a ws.io connection.
398 *
399 * @param {ws.Socket} websocket
400 * @api private
401 */
402 onWebSocket(req, socket, websocket) {
403 websocket.on("error", onUpgradeError);
404 if (transports_1.default[req._query.transport] !== undefined &&
405 !transports_1.default[req._query.transport].prototype.handlesUpgrades) {
406 debug("transport doesnt handle upgraded requests");
407 websocket.close();
408 return;
409 }
410 // get client id
411 const id = req._query.sid;
412 // keep a reference to the ws.Socket
413 req.websocket = websocket;
414 if (id) {
415 const client = this.clients[id];
416 if (!client) {
417 debug("upgrade attempt for closed client");
418 websocket.close();
419 }
420 else if (client.upgrading) {
421 debug("transport has already been trying to upgrade");
422 websocket.close();
423 }
424 else if (client.upgraded) {
425 debug("transport had already been upgraded");
426 websocket.close();
427 }
428 else {
429 debug("upgrading existing transport");
430 // transport error handling takes over
431 websocket.removeListener("error", onUpgradeError);
432 const transport = this.createTransport(req._query.transport, req);
433 if (req._query && req._query.b64) {
434 transport.supportsBinary = false;
435 }
436 else {
437 transport.supportsBinary = true;
438 }
439 transport.perMessageDeflate = this.opts.perMessageDeflate;
440 client.maybeUpgrade(transport);
441 }
442 }
443 else {
444 // transport error handling takes over
445 websocket.removeListener("error", onUpgradeError);
446 const closeConnection = (errorCode, errorContext) => abortUpgrade(socket, errorCode, errorContext);
447 this.handshake(req._query.transport, req, closeConnection);
448 }
449 function onUpgradeError() {
450 debug("websocket error before upgrade");
451 // websocket.close() not needed
452 }
453 }
454 /**
455 * Captures upgrade requests for a http.Server.
456 *
457 * @param {http.Server} server
458 * @param {Object} options
459 * @api public
460 */
461 attach(server, options = {}) {
462 let path = (options.path || "/engine.io").replace(/\/$/, "");
463 const destroyUpgradeTimeout = options.destroyUpgradeTimeout || 1000;
464 // normalize path
465 path += "/";
466 function check(req) {
467 return path === req.url.substr(0, path.length);
468 }
469 // cache and clean up listeners
470 const listeners = server.listeners("request").slice(0);
471 server.removeAllListeners("request");
472 server.on("close", this.close.bind(this));
473 server.on("listening", this.init.bind(this));
474 // add request handler
475 server.on("request", (req, res) => {
476 if (check(req)) {
477 debug('intercepting request for path "%s"', path);
478 this.handleRequest(req, res);
479 }
480 else {
481 let i = 0;
482 const l = listeners.length;
483 for (; i < l; i++) {
484 listeners[i].call(server, req, res);
485 }
486 }
487 });
488 if (~this.opts.transports.indexOf("websocket")) {
489 server.on("upgrade", (req, socket, head) => {
490 if (check(req)) {
491 this.handleUpgrade(req, socket, head);
492 }
493 else if (false !== options.destroyUpgrade) {
494 // default node behavior is to disconnect when no handlers
495 // but by adding a handler, we prevent that
496 // and if no eio thing handles the upgrade
497 // then the socket needs to die!
498 setTimeout(function () {
499 // @ts-ignore
500 if (socket.writable && socket.bytesWritten <= 0) {
501 return socket.end();
502 }
503 }, destroyUpgradeTimeout);
504 }
505 });
506 }
507 }
508}
509exports.Server = Server;
510/**
511 * Close the HTTP long-polling request
512 *
513 * @param res - the response object
514 * @param errorCode - the error code
515 * @param errorContext - additional error context
516 *
517 * @api private
518 */
519function abortRequest(res, errorCode, errorContext) {
520 const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400;
521 const message = errorContext && errorContext.message
522 ? errorContext.message
523 : Server.errorMessages[errorCode];
524 res.writeHead(statusCode, { "Content-Type": "application/json" });
525 res.end(JSON.stringify({
526 code: errorCode,
527 message
528 }));
529}
530/**
531 * Close the WebSocket connection
532 *
533 * @param {net.Socket} socket
534 * @param {string} errorCode - the error code
535 * @param {object} errorContext - additional error context
536 *
537 * @api private
538 */
539function abortUpgrade(socket, errorCode, errorContext = {}) {
540 socket.on("error", () => {
541 debug("ignoring error from closed connection");
542 });
543 if (socket.writable) {
544 const message = errorContext.message || Server.errorMessages[errorCode];
545 const length = Buffer.byteLength(message);
546 socket.write("HTTP/1.1 400 Bad Request\r\n" +
547 "Connection: close\r\n" +
548 "Content-type: text/html\r\n" +
549 "Content-Length: " +
550 length +
551 "\r\n" +
552 "\r\n" +
553 message);
554 }
555 socket.destroy();
556}
557/* eslint-disable */
558/**
559 * From https://github.com/nodejs/node/blob/v8.4.0/lib/_http_common.js#L303-L354
560 *
561 * True if val contains an invalid field-vchar
562 * field-value = *( field-content / obs-fold )
563 * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
564 * field-vchar = VCHAR / obs-text
565 *
566 * checkInvalidHeaderChar() is currently designed to be inlinable by v8,
567 * so take care when making changes to the implementation so that the source
568 * code size does not exceed v8's default max_inlined_source_size setting.
569 **/
570// prettier-ignore
571const validHdrChars = [
572 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
573 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
574 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
575 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
576 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
577 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
578 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
579 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
580 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
581 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
582 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
583 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
584 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
585 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
586 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
587 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // ... 255
588];
589function checkInvalidHeaderChar(val) {
590 val += "";
591 if (val.length < 1)
592 return false;
593 if (!validHdrChars[val.charCodeAt(0)]) {
594 debug('invalid header, index 0, char "%s"', val.charCodeAt(0));
595 return true;
596 }
597 if (val.length < 2)
598 return false;
599 if (!validHdrChars[val.charCodeAt(1)]) {
600 debug('invalid header, index 1, char "%s"', val.charCodeAt(1));
601 return true;
602 }
603 if (val.length < 3)
604 return false;
605 if (!validHdrChars[val.charCodeAt(2)]) {
606 debug('invalid header, index 2, char "%s"', val.charCodeAt(2));
607 return true;
608 }
609 if (val.length < 4)
610 return false;
611 if (!validHdrChars[val.charCodeAt(3)]) {
612 debug('invalid header, index 3, char "%s"', val.charCodeAt(3));
613 return true;
614 }
615 for (let i = 4; i < val.length; ++i) {
616 if (!validHdrChars[val.charCodeAt(i)]) {
617 debug('invalid header, index "%i", char "%s"', i, val.charCodeAt(i));
618 return true;
619 }
620 }
621 return false;
622}
Note: See TracBrowser for help on using the repository browser.