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