repo_description: Node.js JavaScript runtime ✨🐢🚀✨
repo_topics: javascript js linux macos mit node nodejs runtime windows
code: // Copyright Joyent, Inc. and other Node contributors.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a
  // copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to permit
  // persons to whom the Software is furnished to do so, subject to the
  // following conditions:
  //
  // The above copyright notice and this permission notice shall be included
  // in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  // USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  'use strict';
  
  const {
    MathMin,
    Symbol,
    Uint8Array,
  } = primordials;
  const { setImmediate } = require('timers');
  
  const { methods, allMethods, HTTPParser } = internalBinding('http_parser');
  const { getOptionValue } = require('internal/options');
  const insecureHTTPParser = getOptionValue('--insecure-http-parser');
  
  const FreeList = require('internal/freelist');
  const incoming = require('_http_incoming');
  const {
    IncomingMessage,
    readStart,
    readStop,
  } = incoming;
  
  const kIncomingMessage = Symbol('IncomingMessage');
  const kSkipPendingData = Symbol('SkipPendingData');
  const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
  const kOnHeaders = HTTPParser.kOnHeaders | 0;
  const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
  const kOnBody = HTTPParser.kOnBody | 0;
  const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
  const kOnExecute = HTTPParser.kOnExecute | 0;
  const kOnTimeout = HTTPParser.kOnTimeout | 0;
  
  const MAX_HEADER_PAIRS = 2000;
  
  // Only called in the slow case where slow means
  // that the request headers were either fragmented
  // across multiple TCP packets or too large to be
  // processed in a single run. This method is also
  // called to process trailing HTTP headers.
  function parserOnHeaders(headers, url) {
    // Once we exceeded headers limit - stop collecting them
    const capacity = this.maxHeaderPairs - this._headers.length;
    if (this.maxHeaderPairs <= 0 || capacity >= headers.length) {
      this._headers.push(...headers);
    } else if (capacity > 0) {
      this._headers.push(...headers.slice(0, capacity));
    }
    this._url += url;
  }
  
  const HTTP_VERSION_1_1 = '1.1';
  
  // `headers` and `url` are set only if .onHeaders() has not been called for
  // this request.
  // `url` is not set for response parsers but that's not applicable here since
  // all our parsers are request parsers.
  function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
                                   url, statusCode, statusMessage, upgrade,
                                   shouldKeepAlive) {
    const parser = this;
    const { socket } = parser;
  
    if (headers === undefined) {
      headers = parser._headers;
      parser._headers = [];
    }
  
    if (url === undefined) {
      url = parser._url;
      parser._url = '';
    }
  
    // Parser is also used by http client
    const ParserIncomingMessage = (socket?.server?.[kIncomingMessage]) ||
                                   IncomingMessage;
  
    const incoming = parser.incoming = new ParserIncomingMessage(socket);
    incoming.httpVersionMajor = versionMajor;
    incoming.httpVersionMinor = versionMinor;
    incoming.httpVersion = versionMajor === 1 && versionMinor === 1 ?
      HTTP_VERSION_1_1 :
      `${versionMajor}.${versionMinor}`;
    incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders ||
                                    parser.joinDuplicateHeaders;
    incoming.url = url;
    incoming.upgrade = upgrade;
  
    let n = headers.length;
  
    // If parser.maxHeaderPairs <= 0 assume that there's no limit.
    if (parser.maxHeaderPairs > 0)
      n = MathMin(n, parser.maxHeaderPairs);
  
    incoming._addHeaderLines(headers, n);
  
    if (typeof method === 'number') {
      // server only
      incoming.method = allMethods[method];
    } else {
      // client only
      incoming.statusCode = statusCode;
      incoming.statusMessage = statusMessage;
    }
  
    return parser.onIncoming(incoming, shouldKeepAlive);
  }
  
  function parserOnBody(b) {
    const stream = this.incoming;
  
    // If the stream has already been removed, then drop it.
    if (stream === null || stream[kSkipPendingData])
      return;
  
    // Pretend this was the result of a stream._read call.
    if (!stream._dumped) {
      const ret = stream.push(b);
      if (!ret)
        readStop(this.socket);
    }
  }
  
  function parserOnMessageComplete() {
    const parser = this;
    const stream = parser.incoming;
  
    if (stream !== null && !stream[kSkipPendingData]) {
      stream.complete = true;
      // Emit any trailing headers.
      const headers = parser._headers;
      if (headers.length) {
        stream._addHeaderLines(headers, headers.length);
        parser._headers = [];
        parser._url = '';
      }
  
      // For emit end event
      stream.push(null);
    }
  
    // Force to read the next incoming message
    readStart(parser.socket);
  }
  
  
  const parsers = new FreeList('parsers', 1000, function parsersCb() {
    const parser = new HTTPParser();
  
    cleanParser(parser);
  
    parser[kOnHeaders] = parserOnHeaders;
    parser[kOnHeadersComplete] = parserOnHeadersComplete;
    parser[kOnBody] = parserOnBody;
    parser[kOnMessageComplete] = parserOnMessageComplete;
  
    return parser;
  });
  
  function closeParserInstance(parser) { parser.close(); }
  
  // Free the parser and also break any links that it
  // might have to any other things.
  // TODO: All parser data should be attached to a
  // single object, so that it can be easily cleaned
  // up by doing `parser.data = {}`, which should
  // be done in FreeList.free.  `parsers.free(parser)`
  // should be all that is needed.
  function freeParser(parser, req, socket) {
    if (parser) {
      if (parser._consumed)
        parser.unconsume();
      parser.remove();
      cleanParser(parser);
      if (parsers.free(parser) === false) {
        // Make sure the parser's stack has unwound before deleting the
        // corresponding C++ object through .close().
        setImmediate(closeParserInstance, parser);
      } else {
        // Since the Parser destructor isn't going to run the destroy() callbacks
        // it needs to be triggered manually.
        parser.free();
      }
    }
    if (req) {
      req.parser = null;
    }
    if (socket) {
      socket.parser = null;
    }
  }
  
  // Character code ranges for valid HTTP tokens
  // Valid chars: ^_`a-zA-Z-0-9!#$%&'*+.|~
  // Based on RFC 7230 Section 3.2.6 token definition
  // See https://tools.ietf.org/html/rfc7230#section-3.2.6
  const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
  const validTokenChars = new Uint8Array([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
    0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32-47 (!"#$%&'()*+,-./)
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48-63 (0-9:;<=>?)
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64-79 (@A-O)
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80-95 (P-Z[\]^_)
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96-111 (`a-o)
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112-127 (p-z{|}~)
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-159
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-175
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-191
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-207
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-223
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-239
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 240-255
  ]);
  
  /**
   * Verifies that the given val is a valid HTTP token
   * per the rules defined in RFC 7230
   * See https://tools.ietf.org/html/rfc7230#section-3.2.6
   * @param {string} val
   * @returns {boolean}
   */
  function checkIsHttpToken(val) {
    if (val.length >= 10) {
      return tokenRegExp.test(val);
    }
  
    if (val.length === 0) return false;
  
    // Use lookup table for short strings, regex for longer ones
    for (let i = 0; i < val.length; i++) {
      if (!validTokenChars[val.charCodeAt(i)]) {
        return false;
      }
    }
    return true;
  }
  
  const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
  /**
   * True if val contains an invalid field-vchar
   *  field-value    = *( field-content / obs-fold )
   *  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
   *  field-vchar    = VCHAR / obs-text
   * @param {string} val
   * @returns {boolean}
   */
  function checkInvalidHeaderChar(val) {
    return headerCharRegex.test(val);
  }
  
  function cleanParser(parser) {
    parser._headers = [];
    parser._url = '';
    parser.socket = null;
    parser.incoming = null;
    parser.outgoing = null;
    parser.maxHeaderPairs = MAX_HEADER_PAIRS;
    parser[kOnMessageBegin] = null;
    parser[kOnExecute] = null;
    parser[kOnTimeout] = null;
    parser._consumed = false;
    parser.onIncoming = null;
    parser.joinDuplicateHeaders = null;
  }
  
  function prepareError(err, parser, rawPacket) {
    err.rawPacket = rawPacket || parser.getCurrentBuffer();
    if (typeof err.reason === 'string')
      err.message = `Parse Error: ${err.reason}`;
  }
  
  let warnedLenient = false;
  
  function isLenient() {
    if (insecureHTTPParser && !warnedLenient) {
      warnedLenient = true;
      process.emitWarning('Using insecure HTTP parsing');
    }
    return insecureHTTPParser;
  }
  
  module.exports = {
    _checkInvalidHeaderChar: checkInvalidHeaderChar,
    _checkIsHttpToken: checkIsHttpToken,
    chunkExpression: /(?:^|\W)chunked(?:$|\W)/i,
    continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
    CRLF: '\r\n', // TODO: Deprecate this.
    freeParser,
    methods,
    parsers,
    kIncomingMessage,
    HTTPParser,
    isLenient,
    prepareError,
    kSkipPendingData,
  };
  