• 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  ObjectDefineProperty,
26  ObjectSetPrototypeOf,
27} = primordials;
28
29const Stream = require('stream');
30
31function readStart(socket) {
32  if (socket && !socket._paused && socket.readable)
33    socket.resume();
34}
35
36function readStop(socket) {
37  if (socket)
38    socket.pause();
39}
40
41/* Abstract base class for ServerRequest and ClientResponse. */
42function IncomingMessage(socket) {
43  let streamOptions;
44
45  if (socket) {
46    streamOptions = {
47      highWaterMark: socket.readableHighWaterMark
48    };
49  }
50
51  Stream.Readable.call(this, { autoDestroy: false, ...streamOptions });
52
53  this._readableState.readingMore = true;
54
55  this.socket = socket;
56
57  this.httpVersionMajor = null;
58  this.httpVersionMinor = null;
59  this.httpVersion = null;
60  this.complete = false;
61  this.headers = {};
62  this.rawHeaders = [];
63  this.trailers = {};
64  this.rawTrailers = [];
65
66  this.aborted = false;
67
68  this.upgrade = null;
69
70  // request (server) only
71  this.url = '';
72  this.method = null;
73
74  // response (client) only
75  this.statusCode = null;
76  this.statusMessage = null;
77  this.client = socket;
78
79  this._consuming = false;
80  // Flag for when we decide that this message cannot possibly be
81  // read by the user, so there's no point continuing to handle it.
82  this._dumped = false;
83}
84ObjectSetPrototypeOf(IncomingMessage.prototype, Stream.Readable.prototype);
85ObjectSetPrototypeOf(IncomingMessage, Stream.Readable);
86
87ObjectDefineProperty(IncomingMessage.prototype, 'connection', {
88  get: function() {
89    return this.socket;
90  },
91  set: function(val) {
92    this.socket = val;
93  }
94});
95
96IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
97  if (callback)
98    this.on('timeout', callback);
99  this.socket.setTimeout(msecs);
100  return this;
101};
102
103// Argument n cannot be factored out due to the overhead of
104// argument adaptor frame creation inside V8 in case that number of actual
105// arguments is different from expected arguments.
106// Ref: https://bugs.chromium.org/p/v8/issues/detail?id=10201
107// NOTE: Argument adapt frame issue might be solved in V8 engine v8.9.
108// Refactoring `n` out might be possible when V8 is upgraded to that
109// version.
110// Ref: https://v8.dev/blog/v8-release-89
111IncomingMessage.prototype._read = function _read(n) {
112  if (!this._consuming) {
113    this._readableState.readingMore = false;
114    this._consuming = true;
115  }
116
117  // We actually do almost nothing here, because the parserOnBody
118  // function fills up our internal buffer directly.  However, we
119  // do need to unpause the underlying socket so that it flows.
120  if (this.socket.readable)
121    readStart(this.socket);
122};
123
124
125// It's possible that the socket will be destroyed, and removed from
126// any messages, before ever calling this.  In that case, just skip
127// it, since something else is destroying this connection anyway.
128IncomingMessage.prototype.destroy = function destroy(error) {
129  if (this.socket)
130    this.socket.destroy(error);
131  return this;
132};
133
134
135IncomingMessage.prototype._addHeaderLines = _addHeaderLines;
136function _addHeaderLines(headers, n) {
137  if (headers && headers.length) {
138    let dest;
139    if (this.complete) {
140      this.rawTrailers = headers;
141      dest = this.trailers;
142    } else {
143      this.rawHeaders = headers;
144      dest = this.headers;
145    }
146
147    for (let i = 0; i < n; i += 2) {
148      this._addHeaderLine(headers[i], headers[i + 1], dest);
149    }
150  }
151}
152
153
154// This function is used to help avoid the lowercasing of a field name if it
155// matches a 'traditional cased' version of a field name. It then returns the
156// lowercased name to both avoid calling toLowerCase() a second time and to
157// indicate whether the field was a 'no duplicates' field. If a field is not a
158// 'no duplicates' field, a `0` byte is prepended as a flag. The one exception
159// to this is the Set-Cookie header which is indicated by a `1` byte flag, since
160// it is an 'array' field and thus is treated differently in _addHeaderLines().
161// TODO: perhaps http_parser could be returning both raw and lowercased versions
162// of known header names to avoid us having to call toLowerCase() for those
163// headers.
164function matchKnownFields(field, lowercased) {
165  switch (field.length) {
166    case 3:
167      if (field === 'Age' || field === 'age') return 'age';
168      break;
169    case 4:
170      if (field === 'Host' || field === 'host') return 'host';
171      if (field === 'From' || field === 'from') return 'from';
172      if (field === 'ETag' || field === 'etag') return 'etag';
173      if (field === 'Date' || field === 'date') return '\u0000date';
174      if (field === 'Vary' || field === 'vary') return '\u0000vary';
175      break;
176    case 6:
177      if (field === 'Server' || field === 'server') return 'server';
178      if (field === 'Cookie' || field === 'cookie') return '\u0002cookie';
179      if (field === 'Origin' || field === 'origin') return '\u0000origin';
180      if (field === 'Expect' || field === 'expect') return '\u0000expect';
181      if (field === 'Accept' || field === 'accept') return '\u0000accept';
182      break;
183    case 7:
184      if (field === 'Referer' || field === 'referer') return 'referer';
185      if (field === 'Expires' || field === 'expires') return 'expires';
186      if (field === 'Upgrade' || field === 'upgrade') return '\u0000upgrade';
187      break;
188    case 8:
189      if (field === 'Location' || field === 'location')
190        return 'location';
191      if (field === 'If-Match' || field === 'if-match')
192        return '\u0000if-match';
193      break;
194    case 10:
195      if (field === 'User-Agent' || field === 'user-agent')
196        return 'user-agent';
197      if (field === 'Set-Cookie' || field === 'set-cookie')
198        return '\u0001';
199      if (field === 'Connection' || field === 'connection')
200        return '\u0000connection';
201      break;
202    case 11:
203      if (field === 'Retry-After' || field === 'retry-after')
204        return 'retry-after';
205      break;
206    case 12:
207      if (field === 'Content-Type' || field === 'content-type')
208        return 'content-type';
209      if (field === 'Max-Forwards' || field === 'max-forwards')
210        return 'max-forwards';
211      break;
212    case 13:
213      if (field === 'Authorization' || field === 'authorization')
214        return 'authorization';
215      if (field === 'Last-Modified' || field === 'last-modified')
216        return 'last-modified';
217      if (field === 'Cache-Control' || field === 'cache-control')
218        return '\u0000cache-control';
219      if (field === 'If-None-Match' || field === 'if-none-match')
220        return '\u0000if-none-match';
221      break;
222    case 14:
223      if (field === 'Content-Length' || field === 'content-length')
224        return 'content-length';
225      break;
226    case 15:
227      if (field === 'Accept-Encoding' || field === 'accept-encoding')
228        return '\u0000accept-encoding';
229      if (field === 'Accept-Language' || field === 'accept-language')
230        return '\u0000accept-language';
231      if (field === 'X-Forwarded-For' || field === 'x-forwarded-for')
232        return '\u0000x-forwarded-for';
233      break;
234    case 16:
235      if (field === 'Content-Encoding' || field === 'content-encoding')
236        return '\u0000content-encoding';
237      if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host')
238        return '\u0000x-forwarded-host';
239      break;
240    case 17:
241      if (field === 'If-Modified-Since' || field === 'if-modified-since')
242        return 'if-modified-since';
243      if (field === 'Transfer-Encoding' || field === 'transfer-encoding')
244        return '\u0000transfer-encoding';
245      if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto')
246        return '\u0000x-forwarded-proto';
247      break;
248    case 19:
249      if (field === 'Proxy-Authorization' || field === 'proxy-authorization')
250        return 'proxy-authorization';
251      if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since')
252        return 'if-unmodified-since';
253      break;
254  }
255  if (lowercased) {
256    return '\u0000' + field;
257  }
258  return matchKnownFields(field.toLowerCase(), true);
259}
260// Add the given (field, value) pair to the message
261//
262// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
263// same header with a ', ' if the header in question supports specification of
264// multiple values this way. The one exception to this is the Cookie header,
265// which has multiple values joined with a '; ' instead. If a header's values
266// cannot be joined in either of these ways, we declare the first instance the
267// winner and drop the second. Extended header fields (those beginning with
268// 'x-') are always joined.
269IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
270function _addHeaderLine(field, value, dest) {
271  field = matchKnownFields(field);
272  const flag = field.charCodeAt(0);
273  if (flag === 0 || flag === 2) {
274    field = field.slice(1);
275    // Make a delimited list
276    if (typeof dest[field] === 'string') {
277      dest[field] += (flag === 0 ? ', ' : '; ') + value;
278    } else {
279      dest[field] = value;
280    }
281  } else if (flag === 1) {
282    // Array header -- only Set-Cookie at the moment
283    if (dest['set-cookie'] !== undefined) {
284      dest['set-cookie'].push(value);
285    } else {
286      dest['set-cookie'] = [value];
287    }
288  } else if (dest[field] === undefined) {
289    // Drop duplicates
290    dest[field] = value;
291  }
292}
293
294
295// Call this instead of resume() if we want to just
296// dump all the data to /dev/null
297IncomingMessage.prototype._dump = function _dump() {
298  if (!this._dumped) {
299    this._dumped = true;
300    // If there is buffered data, it may trigger 'data' events.
301    // Remove 'data' event listeners explicitly.
302    this.removeAllListeners('data');
303    this.resume();
304  }
305};
306
307module.exports = {
308  IncomingMessage,
309  readStart,
310  readStop
311};
312