• 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 { methods, HTTPParser } = internalBinding('http_parser');
31const { getOptionValue } = require('internal/options');
32const insecureHTTPParser = getOptionValue('--insecure-http-parser');
33
34const FreeList = require('internal/freelist');
35const incoming = require('_http_incoming');
36const {
37  IncomingMessage,
38  readStart,
39  readStop
40} = incoming;
41
42let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
43  debug = fn;
44});
45
46const kIncomingMessage = Symbol('IncomingMessage');
47const kRequestTimeout = Symbol('RequestTimeout');
48const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
49const kOnHeaders = HTTPParser.kOnHeaders | 0;
50const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
51const kOnBody = HTTPParser.kOnBody | 0;
52const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
53const kOnExecute = HTTPParser.kOnExecute | 0;
54const kOnTimeout = HTTPParser.kOnTimeout | 0;
55
56const MAX_HEADER_PAIRS = 2000;
57
58// Only called in the slow case where slow means
59// that the request headers were either fragmented
60// across multiple TCP packets or too large to be
61// processed in a single run. This method is also
62// called to process trailing HTTP headers.
63function parserOnHeaders(headers, url) {
64  // Once we exceeded headers limit - stop collecting them
65  if (this.maxHeaderPairs <= 0 ||
66      this._headers.length < this.maxHeaderPairs) {
67    this._headers = this._headers.concat(headers);
68  }
69  this._url += url;
70}
71
72// `headers` and `url` are set only if .onHeaders() has not been called for
73// this request.
74// `url` is not set for response parsers but that's not applicable here since
75// all our parsers are request parsers.
76function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
77                                 url, statusCode, statusMessage, upgrade,
78                                 shouldKeepAlive) {
79  const parser = this;
80  const { socket } = parser;
81
82  if (headers === undefined) {
83    headers = parser._headers;
84    parser._headers = [];
85  }
86
87  if (url === undefined) {
88    url = parser._url;
89    parser._url = '';
90  }
91
92  // Parser is also used by http client
93  const ParserIncomingMessage = (socket && socket.server &&
94                                 socket.server[kIncomingMessage]) ||
95                                 IncomingMessage;
96
97  const incoming = parser.incoming = new ParserIncomingMessage(socket);
98  incoming.httpVersionMajor = versionMajor;
99  incoming.httpVersionMinor = versionMinor;
100  incoming.httpVersion = `${versionMajor}.${versionMinor}`;
101  incoming.url = url;
102  incoming.upgrade = upgrade;
103
104  if (socket) {
105    debug('requestTimeout timer moved to req');
106    incoming[kRequestTimeout] = incoming.socket[kRequestTimeout];
107    incoming.socket[kRequestTimeout] = undefined;
108  }
109
110  let n = headers.length;
111
112  // If parser.maxHeaderPairs <= 0 assume that there's no limit.
113  if (parser.maxHeaderPairs > 0)
114    n = MathMin(n, parser.maxHeaderPairs);
115
116  incoming._addHeaderLines(headers, n);
117
118  if (typeof method === 'number') {
119    // server only
120    incoming.method = methods[method];
121  } else {
122    // client only
123    incoming.statusCode = statusCode;
124    incoming.statusMessage = statusMessage;
125  }
126
127  return parser.onIncoming(incoming, shouldKeepAlive);
128}
129
130function parserOnBody(b, start, len) {
131  const stream = this.incoming;
132
133  // If the stream has already been removed, then drop it.
134  if (stream === null)
135    return;
136
137  // Pretend this was the result of a stream._read call.
138  if (len > 0 && !stream._dumped) {
139    const slice = b.slice(start, start + len);
140    const ret = stream.push(slice);
141    if (!ret)
142      readStop(this.socket);
143  }
144}
145
146function parserOnMessageComplete() {
147  const parser = this;
148  const stream = parser.incoming;
149
150  if (stream !== null) {
151    stream.complete = true;
152    // Emit any trailing headers.
153    const headers = parser._headers;
154    if (headers.length) {
155      stream._addHeaderLines(headers, headers.length);
156      parser._headers = [];
157      parser._url = '';
158    }
159
160    // For emit end event
161    stream.push(null);
162  }
163
164  // Force to read the next incoming message
165  readStart(parser.socket);
166}
167
168
169const parsers = new FreeList('parsers', 1000, function parsersCb() {
170  const parser = new HTTPParser();
171
172  cleanParser(parser);
173
174  parser[kOnHeaders] = parserOnHeaders;
175  parser[kOnHeadersComplete] = parserOnHeadersComplete;
176  parser[kOnBody] = parserOnBody;
177  parser[kOnMessageComplete] = parserOnMessageComplete;
178
179  return parser;
180});
181
182function closeParserInstance(parser) { parser.close(); }
183
184// Free the parser and also break any links that it
185// might have to any other things.
186// TODO: All parser data should be attached to a
187// single object, so that it can be easily cleaned
188// up by doing `parser.data = {}`, which should
189// be done in FreeList.free.  `parsers.free(parser)`
190// should be all that is needed.
191function freeParser(parser, req, socket) {
192  if (parser) {
193    if (parser._consumed)
194      parser.unconsume();
195    cleanParser(parser);
196    if (parsers.free(parser) === false) {
197      // Make sure the parser's stack has unwound before deleting the
198      // corresponding C++ object through .close().
199      setImmediate(closeParserInstance, parser);
200    } else {
201      // Since the Parser destructor isn't going to run the destroy() callbacks
202      // it needs to be triggered manually.
203      parser.free();
204    }
205  }
206  if (req) {
207    req.parser = null;
208  }
209  if (socket) {
210    socket.parser = null;
211  }
212}
213
214const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
215/**
216 * Verifies that the given val is a valid HTTP token
217 * per the rules defined in RFC 7230
218 * See https://tools.ietf.org/html/rfc7230#section-3.2.6
219 */
220function checkIsHttpToken(val) {
221  return tokenRegExp.test(val);
222}
223
224const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
225/**
226 * True if val contains an invalid field-vchar
227 *  field-value    = *( field-content / obs-fold )
228 *  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
229 *  field-vchar    = VCHAR / obs-text
230 */
231function checkInvalidHeaderChar(val) {
232  return headerCharRegex.test(val);
233}
234
235function cleanParser(parser) {
236  parser._headers = [];
237  parser._url = '';
238  parser.socket = null;
239  parser.incoming = null;
240  parser.outgoing = null;
241  parser.maxHeaderPairs = MAX_HEADER_PAIRS;
242  parser[kOnMessageBegin] = null;
243  parser[kOnExecute] = null;
244  parser[kOnTimeout] = null;
245  parser._consumed = false;
246  parser.onIncoming = null;
247}
248
249function prepareError(err, parser, rawPacket) {
250  err.rawPacket = rawPacket || parser.getCurrentBuffer();
251  if (typeof err.reason === 'string')
252    err.message = `Parse Error: ${err.reason}`;
253}
254
255let warnedLenient = false;
256
257function isLenient() {
258  if (insecureHTTPParser && !warnedLenient) {
259    warnedLenient = true;
260    process.emitWarning('Using insecure HTTP parsing');
261  }
262  return insecureHTTPParser;
263}
264
265module.exports = {
266  _checkInvalidHeaderChar: checkInvalidHeaderChar,
267  _checkIsHttpToken: checkIsHttpToken,
268  chunkExpression: /(?:^|\W)chunked(?:$|\W)/i,
269  continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
270  CRLF: '\r\n',
271  debug,
272  freeParser,
273  methods,
274  parsers,
275  kIncomingMessage,
276  kRequestTimeout,
277  HTTPParser,
278  isLenient,
279  prepareError,
280};
281