• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23
24const {
25  MathMin,
26  Symbol,
27} = primordials;
28const { setImmediate } = require('timers');
29
30const { getOptionValue } = require('internal/options');
31
32const { methods, HTTPParser } =
33  getOptionValue('--http-parser') === 'legacy' ?
34    internalBinding('http_parser') : internalBinding('http_parser_llhttp');
35const insecureHTTPParser = getOptionValue('--insecure-http-parser');
36
37const FreeList = require('internal/freelist');
38const incoming = require('_http_incoming');
39const {
40  IncomingMessage,
41  readStart,
42  readStop
43} = incoming;
44
45let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
46  debug = fn;
47});
48
49const kIncomingMessage = Symbol('IncomingMessage');
50const kOnHeaders = HTTPParser.kOnHeaders | 0;
51const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
52const kOnBody = HTTPParser.kOnBody | 0;
53const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
54const kOnExecute = HTTPParser.kOnExecute | 0;
55const kOnTimeout = HTTPParser.kOnTimeout | 0;
56
57const MAX_HEADER_PAIRS = 2000;
58
59// Only called in the slow case where slow means
60// that the request headers were either fragmented
61// across multiple TCP packets or too large to be
62// processed in a single run. This method is also
63// called to process trailing HTTP headers.
64function parserOnHeaders(headers, url) {
65  // Once we exceeded headers limit - stop collecting them
66  if (this.maxHeaderPairs <= 0 ||
67      this._headers.length < this.maxHeaderPairs) {
68    this._headers = this._headers.concat(headers);
69  }
70  this._url += url;
71}
72
73// `headers` and `url` are set only if .onHeaders() has not been called for
74// this request.
75// `url` is not set for response parsers but that's not applicable here since
76// all our parsers are request parsers.
77function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
78                                 url, statusCode, statusMessage, upgrade,
79                                 shouldKeepAlive) {
80  const parser = this;
81  const { socket } = parser;
82
83  if (headers === undefined) {
84    headers = parser._headers;
85    parser._headers = [];
86  }
87
88  if (url === undefined) {
89    url = parser._url;
90    parser._url = '';
91  }
92
93  // Parser is also used by http client
94  const ParserIncomingMessage = (socket && socket.server &&
95                                 socket.server[kIncomingMessage]) ||
96                                 IncomingMessage;
97
98  const incoming = parser.incoming = new ParserIncomingMessage(socket);
99  incoming.httpVersionMajor = versionMajor;
100  incoming.httpVersionMinor = versionMinor;
101  incoming.httpVersion = `${versionMajor}.${versionMinor}`;
102  incoming.url = url;
103  incoming.upgrade = upgrade;
104
105  let n = headers.length;
106
107  // If parser.maxHeaderPairs <= 0 assume that there's no limit.
108  if (parser.maxHeaderPairs > 0)
109    n = MathMin(n, parser.maxHeaderPairs);
110
111  incoming._addHeaderLines(headers, n);
112
113  if (typeof method === 'number') {
114    // server only
115    incoming.method = methods[method];
116  } else {
117    // client only
118    incoming.statusCode = statusCode;
119    incoming.statusMessage = statusMessage;
120  }
121
122  return parser.onIncoming(incoming, shouldKeepAlive);
123}
124
125function parserOnBody(b, start, len) {
126  const stream = this.incoming;
127
128  // If the stream has already been removed, then drop it.
129  if (stream === null)
130    return;
131
132  // Pretend this was the result of a stream._read call.
133  if (len > 0 && !stream._dumped) {
134    const slice = b.slice(start, start + len);
135    const ret = stream.push(slice);
136    if (!ret)
137      readStop(this.socket);
138  }
139}
140
141function parserOnMessageComplete() {
142  const parser = this;
143  const stream = parser.incoming;
144
145  if (stream !== null) {
146    stream.complete = true;
147    // Emit any trailing headers.
148    const headers = parser._headers;
149    if (headers.length) {
150      stream._addHeaderLines(headers, headers.length);
151      parser._headers = [];
152      parser._url = '';
153    }
154
155    // For emit end event
156    stream.push(null);
157  }
158
159  // Force to read the next incoming message
160  readStart(parser.socket);
161}
162
163
164const parsers = new FreeList('parsers', 1000, function parsersCb() {
165  const parser = new HTTPParser();
166
167  cleanParser(parser);
168
169  parser[kOnHeaders] = parserOnHeaders;
170  parser[kOnHeadersComplete] = parserOnHeadersComplete;
171  parser[kOnBody] = parserOnBody;
172  parser[kOnMessageComplete] = parserOnMessageComplete;
173
174  return parser;
175});
176
177function closeParserInstance(parser) { parser.close(); }
178
179// Free the parser and also break any links that it
180// might have to any other things.
181// TODO: All parser data should be attached to a
182// single object, so that it can be easily cleaned
183// up by doing `parser.data = {}`, which should
184// be done in FreeList.free.  `parsers.free(parser)`
185// should be all that is needed.
186function freeParser(parser, req, socket) {
187  if (parser) {
188    if (parser._consumed)
189      parser.unconsume();
190    cleanParser(parser);
191    if (parsers.free(parser) === false) {
192      // Make sure the parser's stack has unwound before deleting the
193      // corresponding C++ object through .close().
194      setImmediate(closeParserInstance, parser);
195    } else {
196      // Since the Parser destructor isn't going to run the destroy() callbacks
197      // it needs to be triggered manually.
198      parser.free();
199    }
200  }
201  if (req) {
202    req.parser = null;
203  }
204  if (socket) {
205    socket.parser = null;
206  }
207}
208
209const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
210/**
211 * Verifies that the given val is a valid HTTP token
212 * per the rules defined in RFC 7230
213 * See https://tools.ietf.org/html/rfc7230#section-3.2.6
214 */
215function checkIsHttpToken(val) {
216  return tokenRegExp.test(val);
217}
218
219const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
220/**
221 * True if val contains an invalid field-vchar
222 *  field-value    = *( field-content / obs-fold )
223 *  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
224 *  field-vchar    = VCHAR / obs-text
225 */
226function checkInvalidHeaderChar(val) {
227  return headerCharRegex.test(val);
228}
229
230function cleanParser(parser) {
231  parser._headers = [];
232  parser._url = '';
233  parser.socket = null;
234  parser.incoming = null;
235  parser.outgoing = null;
236  parser.maxHeaderPairs = MAX_HEADER_PAIRS;
237  parser[kOnExecute] = null;
238  parser[kOnTimeout] = null;
239  parser._consumed = false;
240  parser.onIncoming = null;
241}
242
243function prepareError(err, parser, rawPacket) {
244  err.rawPacket = rawPacket || parser.getCurrentBuffer();
245  if (typeof err.reason === 'string')
246    err.message = `Parse Error: ${err.reason}`;
247}
248
249let warnedLenient = false;
250
251function isLenient() {
252  if (insecureHTTPParser && !warnedLenient) {
253    warnedLenient = true;
254    process.emitWarning('Using insecure HTTP parsing');
255  }
256  return insecureHTTPParser;
257}
258
259module.exports = {
260  _checkInvalidHeaderChar: checkInvalidHeaderChar,
261  _checkIsHttpToken: checkIsHttpToken,
262  chunkExpression: /(?:^|\W)chunked(?:$|\W)/i,
263  continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
264  CRLF: '\r\n',
265  debug,
266  freeParser,
267  methods,
268  parsers,
269  kIncomingMessage,
270  HTTPParser,
271  isLenient,
272  prepareError,
273};
274