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