Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

server.js 18KB

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