• 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  Array,
26  ArrayIsArray,
27  ArrayPrototypeJoin,
28  MathFloor,
29  NumberPrototypeToString,
30  ObjectCreate,
31  ObjectDefineProperty,
32  ObjectKeys,
33  ObjectValues,
34  ObjectPrototypeHasOwnProperty,
35  ObjectSetPrototypeOf,
36  RegExpPrototypeExec,
37  SafeSet,
38  StringPrototypeToLowerCase,
39  Symbol,
40} = primordials;
41
42const { getDefaultHighWaterMark } = require('internal/streams/state');
43const assert = require('internal/assert');
44const EE = require('events');
45const Stream = require('stream');
46const internalUtil = require('internal/util');
47const { kOutHeaders, utcDate, kNeedDrain } = require('internal/http');
48const { Buffer } = require('buffer');
49const {
50  _checkIsHttpToken: checkIsHttpToken,
51  _checkInvalidHeaderChar: checkInvalidHeaderChar,
52  chunkExpression: RE_TE_CHUNKED,
53} = require('_http_common');
54const {
55  defaultTriggerAsyncIdScope,
56  symbols: { async_id_symbol },
57} = require('internal/async_hooks');
58const {
59  codes: {
60    ERR_HTTP_CONTENT_LENGTH_MISMATCH,
61    ERR_HTTP_HEADERS_SENT,
62    ERR_HTTP_INVALID_HEADER_VALUE,
63    ERR_HTTP_TRAILER_INVALID,
64    ERR_HTTP_BODY_NOT_ALLOWED,
65    ERR_INVALID_HTTP_TOKEN,
66    ERR_INVALID_ARG_TYPE,
67    ERR_INVALID_ARG_VALUE,
68    ERR_INVALID_CHAR,
69    ERR_METHOD_NOT_IMPLEMENTED,
70    ERR_STREAM_CANNOT_PIPE,
71    ERR_STREAM_ALREADY_FINISHED,
72    ERR_STREAM_WRITE_AFTER_END,
73    ERR_STREAM_NULL_VALUES,
74    ERR_STREAM_DESTROYED,
75  },
76  hideStackFrames,
77} = require('internal/errors');
78const { validateString } = require('internal/validators');
79const { isUint8Array } = require('internal/util/types');
80
81let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
82  debug = fn;
83});
84
85const kCorked = Symbol('corked');
86const kUniqueHeaders = Symbol('kUniqueHeaders');
87const kBytesWritten = Symbol('kBytesWritten');
88const kErrored = Symbol('errored');
89const kHighWaterMark = Symbol('kHighWaterMark');
90const kRejectNonStandardBodyWrites = Symbol('kRejectNonStandardBodyWrites');
91
92const nop = () => {};
93
94const RE_CONN_CLOSE = /(?:^|\W)close(?:$|\W)/i;
95
96// isCookieField performs a case-insensitive comparison of a provided string
97// against the word "cookie." As of V8 6.6 this is faster than handrolling or
98// using a case-insensitive RegExp.
99function isCookieField(s) {
100  return s.length === 6 && StringPrototypeToLowerCase(s) === 'cookie';
101}
102
103function isContentDispositionField(s) {
104  return s.length === 19 && StringPrototypeToLowerCase(s) === 'content-disposition';
105}
106
107function OutgoingMessage(options) {
108  Stream.call(this);
109
110  // Queue that holds all currently pending data, until the response will be
111  // assigned to the socket (until it will its turn in the HTTP pipeline).
112  this.outputData = [];
113
114  // `outputSize` is an approximate measure of how much data is queued on this
115  // response. `_onPendingData` will be invoked to update similar global
116  // per-connection counter. That counter will be used to pause/unpause the
117  // TCP socket and HTTP Parser and thus handle the backpressure.
118  this.outputSize = 0;
119
120  this.writable = true;
121  this.destroyed = false;
122
123  this._last = false;
124  this.chunkedEncoding = false;
125  this.shouldKeepAlive = true;
126  this.maxRequestsOnConnectionReached = false;
127  this._defaultKeepAlive = true;
128  this.useChunkedEncodingByDefault = true;
129  this.sendDate = false;
130  this._removedConnection = false;
131  this._removedContLen = false;
132  this._removedTE = false;
133
134  this.strictContentLength = false;
135  this[kBytesWritten] = 0;
136  this._contentLength = null;
137  this._hasBody = true;
138  this._trailer = '';
139  this[kNeedDrain] = false;
140
141  this.finished = false;
142  this._headerSent = false;
143  this[kCorked] = 0;
144  this._closed = false;
145
146  this.socket = null;
147  this._header = null;
148  this[kOutHeaders] = null;
149
150  this._keepAliveTimeout = 0;
151
152  this._onPendingData = nop;
153
154  this[kErrored] = null;
155  this[kHighWaterMark] = options?.highWaterMark ?? getDefaultHighWaterMark();
156  this[kRejectNonStandardBodyWrites] = options?.rejectNonStandardBodyWrites ?? false;
157}
158ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype);
159ObjectSetPrototypeOf(OutgoingMessage, Stream);
160
161ObjectDefineProperty(OutgoingMessage.prototype, 'errored', {
162  __proto__: null,
163  get() {
164    return this[kErrored];
165  },
166});
167
168ObjectDefineProperty(OutgoingMessage.prototype, 'closed', {
169  __proto__: null,
170  get() {
171    return this._closed;
172  },
173});
174
175ObjectDefineProperty(OutgoingMessage.prototype, 'writableFinished', {
176  __proto__: null,
177  get() {
178    return (
179      this.finished &&
180      this.outputSize === 0 &&
181      (!this.socket || this.socket.writableLength === 0)
182    );
183  },
184});
185
186ObjectDefineProperty(OutgoingMessage.prototype, 'writableObjectMode', {
187  __proto__: null,
188  get() {
189    return false;
190  },
191});
192
193ObjectDefineProperty(OutgoingMessage.prototype, 'writableLength', {
194  __proto__: null,
195  get() {
196    return this.outputSize + (this.socket ? this.socket.writableLength : 0);
197  },
198});
199
200ObjectDefineProperty(OutgoingMessage.prototype, 'writableHighWaterMark', {
201  __proto__: null,
202  get() {
203    return this.socket ? this.socket.writableHighWaterMark : this[kHighWaterMark];
204  },
205});
206
207ObjectDefineProperty(OutgoingMessage.prototype, 'writableCorked', {
208  __proto__: null,
209  get() {
210    const corked = this.socket ? this.socket.writableCorked : 0;
211    return corked + this[kCorked];
212  },
213});
214
215ObjectDefineProperty(OutgoingMessage.prototype, '_headers', {
216  __proto__: null,
217  get: internalUtil.deprecate(function() {
218    return this.getHeaders();
219  }, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066'),
220  set: internalUtil.deprecate(function(val) {
221    if (val == null) {
222      this[kOutHeaders] = null;
223    } else if (typeof val === 'object') {
224      const headers = this[kOutHeaders] = ObjectCreate(null);
225      const keys = ObjectKeys(val);
226      // Retain for(;;) loop for performance reasons
227      // Refs: https://github.com/nodejs/node/pull/30958
228      for (let i = 0; i < keys.length; ++i) {
229        const name = keys[i];
230        headers[StringPrototypeToLowerCase(name)] = [name, val[name]];
231      }
232    }
233  }, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066'),
234});
235
236ObjectDefineProperty(OutgoingMessage.prototype, 'connection', {
237  __proto__: null,
238  get: function() {
239    return this.socket;
240  },
241  set: function(val) {
242    this.socket = val;
243  },
244});
245
246ObjectDefineProperty(OutgoingMessage.prototype, '_headerNames', {
247  __proto__: null,
248  get: internalUtil.deprecate(function() {
249    const headers = this[kOutHeaders];
250    if (headers !== null) {
251      const out = ObjectCreate(null);
252      const keys = ObjectKeys(headers);
253      // Retain for(;;) loop for performance reasons
254      // Refs: https://github.com/nodejs/node/pull/30958
255      for (let i = 0; i < keys.length; ++i) {
256        const key = keys[i];
257        const val = headers[key][0];
258        out[key] = val;
259      }
260      return out;
261    }
262    return null;
263  }, 'OutgoingMessage.prototype._headerNames is deprecated', 'DEP0066'),
264  set: internalUtil.deprecate(function(val) {
265    if (typeof val === 'object' && val !== null) {
266      const headers = this[kOutHeaders];
267      if (!headers)
268        return;
269      const keys = ObjectKeys(val);
270      // Retain for(;;) loop for performance reasons
271      // Refs: https://github.com/nodejs/node/pull/30958
272      for (let i = 0; i < keys.length; ++i) {
273        const header = headers[keys[i]];
274        if (header)
275          header[0] = val[keys[i]];
276      }
277    }
278  }, 'OutgoingMessage.prototype._headerNames is deprecated', 'DEP0066'),
279});
280
281
282OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
283  if (this._header) {
284    throw new ERR_HTTP_HEADERS_SENT('render');
285  }
286
287  const headersMap = this[kOutHeaders];
288  const headers = {};
289
290  if (headersMap !== null) {
291    const keys = ObjectKeys(headersMap);
292    // Retain for(;;) loop for performance reasons
293    // Refs: https://github.com/nodejs/node/pull/30958
294    for (let i = 0, l = keys.length; i < l; i++) {
295      const key = keys[i];
296      headers[headersMap[key][0]] = headersMap[key][1];
297    }
298  }
299  return headers;
300};
301
302OutgoingMessage.prototype.cork = function() {
303  if (this.socket) {
304    this.socket.cork();
305  } else {
306    this[kCorked]++;
307  }
308};
309
310OutgoingMessage.prototype.uncork = function() {
311  if (this.socket) {
312    this.socket.uncork();
313  } else if (this[kCorked]) {
314    this[kCorked]--;
315  }
316};
317
318OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
319
320  if (callback) {
321    this.on('timeout', callback);
322  }
323
324  if (!this.socket) {
325    this.once('socket', function socketSetTimeoutOnConnect(socket) {
326      socket.setTimeout(msecs);
327    });
328  } else {
329    this.socket.setTimeout(msecs);
330  }
331  return this;
332};
333
334
335// It's possible that the socket will be destroyed, and removed from
336// any messages, before ever calling this.  In that case, just skip
337// it, since something else is destroying this connection anyway.
338OutgoingMessage.prototype.destroy = function destroy(error) {
339  if (this.destroyed) {
340    return this;
341  }
342  this.destroyed = true;
343
344  this[kErrored] = error;
345
346  if (this.socket) {
347    this.socket.destroy(error);
348  } else {
349    this.once('socket', function socketDestroyOnConnect(socket) {
350      socket.destroy(error);
351    });
352  }
353
354  return this;
355};
356
357
358// This abstract either writing directly to the socket or buffering it.
359OutgoingMessage.prototype._send = function _send(data, encoding, callback, byteLength) {
360  // This is a shameful hack to get the headers and first body chunk onto
361  // the same packet. Future versions of Node are going to take care of
362  // this at a lower level and in a more general way.
363  if (!this._headerSent && this._header !== null) {
364    // `this._header` can be null if OutgoingMessage is used without a proper Socket
365    // See: /test/parallel/test-http-outgoing-message-inheritance.js
366    if (typeof data === 'string' &&
367        (encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
368      data = this._header + data;
369    } else {
370      const header = this._header;
371      this.outputData.unshift({
372        data: header,
373        encoding: 'latin1',
374        callback: null,
375      });
376      this.outputSize += header.length;
377      this._onPendingData(header.length);
378    }
379    this._headerSent = true;
380  }
381  return this._writeRaw(data, encoding, callback, byteLength);
382};
383
384OutgoingMessage.prototype._writeRaw = _writeRaw;
385function _writeRaw(data, encoding, callback, size) {
386  const conn = this.socket;
387  if (conn && conn.destroyed) {
388    // The socket was destroyed. If we're still trying to write to it,
389    // then we haven't gotten the 'close' event yet.
390    return false;
391  }
392
393  if (typeof encoding === 'function') {
394    callback = encoding;
395    encoding = null;
396  }
397
398  if (conn && conn._httpMessage === this && conn.writable) {
399    // There might be pending data in the this.output buffer.
400    if (this.outputData.length) {
401      this._flushOutput(conn);
402    }
403    // Directly write to socket.
404    return conn.write(data, encoding, callback);
405  }
406  // Buffer, as long as we're not destroyed.
407  this.outputData.push({ data, encoding, callback });
408  this.outputSize += data.length;
409  this._onPendingData(data.length);
410  return this.outputSize < this[kHighWaterMark];
411}
412
413
414OutgoingMessage.prototype._storeHeader = _storeHeader;
415function _storeHeader(firstLine, headers) {
416  // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
417  // in the case of response it is: 'HTTP/1.1 200 OK\r\n'
418  const state = {
419    connection: false,
420    contLen: false,
421    te: false,
422    date: false,
423    expect: false,
424    trailer: false,
425    header: firstLine,
426  };
427
428  if (headers) {
429    if (headers === this[kOutHeaders]) {
430      for (const key in headers) {
431        const entry = headers[key];
432        processHeader(this, state, entry[0], entry[1], false);
433      }
434    } else if (ArrayIsArray(headers)) {
435      if (headers.length && ArrayIsArray(headers[0])) {
436        for (let i = 0; i < headers.length; i++) {
437          const entry = headers[i];
438          processHeader(this, state, entry[0], entry[1], true);
439        }
440      } else {
441        if (headers.length % 2 !== 0) {
442          throw new ERR_INVALID_ARG_VALUE('headers', headers);
443        }
444
445        for (let n = 0; n < headers.length; n += 2) {
446          processHeader(this, state, headers[n + 0], headers[n + 1], true);
447        }
448      }
449    } else {
450      for (const key in headers) {
451        if (ObjectPrototypeHasOwnProperty(headers, key)) {
452          processHeader(this, state, key, headers[key], true);
453        }
454      }
455    }
456  }
457
458  let { header } = state;
459
460  // Date header
461  if (this.sendDate && !state.date) {
462    header += 'Date: ' + utcDate() + '\r\n';
463  }
464
465  // Force the connection to close when the response is a 204 No Content or
466  // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked"
467  // header.
468  //
469  // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but
470  // node.js used to send out a zero chunk anyway to accommodate clients
471  // that don't have special handling for those responses.
472  //
473  // It was pointed out that this might confuse reverse proxies to the point
474  // of creating security liabilities, so suppress the zero chunk and force
475  // the connection to close.
476  if (this.chunkedEncoding && (this.statusCode === 204 ||
477                               this.statusCode === 304)) {
478    debug(this.statusCode + ' response should not use chunked encoding,' +
479          ' closing connection.');
480    this.chunkedEncoding = false;
481    this.shouldKeepAlive = false;
482  }
483
484  // keep-alive logic
485  if (this._removedConnection) {
486    this._last = true;
487    this.shouldKeepAlive = false;
488  } else if (!state.connection) {
489    const shouldSendKeepAlive = this.shouldKeepAlive &&
490        (state.contLen || this.useChunkedEncodingByDefault || this.agent);
491    if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) {
492      header += 'Connection: close\r\n';
493    } else if (shouldSendKeepAlive) {
494      header += 'Connection: keep-alive\r\n';
495      if (this._keepAliveTimeout && this._defaultKeepAlive) {
496        const timeoutSeconds = MathFloor(this._keepAliveTimeout / 1000);
497        let max = '';
498        if (~~this._maxRequestsPerSocket > 0) {
499          max = `, max=${this._maxRequestsPerSocket}`;
500        }
501        header += `Keep-Alive: timeout=${timeoutSeconds}${max}\r\n`;
502      }
503    } else {
504      this._last = true;
505      header += 'Connection: close\r\n';
506    }
507  }
508
509  if (!state.contLen && !state.te) {
510    if (!this._hasBody) {
511      // Make sure we don't end the 0\r\n\r\n at the end of the message.
512      this.chunkedEncoding = false;
513    } else if (!this.useChunkedEncodingByDefault) {
514      this._last = true;
515    } else if (!state.trailer &&
516               !this._removedContLen &&
517               typeof this._contentLength === 'number') {
518      header += 'Content-Length: ' + this._contentLength + '\r\n';
519    } else if (!this._removedTE) {
520      header += 'Transfer-Encoding: chunked\r\n';
521      this.chunkedEncoding = true;
522    } else {
523      // We should only be able to get here if both Content-Length and
524      // Transfer-Encoding are removed by the user.
525      // See: test/parallel/test-http-remove-header-stays-removed.js
526      debug('Both Content-Length and Transfer-Encoding are removed');
527    }
528  }
529
530  // Test non-chunked message does not have trailer header set,
531  // message will be terminated by the first empty line after the
532  // header fields, regardless of the header fields present in the
533  // message, and thus cannot contain a message body or 'trailers'.
534  if (this.chunkedEncoding !== true && state.trailer) {
535    throw new ERR_HTTP_TRAILER_INVALID();
536  }
537
538  this._header = header + '\r\n';
539  this._headerSent = false;
540
541  // Wait until the first body chunk, or close(), is sent to flush,
542  // UNLESS we're sending Expect: 100-continue.
543  if (state.expect) this._send('');
544}
545
546function processHeader(self, state, key, value, validate) {
547  if (validate)
548    validateHeaderName(key);
549
550  // If key is content-disposition and there is content-length
551  // encode the value in latin1
552  // https://www.rfc-editor.org/rfc/rfc6266#section-4.3
553  // Refs: https://github.com/nodejs/node/pull/46528
554  if (isContentDispositionField(key) && self._contentLength) {
555    value = Buffer.from(value, 'latin1');
556  }
557
558  if (ArrayIsArray(value)) {
559    if (
560      (value.length < 2 || !isCookieField(key)) &&
561      (!self[kUniqueHeaders] || !self[kUniqueHeaders].has(StringPrototypeToLowerCase(key)))
562    ) {
563      // Retain for(;;) loop for performance reasons
564      // Refs: https://github.com/nodejs/node/pull/30958
565      for (let i = 0; i < value.length; i++)
566        storeHeader(self, state, key, value[i], validate);
567      return;
568    }
569    value = ArrayPrototypeJoin(value, '; ');
570  }
571  storeHeader(self, state, key, value, validate);
572}
573
574function storeHeader(self, state, key, value, validate) {
575  if (validate)
576    validateHeaderValue(key, value);
577  state.header += key + ': ' + value + '\r\n';
578  matchHeader(self, state, key, value);
579}
580
581function matchHeader(self, state, field, value) {
582  if (field.length < 4 || field.length > 17)
583    return;
584  field = StringPrototypeToLowerCase(field);
585  switch (field) {
586    case 'connection':
587      state.connection = true;
588      self._removedConnection = false;
589      if (RegExpPrototypeExec(RE_CONN_CLOSE, value) !== null)
590        self._last = true;
591      else
592        self.shouldKeepAlive = true;
593      break;
594    case 'transfer-encoding':
595      state.te = true;
596      self._removedTE = false;
597      if (RegExpPrototypeExec(RE_TE_CHUNKED, value) !== null)
598        self.chunkedEncoding = true;
599      break;
600    case 'content-length':
601      state.contLen = true;
602      self._contentLength = value;
603      self._removedContLen = false;
604      break;
605    case 'date':
606    case 'expect':
607    case 'trailer':
608      state[field] = true;
609      break;
610    case 'keep-alive':
611      self._defaultKeepAlive = false;
612      break;
613  }
614}
615
616const validateHeaderName = hideStackFrames((name, label) => {
617  if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
618    throw new ERR_INVALID_HTTP_TOKEN(label || 'Header name', name);
619  }
620});
621
622const validateHeaderValue = hideStackFrames((name, value) => {
623  if (value === undefined) {
624    throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
625  }
626  if (checkInvalidHeaderChar(value)) {
627    debug('Header "%s" contains invalid characters', name);
628    throw new ERR_INVALID_CHAR('header content', name);
629  }
630});
631
632function parseUniqueHeadersOption(headers) {
633  if (!ArrayIsArray(headers)) {
634    return null;
635  }
636
637  const unique = new SafeSet();
638  const l = headers.length;
639  for (let i = 0; i < l; i++) {
640    unique.add(StringPrototypeToLowerCase(headers[i]));
641  }
642
643  return unique;
644}
645
646OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
647  if (this._header) {
648    throw new ERR_HTTP_HEADERS_SENT('set');
649  }
650  validateHeaderName(name);
651  validateHeaderValue(name, value);
652
653  let headers = this[kOutHeaders];
654  if (headers === null)
655    this[kOutHeaders] = headers = ObjectCreate(null);
656
657  headers[StringPrototypeToLowerCase(name)] = [name, value];
658  return this;
659};
660
661OutgoingMessage.prototype.setHeaders = function setHeaders(headers) {
662  if (this._header) {
663    throw new ERR_HTTP_HEADERS_SENT('set');
664  }
665
666
667  if (
668    !headers ||
669    ArrayIsArray(headers) ||
670    typeof headers.keys !== 'function' ||
671    typeof headers.get !== 'function'
672  ) {
673    throw new ERR_INVALID_ARG_TYPE('headers', ['Headers', 'Map'], headers);
674  }
675
676  for (const key of headers.keys()) {
677    this.setHeader(key, headers.get(key));
678  }
679
680  return this;
681};
682
683OutgoingMessage.prototype.appendHeader = function appendHeader(name, value) {
684  if (this._header) {
685    throw new ERR_HTTP_HEADERS_SENT('append');
686  }
687  validateHeaderName(name);
688  validateHeaderValue(name, value);
689
690  const field = StringPrototypeToLowerCase(name);
691  const headers = this[kOutHeaders];
692  if (headers === null || !headers[field]) {
693    return this.setHeader(name, value);
694  }
695
696  // Prepare the field for appending, if required
697  if (!ArrayIsArray(headers[field][1])) {
698    headers[field][1] = [headers[field][1]];
699  }
700
701  const existingValues = headers[field][1];
702  if (ArrayIsArray(value)) {
703    for (let i = 0, length = value.length; i < length; i++) {
704      existingValues.push(value[i]);
705    }
706  } else {
707    existingValues.push(value);
708  }
709
710  return this;
711};
712
713
714OutgoingMessage.prototype.getHeader = function getHeader(name) {
715  validateString(name, 'name');
716
717  const headers = this[kOutHeaders];
718  if (headers === null)
719    return;
720
721  const entry = headers[StringPrototypeToLowerCase(name)];
722  return entry && entry[1];
723};
724
725
726// Returns an array of the names of the current outgoing headers.
727OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() {
728  return this[kOutHeaders] !== null ? ObjectKeys(this[kOutHeaders]) : [];
729};
730
731
732// Returns an array of the names of the current outgoing raw headers.
733OutgoingMessage.prototype.getRawHeaderNames = function getRawHeaderNames() {
734  const headersMap = this[kOutHeaders];
735  if (headersMap === null) return [];
736
737  const values = ObjectValues(headersMap);
738  const headers = Array(values.length);
739  // Retain for(;;) loop for performance reasons
740  // Refs: https://github.com/nodejs/node/pull/30958
741  for (let i = 0, l = values.length; i < l; i++) {
742    headers[i] = values[i][0];
743  }
744
745  return headers;
746};
747
748
749// Returns a shallow copy of the current outgoing headers.
750OutgoingMessage.prototype.getHeaders = function getHeaders() {
751  const headers = this[kOutHeaders];
752  const ret = ObjectCreate(null);
753  if (headers) {
754    const keys = ObjectKeys(headers);
755    // Retain for(;;) loop for performance reasons
756    // Refs: https://github.com/nodejs/node/pull/30958
757    for (let i = 0; i < keys.length; ++i) {
758      const key = keys[i];
759      const val = headers[key][1];
760      ret[key] = val;
761    }
762  }
763  return ret;
764};
765
766
767OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
768  validateString(name, 'name');
769  return this[kOutHeaders] !== null &&
770    !!this[kOutHeaders][StringPrototypeToLowerCase(name)];
771};
772
773
774OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
775  validateString(name, 'name');
776
777  if (this._header) {
778    throw new ERR_HTTP_HEADERS_SENT('remove');
779  }
780
781  const key = StringPrototypeToLowerCase(name);
782
783  switch (key) {
784    case 'connection':
785      this._removedConnection = true;
786      break;
787    case 'content-length':
788      this._removedContLen = true;
789      break;
790    case 'transfer-encoding':
791      this._removedTE = true;
792      break;
793    case 'date':
794      this.sendDate = false;
795      break;
796  }
797
798  if (this[kOutHeaders] !== null) {
799    delete this[kOutHeaders][key];
800  }
801};
802
803
804OutgoingMessage.prototype._implicitHeader = function _implicitHeader() {
805  throw new ERR_METHOD_NOT_IMPLEMENTED('_implicitHeader()');
806};
807
808ObjectDefineProperty(OutgoingMessage.prototype, 'headersSent', {
809  __proto__: null,
810  configurable: true,
811  enumerable: true,
812  get: function() { return !!this._header; },
813});
814
815ObjectDefineProperty(OutgoingMessage.prototype, 'writableEnded', {
816  __proto__: null,
817  get: function() { return this.finished; },
818});
819
820ObjectDefineProperty(OutgoingMessage.prototype, 'writableNeedDrain', {
821  __proto__: null,
822  get: function() {
823    return !this.destroyed && !this.finished && this[kNeedDrain];
824  },
825});
826
827const crlf_buf = Buffer.from('\r\n');
828OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
829  if (typeof encoding === 'function') {
830    callback = encoding;
831    encoding = null;
832  }
833
834  const ret = write_(this, chunk, encoding, callback, false);
835  if (!ret)
836    this[kNeedDrain] = true;
837  return ret;
838};
839
840function onError(msg, err, callback) {
841  const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined;
842  defaultTriggerAsyncIdScope(triggerAsyncId,
843                             process.nextTick,
844                             emitErrorNt,
845                             msg,
846                             err,
847                             callback);
848}
849
850function emitErrorNt(msg, err, callback) {
851  callback(err);
852  if (typeof msg.emit === 'function' && !msg._closed) {
853    msg.emit('error', err);
854  }
855}
856
857function strictContentLength(msg) {
858  return (
859    msg.strictContentLength &&
860    msg._contentLength != null &&
861    msg._hasBody &&
862    !msg._removedContLen &&
863    !msg.chunkedEncoding &&
864    !msg.hasHeader('transfer-encoding')
865  );
866}
867
868function write_(msg, chunk, encoding, callback, fromEnd) {
869  if (typeof callback !== 'function')
870    callback = nop;
871
872  if (chunk === null) {
873    throw new ERR_STREAM_NULL_VALUES();
874  } else if (typeof chunk !== 'string' && !isUint8Array(chunk)) {
875    throw new ERR_INVALID_ARG_TYPE(
876      'chunk', ['string', 'Buffer', 'Uint8Array'], chunk);
877  }
878
879  let err;
880  if (msg.finished) {
881    err = new ERR_STREAM_WRITE_AFTER_END();
882  } else if (msg.destroyed) {
883    err = new ERR_STREAM_DESTROYED('write');
884  }
885
886  if (err) {
887    if (!msg.destroyed) {
888      onError(msg, err, callback);
889    } else {
890      process.nextTick(callback, err);
891    }
892    return false;
893  }
894
895  let len;
896
897  if (msg.strictContentLength) {
898    len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
899
900    if (
901      strictContentLength(msg) &&
902      (fromEnd ? msg[kBytesWritten] + len !== msg._contentLength : msg[kBytesWritten] + len > msg._contentLength)
903    ) {
904      throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(len + msg[kBytesWritten], msg._contentLength);
905    }
906
907    msg[kBytesWritten] += len;
908  }
909
910  if (!msg._header) {
911    if (fromEnd) {
912      len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
913      msg._contentLength = len;
914    }
915    msg._implicitHeader();
916  }
917
918  if (!msg._hasBody) {
919    if (msg[kRejectNonStandardBodyWrites]) {
920      throw new ERR_HTTP_BODY_NOT_ALLOWED();
921    } else {
922      debug('This type of response MUST NOT have a body. ' +
923        'Ignoring write() calls.');
924      process.nextTick(callback);
925      return true;
926    }
927  }
928
929  if (!fromEnd && msg.socket && !msg.socket.writableCorked) {
930    msg.socket.cork();
931    process.nextTick(connectionCorkNT, msg.socket);
932  }
933
934  let ret;
935  if (msg.chunkedEncoding && chunk.length !== 0) {
936    len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength;
937    msg._send(NumberPrototypeToString(len, 16), 'latin1', null);
938    msg._send(crlf_buf, null, null);
939    msg._send(chunk, encoding, null, len);
940    ret = msg._send(crlf_buf, null, callback);
941  } else {
942    ret = msg._send(chunk, encoding, callback, len);
943  }
944
945  debug('write ret = ' + ret);
946  return ret;
947}
948
949
950function connectionCorkNT(conn) {
951  conn.uncork();
952}
953
954OutgoingMessage.prototype.addTrailers = function addTrailers(headers) {
955  this._trailer = '';
956  const keys = ObjectKeys(headers);
957  const isArray = ArrayIsArray(headers);
958  // Retain for(;;) loop for performance reasons
959  // Refs: https://github.com/nodejs/node/pull/30958
960  for (let i = 0, l = keys.length; i < l; i++) {
961    let field, value;
962    const key = keys[i];
963    if (isArray) {
964      field = headers[key][0];
965      value = headers[key][1];
966    } else {
967      field = key;
968      value = headers[key];
969    }
970    validateHeaderName(field, 'Trailer name');
971
972    // Check if the field must be sent several times
973    const isArrayValue = ArrayIsArray(value);
974    if (
975      isArrayValue && value.length > 1 &&
976      (!this[kUniqueHeaders] || !this[kUniqueHeaders].has(StringPrototypeToLowerCase(field)))
977    ) {
978      for (let j = 0, l = value.length; j < l; j++) {
979        if (checkInvalidHeaderChar(value[j])) {
980          debug('Trailer "%s"[%d] contains invalid characters', field, j);
981          throw new ERR_INVALID_CHAR('trailer content', field);
982        }
983        this._trailer += field + ': ' + value[j] + '\r\n';
984      }
985    } else {
986      if (isArrayValue) {
987        value = ArrayPrototypeJoin(value, '; ');
988      }
989
990      if (checkInvalidHeaderChar(value)) {
991        debug('Trailer "%s" contains invalid characters', field);
992        throw new ERR_INVALID_CHAR('trailer content', field);
993      }
994      this._trailer += field + ': ' + value + '\r\n';
995    }
996  }
997};
998
999function onFinish(outmsg) {
1000  if (outmsg && outmsg.socket && outmsg.socket._hadError) return;
1001  outmsg.emit('finish');
1002}
1003
1004OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
1005  if (typeof chunk === 'function') {
1006    callback = chunk;
1007    chunk = null;
1008    encoding = null;
1009  } else if (typeof encoding === 'function') {
1010    callback = encoding;
1011    encoding = null;
1012  }
1013
1014  if (chunk) {
1015    if (this.finished) {
1016      onError(this,
1017              new ERR_STREAM_WRITE_AFTER_END(),
1018              typeof callback !== 'function' ? nop : callback);
1019      return this;
1020    }
1021
1022    if (this.socket) {
1023      this.socket.cork();
1024    }
1025
1026    write_(this, chunk, encoding, null, true);
1027  } else if (this.finished) {
1028    if (typeof callback === 'function') {
1029      if (!this.writableFinished) {
1030        this.on('finish', callback);
1031      } else {
1032        callback(new ERR_STREAM_ALREADY_FINISHED('end'));
1033      }
1034    }
1035    return this;
1036  } else if (!this._header) {
1037    if (this.socket) {
1038      this.socket.cork();
1039    }
1040
1041    this._contentLength = 0;
1042    this._implicitHeader();
1043  }
1044
1045  if (typeof callback === 'function')
1046    this.once('finish', callback);
1047
1048  if (strictContentLength(this) && this[kBytesWritten] !== this._contentLength) {
1049    throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength);
1050  }
1051
1052  const finish = onFinish.bind(undefined, this);
1053
1054  if (this._hasBody && this.chunkedEncoding) {
1055    this._send('0\r\n' + this._trailer + '\r\n', 'latin1', finish);
1056  } else if (!this._headerSent || this.writableLength || chunk) {
1057    this._send('', 'latin1', finish);
1058  } else {
1059    process.nextTick(finish);
1060  }
1061
1062  if (this.socket) {
1063    // Fully uncork connection on end().
1064    this.socket._writableState.corked = 1;
1065    this.socket.uncork();
1066  }
1067  this[kCorked] = 0;
1068
1069  this.finished = true;
1070
1071  // There is the first message on the outgoing queue, and we've sent
1072  // everything to the socket.
1073  debug('outgoing message end.');
1074  if (this.outputData.length === 0 &&
1075      this.socket &&
1076      this.socket._httpMessage === this) {
1077    this._finish();
1078  }
1079
1080  return this;
1081};
1082
1083
1084// This function is called once all user data are flushed to the socket.
1085// Note that it has a chance that the socket is not drained.
1086OutgoingMessage.prototype._finish = function _finish() {
1087  assert(this.socket);
1088  this.emit('prefinish');
1089};
1090
1091
1092// This logic is probably a bit confusing. Let me explain a bit:
1093//
1094// In both HTTP servers and clients it is possible to queue up several
1095// outgoing messages. This is easiest to imagine in the case of a client.
1096// Take the following situation:
1097//
1098//    req1 = client.request('GET', '/');
1099//    req2 = client.request('POST', '/');
1100//
1101// When the user does
1102//
1103//   req2.write('hello world\n');
1104//
1105// it's possible that the first request has not been completely flushed to
1106// the socket yet. Thus the outgoing messages need to be prepared to queue
1107// up data internally before sending it on further to the socket's queue.
1108//
1109// This function, _flush(), is called by both the Server and Client
1110// to attempt to flush any pending messages out to the socket.
1111OutgoingMessage.prototype._flush = function _flush() {
1112  const socket = this.socket;
1113
1114  if (socket && socket.writable) {
1115    // There might be remaining data in this.output; write it out
1116    const ret = this._flushOutput(socket);
1117
1118    if (this.finished) {
1119      // This is a queue to the server or client to bring in the next this.
1120      this._finish();
1121    } else if (ret && this[kNeedDrain]) {
1122      this[kNeedDrain] = false;
1123      this.emit('drain');
1124    }
1125  }
1126};
1127
1128OutgoingMessage.prototype._flushOutput = function _flushOutput(socket) {
1129  while (this[kCorked]) {
1130    this[kCorked]--;
1131    socket.cork();
1132  }
1133
1134  const outputLength = this.outputData.length;
1135  if (outputLength <= 0)
1136    return undefined;
1137
1138  const outputData = this.outputData;
1139  socket.cork();
1140  let ret;
1141  // Retain for(;;) loop for performance reasons
1142  // Refs: https://github.com/nodejs/node/pull/30958
1143  for (let i = 0; i < outputLength; i++) {
1144    const { data, encoding, callback } = outputData[i];
1145    ret = socket.write(data, encoding, callback);
1146  }
1147  socket.uncork();
1148
1149  this.outputData = [];
1150  this._onPendingData(-this.outputSize);
1151  this.outputSize = 0;
1152
1153  return ret;
1154};
1155
1156
1157OutgoingMessage.prototype.flushHeaders = function flushHeaders() {
1158  if (!this._header) {
1159    this._implicitHeader();
1160  }
1161
1162  // Force-flush the headers.
1163  this._send('');
1164};
1165
1166OutgoingMessage.prototype.pipe = function pipe() {
1167  // OutgoingMessage should be write-only. Piping from it is disabled.
1168  this.emit('error', new ERR_STREAM_CANNOT_PIPE());
1169};
1170
1171OutgoingMessage.prototype[EE.captureRejectionSymbol] =
1172function(err, event) {
1173  this.destroy(err);
1174};
1175
1176module.exports = {
1177  kHighWaterMark,
1178  kUniqueHeaders,
1179  parseUniqueHeadersOption,
1180  validateHeaderName,
1181  validateHeaderValue,
1182  OutgoingMessage,
1183};
1184