Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

server.js 15KB

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