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 {
    ObjectDefineProperty,
    ObjectSetPrototypeOf,
    Symbol,
  } = primordials;
  
  const { Readable, finished } = require('stream');
  
  const { AbortController } = require('internal/abort_controller');
  
  const kHeaders = Symbol('kHeaders');
  const kHeadersDistinct = Symbol('kHeadersDistinct');
  const kHeadersCount = Symbol('kHeadersCount');
  const kTrailers = Symbol('kTrailers');
  const kTrailersDistinct = Symbol('kTrailersDistinct');
  const kTrailersCount = Symbol('kTrailersCount');
  const kAbortController = Symbol('kAbortController');
  
  function readStart(socket) {
    if (socket && !socket._paused && socket.readable)
      socket.resume();
  }
  
  function readStop(socket) {
    if (socket)
      socket.pause();
  }
  
  /* Abstract base class for ServerRequest and ClientResponse. */
  function IncomingMessage(socket) {
    let streamOptions;
  
    if (socket) {
      streamOptions = {
        highWaterMark: socket.readableHighWaterMark,
      };
    }
  
    Readable.call(this, streamOptions);
  
    this._readableState.readingMore = true;
  
    this.socket = socket;
  
    this.httpVersionMajor = null;
    this.httpVersionMinor = null;
    this.httpVersion = null;
    this.complete = false;
    this[kHeaders] = null;
    this[kHeadersCount] = 0;
    this.rawHeaders = [];
    this[kTrailers] = null;
    this[kTrailersCount] = 0;
    this.rawTrailers = [];
    this.joinDuplicateHeaders = false;
    this.aborted = false;
  
    this.upgrade = null;
  
    // request (server) only
    this.url = '';
    this.method = null;
  
    // response (client) only
    this.statusCode = null;
    this.statusMessage = null;
    this.client = socket;
  
    this._consuming = false;
    // Flag for when we decide that this message cannot possibly be
    // read by the user, so there's no point continuing to handle it.
    this._dumped = false;
    this[kAbortController] = null;
  }
  ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype);
  ObjectSetPrototypeOf(IncomingMessage, Readable);
  
  ObjectDefineProperty(IncomingMessage.prototype, 'connection', {
    __proto__: null,
    get: function() {
      return this.socket;
    },
    set: function(val) {
      this.socket = val;
    },
  });
  
  ObjectDefineProperty(IncomingMessage.prototype, 'headers', {
    __proto__: null,
    get: function() {
      if (!this[kHeaders]) {
        this[kHeaders] = {};
  
        const src = this.rawHeaders;
        const dst = this[kHeaders];
  
        for (let n = 0; n < this[kHeadersCount]; n += 2) {
          this._addHeaderLine(src[n + 0], src[n + 1], dst);
        }
      }
      return this[kHeaders];
    },
    set: function(val) {
      this[kHeaders] = val;
    },
  });
  
  ObjectDefineProperty(IncomingMessage.prototype, 'headersDistinct', {
    __proto__: null,
    get: function() {
      if (!this[kHeadersDistinct]) {
        this[kHeadersDistinct] = { __proto__: null };
  
        const src = this.rawHeaders;
        const dst = this[kHeadersDistinct];
  
        for (let n = 0; n < this[kHeadersCount]; n += 2) {
          this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
        }
      }
      return this[kHeadersDistinct];
    },
    set: function(val) {
      this[kHeadersDistinct] = val;
    },
  });
  
  ObjectDefineProperty(IncomingMessage.prototype, 'trailers', {
    __proto__: null,
    get: function() {
      if (!this[kTrailers]) {
        this[kTrailers] = {};
  
        const src = this.rawTrailers;
        const dst = this[kTrailers];
  
        for (let n = 0; n < this[kTrailersCount]; n += 2) {
          this._addHeaderLine(src[n + 0], src[n + 1], dst);
        }
      }
      return this[kTrailers];
    },
    set: function(val) {
      this[kTrailers] = val;
    },
  });
  
  ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', {
    __proto__: null,
    get: function() {
      if (!this[kTrailersDistinct]) {
        this[kTrailersDistinct] = { __proto__: null };
  
        const src = this.rawTrailers;
        const dst = this[kTrailersDistinct];
  
        for (let n = 0; n < this[kTrailersCount]; n += 2) {
          this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
        }
      }
      return this[kTrailersDistinct];
    },
    set: function(val) {
      this[kTrailersDistinct] = val;
    },
  });
  
  ObjectDefineProperty(IncomingMessage.prototype, 'signal', {
    __proto__: null,
    configurable: true,
    get: function() {
      if (this[kAbortController] === null) {
        const ac = new AbortController();
        this[kAbortController] = ac;
        if (this.destroyed) {
          ac.abort();
        } else {
          this.once('close', function() {
            ac.abort();
          });
        }
      }
      return this[kAbortController].signal;
    },
  });
  
  IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
    if (callback)
      this.on('timeout', callback);
    this.socket.setTimeout(msecs);
    return this;
  };
  
  // Argument n cannot be factored out due to the overhead of
  // argument adaptor frame creation inside V8 in case that number of actual
  // arguments is different from expected arguments.
  // Ref: https://bugs.chromium.org/p/v8/issues/detail?id=10201
  // NOTE: Argument adapt frame issue might be solved in V8 engine v8.9.
  // Refactoring `n` out might be possible when V8 is upgraded to that
  // version.
  // Ref: https://v8.dev/blog/v8-release-89
  IncomingMessage.prototype._read = function _read(n) {
    if (!this._consuming) {
      this._readableState.readingMore = false;
      this._consuming = true;
    }
  
    // We actually do almost nothing here, because the parserOnBody
    // function fills up our internal buffer directly.  However, we
    // do need to unpause the underlying socket so that it flows.
    if (this.socket.readable)
      readStart(this.socket);
  };
  
  // It's possible that the socket will be destroyed, and removed from
  // any messages, before ever calling this.  In that case, just skip
  // it, since something else is destroying this connection anyway.
  IncomingMessage.prototype._destroy = function _destroy(err, cb) {
    if (!this.readableEnded || !this.complete) {
      this.aborted = true;
      this.emit('aborted');
    }
  
    // If aborted and the underlying socket is not already destroyed,
    // destroy it.
    // We have to check if the socket is already destroyed because finished
    // does not call the callback when this method is invoked from `_http_client`
    // in `test/parallel/test-http-client-spurious-aborted.js`
    if (this.socket && !this.socket.destroyed && this.aborted) {
      this.socket.destroy(err);
      const cleanup = finished(this.socket, (e) => {
        if (e?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
          e = null;
        }
        cleanup();
        process.nextTick(onError, this, e || err, cb);
      });
    } else {
      process.nextTick(onError, this, err, cb);
    }
  };
  
  IncomingMessage.prototype._addHeaderLines = _addHeaderLines;
  function _addHeaderLines(headers, n) {
    if (headers?.length) {
      let dest;
      if (this.complete) {
        this.rawTrailers = headers;
        this[kTrailersCount] = n;
        dest = this[kTrailers];
      } else {
        this.rawHeaders = headers;
        this[kHeadersCount] = n;
        dest = this[kHeaders];
      }
  
      if (dest) {
        for (let i = 0; i < n; i += 2) {
          this._addHeaderLine(headers[i], headers[i + 1], dest);
        }
      }
    }
  }
  
  
  // This function is used to help avoid the lowercasing of a field name if it
  // matches a 'traditional cased' version of a field name. It then returns the
  // lowercased name to both avoid calling toLowerCase() a second time and to
  // indicate whether the field was a 'no duplicates' field. If a field is not a
  // 'no duplicates' field, a `0` byte is prepended as a flag. The one exception
  // to this is the Set-Cookie header which is indicated by a `1` byte flag, since
  // it is an 'array' field and thus is treated differently in _addHeaderLines().
  // TODO: perhaps http_parser could be returning both raw and lowercased versions
  // of known header names to avoid us having to call toLowerCase() for those
  // headers.
  function matchKnownFields(field, lowercased) {
    switch (field.length) {
      case 3:
        if (field === 'Age' || field === 'age') return 'age';
        break;
      case 4:
        if (field === 'Host' || field === 'host') return 'host';
        if (field === 'From' || field === 'from') return 'from';
        if (field === 'ETag' || field === 'etag') return 'etag';
        if (field === 'Date' || field === 'date') return '\u0000date';
        if (field === 'Vary' || field === 'vary') return '\u0000vary';
        break;
      case 6:
        if (field === 'Server' || field === 'server') return 'server';
        if (field === 'Cookie' || field === 'cookie') return '\u0002cookie';
        if (field === 'Origin' || field === 'origin') return '\u0000origin';
        if (field === 'Expect' || field === 'expect') return '\u0000expect';
        if (field === 'Accept' || field === 'accept') return '\u0000accept';
        break;
      case 7:
        if (field === 'Referer' || field === 'referer') return 'referer';
        if (field === 'Expires' || field === 'expires') return 'expires';
        if (field === 'Upgrade' || field === 'upgrade') return '\u0000upgrade';
        break;
      case 8:
        if (field === 'Location' || field === 'location')
          return 'location';
        if (field === 'If-Match' || field === 'if-match')
          return '\u0000if-match';
        break;
      case 10:
        if (field === 'User-Agent' || field === 'user-agent')
          return 'user-agent';
        if (field === 'Set-Cookie' || field === 'set-cookie')
          return '\u0001';
        if (field === 'Connection' || field === 'connection')
          return '\u0000connection';
        break;
      case 11:
        if (field === 'Retry-After' || field === 'retry-after')
          return 'retry-after';
        break;
      case 12:
        if (field === 'Content-Type' || field === 'content-type')
          return 'content-type';
        if (field === 'Max-Forwards' || field === 'max-forwards')
          return 'max-forwards';
        break;
      case 13:
        if (field === 'Authorization' || field === 'authorization')
          return 'authorization';
        if (field === 'Last-Modified' || field === 'last-modified')
          return 'last-modified';
        if (field === 'Cache-Control' || field === 'cache-control')
          return '\u0000cache-control';
        if (field === 'If-None-Match' || field === 'if-none-match')
          return '\u0000if-none-match';
        break;
      case 14:
        if (field === 'Content-Length' || field === 'content-length')
          return 'content-length';
        break;
      case 15:
        if (field === 'Accept-Encoding' || field === 'accept-encoding')
          return '\u0000accept-encoding';
        if (field === 'Accept-Language' || field === 'accept-language')
          return '\u0000accept-language';
        if (field === 'X-Forwarded-For' || field === 'x-forwarded-for')
          return '\u0000x-forwarded-for';
        break;
      case 16:
        if (field === 'Content-Encoding' || field === 'content-encoding')
          return '\u0000content-encoding';
        if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host')
          return '\u0000x-forwarded-host';
        break;
      case 17:
        if (field === 'If-Modified-Since' || field === 'if-modified-since')
          return 'if-modified-since';
        if (field === 'Transfer-Encoding' || field === 'transfer-encoding')
          return '\u0000transfer-encoding';
        if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto')
          return '\u0000x-forwarded-proto';
        break;
      case 19:
        if (field === 'Proxy-Authorization' || field === 'proxy-authorization')
          return 'proxy-authorization';
        if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since')
          return 'if-unmodified-since';
        break;
    }
    if (lowercased) {
      return '\u0000' + field;
    }
    return matchKnownFields(field.toLowerCase(), true);
  }
  // Add the given (field, value) pair to the message
  //
  // Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
  // same header with a ', ' if the header in question supports specification of
  // multiple values this way. The one exception to this is the Cookie header,
  // which has multiple values joined with a '; ' instead. If a header's values
  // cannot be joined in either of these ways, we declare the first instance the
  // winner and drop the second. Extended header fields (those beginning with
  // 'x-') are always joined.
  IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
  function _addHeaderLine(field, value, dest) {
    field = matchKnownFields(field);
    const flag = field.charCodeAt(0);
    if (flag === 0 || flag === 2) {
      field = field.slice(1);
      // Make a delimited list
      if (typeof dest[field] === 'string') {
        dest[field] += (flag === 0 ? ', ' : '; ') + value;
      } else {
        dest[field] = value;
      }
    } else if (flag === 1) {
      // Array header -- only Set-Cookie at the moment
      if (dest['set-cookie'] !== undefined) {
        dest['set-cookie'].push(value);
      } else {
        dest['set-cookie'] = [value];
      }
    } else if (this.joinDuplicateHeaders) {
      // RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2
      // https://github.com/nodejs/node/issues/45699
      // allow authorization multiple fields
      // Make a delimited list
      if (dest[field] === undefined) {
        dest[field] = value;
      } else {
        dest[field] += ', ' + value;
      }
    } else if (dest[field] === undefined) {
      // Drop duplicates
      dest[field] = value;
    }
  }
  
  IncomingMessage.prototype._addHeaderLineDistinct = _addHeaderLineDistinct;
  function _addHeaderLineDistinct(field, value, dest) {
    field = field.toLowerCase();
    if (!dest[field]) {
      dest[field] = [value];
    } else {
      dest[field].push(value);
    }
  }
  
  IncomingMessage.prototype._dumpAndCloseReadable = function _dumpAndCloseReadable() {
    this._dumped = true;
    this._readableState.ended = true;
    this._readableState.endEmitted = true;
    this._readableState.destroyed = true;
    this._readableState.closed = true;
    this._readableState.closeEmitted = true;
  };
  
  
  // Call this instead of resume() if we want to just
  // dump all the data to /dev/null
  IncomingMessage.prototype._dump = function _dump() {
    if (!this._dumped) {
      this._dumped = true;
      // If there is buffered data, it may trigger 'data' events.
      // Remove 'data' event listeners explicitly.
      this.removeAllListeners('data');
      this.resume();
    }
  };
  
  function onError(self, error, cb) {
    // This is to keep backward compatible behavior.
    // An error is emitted only if there are listeners attached to the event.
    if (self.listenerCount('error') === 0) {
      cb();
    } else {
      cb(error);
    }
  }
  
  module.exports = {
    IncomingMessage,
    readStart,
    readStop,
  };
  