GSI - Employe Self Service Mobile
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.

463 lines
13 KiB

2 months ago
  1. /*jshint node:true */
  2. var assert = require('assert');
  3. exports.HTTPParser = HTTPParser;
  4. function HTTPParser(type) {
  5. assert.ok(type === HTTPParser.REQUEST || type === HTTPParser.RESPONSE || type === undefined);
  6. if (type === undefined) {
  7. // Node v12+
  8. } else {
  9. this.initialize(type);
  10. }
  11. this.maxHeaderSize=HTTPParser.maxHeaderSize
  12. }
  13. HTTPParser.prototype.initialize = function (type, async_resource) {
  14. assert.ok(type === HTTPParser.REQUEST || type === HTTPParser.RESPONSE);
  15. this.type = type;
  16. this.state = type + '_LINE';
  17. this.info = {
  18. headers: [],
  19. upgrade: false
  20. };
  21. this.trailers = [];
  22. this.line = '';
  23. this.isChunked = false;
  24. this.connection = '';
  25. this.headerSize = 0; // for preventing too big headers
  26. this.body_bytes = null;
  27. this.isUserCall = false;
  28. this.hadError = false;
  29. };
  30. HTTPParser.encoding = 'ascii';
  31. HTTPParser.maxHeaderSize = 80 * 1024; // maxHeaderSize (in bytes) is configurable, but 80kb by default;
  32. HTTPParser.REQUEST = 'REQUEST';
  33. HTTPParser.RESPONSE = 'RESPONSE';
  34. // Note: *not* starting with kOnHeaders=0 line the Node parser, because any
  35. // newly added constants (kOnTimeout in Node v12.19.0) will overwrite 0!
  36. var kOnHeaders = HTTPParser.kOnHeaders = 1;
  37. var kOnHeadersComplete = HTTPParser.kOnHeadersComplete = 2;
  38. var kOnBody = HTTPParser.kOnBody = 3;
  39. var kOnMessageComplete = HTTPParser.kOnMessageComplete = 4;
  40. // Some handler stubs, needed for compatibility
  41. HTTPParser.prototype[kOnHeaders] =
  42. HTTPParser.prototype[kOnHeadersComplete] =
  43. HTTPParser.prototype[kOnBody] =
  44. HTTPParser.prototype[kOnMessageComplete] = function () {};
  45. var compatMode0_12 = true;
  46. Object.defineProperty(HTTPParser, 'kOnExecute', {
  47. get: function () {
  48. // hack for backward compatibility
  49. compatMode0_12 = false;
  50. return 99;
  51. }
  52. });
  53. var methods = exports.methods = HTTPParser.methods = [
  54. 'DELETE',
  55. 'GET',
  56. 'HEAD',
  57. 'POST',
  58. 'PUT',
  59. 'CONNECT',
  60. 'OPTIONS',
  61. 'TRACE',
  62. 'COPY',
  63. 'LOCK',
  64. 'MKCOL',
  65. 'MOVE',
  66. 'PROPFIND',
  67. 'PROPPATCH',
  68. 'SEARCH',
  69. 'UNLOCK',
  70. 'BIND',
  71. 'REBIND',
  72. 'UNBIND',
  73. 'ACL',
  74. 'REPORT',
  75. 'MKACTIVITY',
  76. 'CHECKOUT',
  77. 'MERGE',
  78. 'M-SEARCH',
  79. 'NOTIFY',
  80. 'SUBSCRIBE',
  81. 'UNSUBSCRIBE',
  82. 'PATCH',
  83. 'PURGE',
  84. 'MKCALENDAR',
  85. 'LINK',
  86. 'UNLINK',
  87. 'SOURCE',
  88. ];
  89. var method_connect = methods.indexOf('CONNECT');
  90. HTTPParser.prototype.reinitialize = HTTPParser;
  91. HTTPParser.prototype.close =
  92. HTTPParser.prototype.pause =
  93. HTTPParser.prototype.resume =
  94. HTTPParser.prototype.free = function () {};
  95. HTTPParser.prototype._compatMode0_11 = false;
  96. HTTPParser.prototype.getAsyncId = function() { return 0; };
  97. var headerState = {
  98. REQUEST_LINE: true,
  99. RESPONSE_LINE: true,
  100. HEADER: true
  101. };
  102. HTTPParser.prototype.execute = function (chunk, start, length) {
  103. if (!(this instanceof HTTPParser)) {
  104. throw new TypeError('not a HTTPParser');
  105. }
  106. // backward compat to node < 0.11.4
  107. // Note: the start and length params were removed in newer version
  108. start = start || 0;
  109. length = typeof length === 'number' ? length : chunk.length;
  110. this.chunk = chunk;
  111. this.offset = start;
  112. var end = this.end = start + length;
  113. try {
  114. while (this.offset < end) {
  115. if (this[this.state]()) {
  116. break;
  117. }
  118. }
  119. } catch (err) {
  120. if (this.isUserCall) {
  121. throw err;
  122. }
  123. this.hadError = true;
  124. return err;
  125. }
  126. this.chunk = null;
  127. length = this.offset - start;
  128. if (headerState[this.state]) {
  129. this.headerSize += length;
  130. if (this.headerSize > (this.maxHeaderSize||HTTPParser.maxHeaderSize)) {
  131. return new Error('max header size exceeded');
  132. }
  133. }
  134. return length;
  135. };
  136. var stateFinishAllowed = {
  137. REQUEST_LINE: true,
  138. RESPONSE_LINE: true,
  139. BODY_RAW: true
  140. };
  141. HTTPParser.prototype.finish = function () {
  142. if (this.hadError) {
  143. return;
  144. }
  145. if (!stateFinishAllowed[this.state]) {
  146. return new Error('invalid state for EOF');
  147. }
  148. if (this.state === 'BODY_RAW') {
  149. this.userCall()(this[kOnMessageComplete]());
  150. }
  151. };
  152. // These three methods are used for an internal speed optimization, and it also
  153. // works if theses are noops. Basically consume() asks us to read the bytes
  154. // ourselves, but if we don't do it we get them through execute().
  155. HTTPParser.prototype.consume =
  156. HTTPParser.prototype.unconsume =
  157. HTTPParser.prototype.getCurrentBuffer = function () {};
  158. //For correct error handling - see HTTPParser#execute
  159. //Usage: this.userCall()(userFunction('arg'));
  160. HTTPParser.prototype.userCall = function () {
  161. this.isUserCall = true;
  162. var self = this;
  163. return function (ret) {
  164. self.isUserCall = false;
  165. return ret;
  166. };
  167. };
  168. HTTPParser.prototype.nextRequest = function () {
  169. this.userCall()(this[kOnMessageComplete]());
  170. this.reinitialize(this.type);
  171. };
  172. HTTPParser.prototype.consumeLine = function () {
  173. var end = this.end,
  174. chunk = this.chunk;
  175. for (var i = this.offset; i < end; i++) {
  176. if (chunk[i] === 0x0a) { // \n
  177. var line = this.line + chunk.toString(HTTPParser.encoding, this.offset, i);
  178. if (line.charAt(line.length - 1) === '\r') {
  179. line = line.substr(0, line.length - 1);
  180. }
  181. this.line = '';
  182. this.offset = i + 1;
  183. return line;
  184. }
  185. }
  186. //line split over multiple chunks
  187. this.line += chunk.toString(HTTPParser.encoding, this.offset, this.end);
  188. this.offset = this.end;
  189. };
  190. var headerExp = /^([^: \t]+):[ \t]*((?:.*[^ \t])|)/;
  191. var headerContinueExp = /^[ \t]+(.*[^ \t])/;
  192. HTTPParser.prototype.parseHeader = function (line, headers) {
  193. if (line.indexOf('\r') !== -1) {
  194. throw parseErrorCode('HPE_LF_EXPECTED');
  195. }
  196. var match = headerExp.exec(line);
  197. var k = match && match[1];
  198. if (k) { // skip empty string (malformed header)
  199. headers.push(k);
  200. headers.push(match[2]);
  201. } else {
  202. var matchContinue = headerContinueExp.exec(line);
  203. if (matchContinue && headers.length) {
  204. if (headers[headers.length - 1]) {
  205. headers[headers.length - 1] += ' ';
  206. }
  207. headers[headers.length - 1] += matchContinue[1];
  208. }
  209. }
  210. };
  211. var requestExp = /^([A-Z-]+) ([^ ]+) HTTP\/(\d)\.(\d)$/;
  212. HTTPParser.prototype.REQUEST_LINE = function () {
  213. var line = this.consumeLine();
  214. if (!line) {
  215. return;
  216. }
  217. var match = requestExp.exec(line);
  218. if (match === null) {
  219. throw parseErrorCode('HPE_INVALID_CONSTANT');
  220. }
  221. this.info.method = this._compatMode0_11 ? match[1] : methods.indexOf(match[1]);
  222. if (this.info.method === -1) {
  223. throw new Error('invalid request method');
  224. }
  225. this.info.url = match[2];
  226. this.info.versionMajor = +match[3];
  227. this.info.versionMinor = +match[4];
  228. this.body_bytes = 0;
  229. this.state = 'HEADER';
  230. };
  231. var responseExp = /^HTTP\/(\d)\.(\d) (\d{3}) ?(.*)$/;
  232. HTTPParser.prototype.RESPONSE_LINE = function () {
  233. var line = this.consumeLine();
  234. if (!line) {
  235. return;
  236. }
  237. var match = responseExp.exec(line);
  238. if (match === null) {
  239. throw parseErrorCode('HPE_INVALID_CONSTANT');
  240. }
  241. this.info.versionMajor = +match[1];
  242. this.info.versionMinor = +match[2];
  243. var statusCode = this.info.statusCode = +match[3];
  244. this.info.statusMessage = match[4];
  245. // Implied zero length.
  246. if ((statusCode / 100 | 0) === 1 || statusCode === 204 || statusCode === 304) {
  247. this.body_bytes = 0;
  248. }
  249. this.state = 'HEADER';
  250. };
  251. HTTPParser.prototype.shouldKeepAlive = function () {
  252. if (this.info.versionMajor > 0 && this.info.versionMinor > 0) {
  253. if (this.connection.indexOf('close') !== -1) {
  254. return false;
  255. }
  256. } else if (this.connection.indexOf('keep-alive') === -1) {
  257. return false;
  258. }
  259. if (this.body_bytes !== null || this.isChunked) { // || skipBody
  260. return true;
  261. }
  262. return false;
  263. };
  264. HTTPParser.prototype.HEADER = function () {
  265. var line = this.consumeLine();
  266. if (line === undefined) {
  267. return;
  268. }
  269. var info = this.info;
  270. if (line) {
  271. this.parseHeader(line, info.headers);
  272. } else {
  273. var headers = info.headers;
  274. var hasContentLength = false;
  275. var currentContentLengthValue;
  276. var hasUpgradeHeader = false;
  277. for (var i = 0; i < headers.length; i += 2) {
  278. switch (headers[i].toLowerCase()) {
  279. case 'transfer-encoding':
  280. this.isChunked = headers[i + 1].toLowerCase() === 'chunked';
  281. break;
  282. case 'content-length':
  283. currentContentLengthValue = +headers[i + 1];
  284. if (hasContentLength) {
  285. // Fix duplicate Content-Length header with same values.
  286. // Throw error only if values are different.
  287. // Known issues:
  288. // https://github.com/request/request/issues/2091#issuecomment-328715113
  289. // https://github.com/nodejs/node/issues/6517#issuecomment-216263771
  290. if (currentContentLengthValue !== this.body_bytes) {
  291. throw parseErrorCode('HPE_UNEXPECTED_CONTENT_LENGTH');
  292. }
  293. } else {
  294. hasContentLength = true;
  295. this.body_bytes = currentContentLengthValue;
  296. }
  297. break;
  298. case 'connection':
  299. this.connection += headers[i + 1].toLowerCase();
  300. break;
  301. case 'upgrade':
  302. hasUpgradeHeader = true;
  303. break;
  304. }
  305. }
  306. // if both isChunked and hasContentLength, isChunked wins
  307. // This is required so the body is parsed using the chunked method, and matches
  308. // Chrome's behavior. We could, maybe, ignore them both (would get chunked
  309. // encoding into the body), and/or disable shouldKeepAlive to be more
  310. // resilient.
  311. if (this.isChunked && hasContentLength) {
  312. hasContentLength = false;
  313. this.body_bytes = null;
  314. }
  315. // Logic from https://github.com/nodejs/http-parser/blob/921d5585515a153fa00e411cf144280c59b41f90/http_parser.c#L1727-L1737
  316. // "For responses, "Upgrade: foo" and "Connection: upgrade" are
  317. // mandatory only when it is a 101 Switching Protocols response,
  318. // otherwise it is purely informational, to announce support.
  319. if (hasUpgradeHeader && this.connection.indexOf('upgrade') != -1) {
  320. info.upgrade = this.type === HTTPParser.REQUEST || info.statusCode === 101;
  321. } else {
  322. info.upgrade = info.method === method_connect;
  323. }
  324. if (this.isChunked && info.upgrade) {
  325. this.isChunked = false;
  326. }
  327. info.shouldKeepAlive = this.shouldKeepAlive();
  328. //problem which also exists in original node: we should know skipBody before calling onHeadersComplete
  329. var skipBody;
  330. if (compatMode0_12) {
  331. skipBody = this.userCall()(this[kOnHeadersComplete](info));
  332. } else {
  333. skipBody = this.userCall()(this[kOnHeadersComplete](info.versionMajor,
  334. info.versionMinor, info.headers, info.method, info.url, info.statusCode,
  335. info.statusMessage, info.upgrade, info.shouldKeepAlive));
  336. }
  337. if (skipBody === 2) {
  338. this.nextRequest();
  339. return true;
  340. } else if (this.isChunked && !skipBody) {
  341. this.state = 'BODY_CHUNKHEAD';
  342. } else if (skipBody || this.body_bytes === 0) {
  343. this.nextRequest();
  344. // For older versions of node (v6.x and older?), that return skipBody=1 or skipBody=true,
  345. // need this "return true;" if it's an upgrade request.
  346. return info.upgrade;
  347. } else if (this.body_bytes === null) {
  348. this.state = 'BODY_RAW';
  349. } else {
  350. this.state = 'BODY_SIZED';
  351. }
  352. }
  353. };
  354. HTTPParser.prototype.BODY_CHUNKHEAD = function () {
  355. var line = this.consumeLine();
  356. if (line === undefined) {
  357. return;
  358. }
  359. this.body_bytes = parseInt(line, 16);
  360. if (!this.body_bytes) {
  361. this.state = 'BODY_CHUNKTRAILERS';
  362. } else {
  363. this.state = 'BODY_CHUNK';
  364. }
  365. };
  366. HTTPParser.prototype.BODY_CHUNK = function () {
  367. var length = Math.min(this.end - this.offset, this.body_bytes);
  368. this.userCall()(this[kOnBody](this.chunk, this.offset, length));
  369. this.offset += length;
  370. this.body_bytes -= length;
  371. if (!this.body_bytes) {
  372. this.state = 'BODY_CHUNKEMPTYLINE';
  373. }
  374. };
  375. HTTPParser.prototype.BODY_CHUNKEMPTYLINE = function () {
  376. var line = this.consumeLine();
  377. if (line === undefined) {
  378. return;
  379. }
  380. assert.equal(line, '');
  381. this.state = 'BODY_CHUNKHEAD';
  382. };
  383. HTTPParser.prototype.BODY_CHUNKTRAILERS = function () {
  384. var line = this.consumeLine();
  385. if (line === undefined) {
  386. return;
  387. }
  388. if (line) {
  389. this.parseHeader(line, this.trailers);
  390. } else {
  391. if (this.trailers.length) {
  392. this.userCall()(this[kOnHeaders](this.trailers, ''));
  393. }
  394. this.nextRequest();
  395. }
  396. };
  397. HTTPParser.prototype.BODY_RAW = function () {
  398. var length = this.end - this.offset;
  399. this.userCall()(this[kOnBody](this.chunk, this.offset, length));
  400. this.offset = this.end;
  401. };
  402. HTTPParser.prototype.BODY_SIZED = function () {
  403. var length = Math.min(this.end - this.offset, this.body_bytes);
  404. this.userCall()(this[kOnBody](this.chunk, this.offset, length));
  405. this.offset += length;
  406. this.body_bytes -= length;
  407. if (!this.body_bytes) {
  408. this.nextRequest();
  409. }
  410. };
  411. // backward compat to node < 0.11.6
  412. ['Headers', 'HeadersComplete', 'Body', 'MessageComplete'].forEach(function (name) {
  413. var k = HTTPParser['kOn' + name];
  414. Object.defineProperty(HTTPParser.prototype, 'on' + name, {
  415. get: function () {
  416. return this[k];
  417. },
  418. set: function (to) {
  419. // hack for backward compatibility
  420. this._compatMode0_11 = true;
  421. method_connect = 'CONNECT';
  422. return (this[k] = to);
  423. }
  424. });
  425. });
  426. function parseErrorCode(code) {
  427. var err = new Error('Parse Error');
  428. err.code = code;
  429. return err;
  430. }