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