source: trip-planner-front/node_modules/http-parser-js/http-parser.js@ 6c1585f

Last change on this file since 6c1585f was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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