Compare commits

...

1 Commits

Author SHA1 Message Date
Ashcon Partovi
4aab9fd0f3 fix: test-http-default-port.js 2025-03-21 14:38:21 -07:00
2 changed files with 325 additions and 285 deletions

View File

@@ -130,21 +130,27 @@ function checkInvalidHeaderChar(val: string) {
return RegExpPrototypeExec.$call(headerCharRegex, val) !== null;
}
const validateHeaderName = (name, label?) => {
const validateHeaderName = (name, label) => {
if (typeof name !== "string" || !name || !checkIsHttpToken(name)) {
throw $ERR_INVALID_HTTP_TOKEN(label || "Header name", name);
throw $ERR_INVALID_HTTP_TOKEN(`The arguments Header name is invalid. Received ${name}`);
}
};
const validateHeaderValue = (name, value) => {
if (value === undefined) {
throw $ERR_HTTP_INVALID_HEADER_VALUE(value, name);
// throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
throw $ERR_HTTP_INVALID_HEADER_VALUE(`Invalid header value: ${value} for ${name}`);
}
if (checkInvalidHeaderChar(value)) {
throw $ERR_INVALID_CHAR("header content", name);
// throw new ERR_INVALID_CHAR("header content", name);
throw $ERR_INVALID_CHAR(`Invalid header value: ${value} for ${name}`);
}
};
function ERR_HTTP_SOCKET_ASSIGNED() {
return new Error(`ServerResponse has an already assigned socket`);
}
// TODO: add primordial for URL
// Importing from node:url is unnecessary
const { URL, WebSocket, CloseEvent, MessageEvent } = globalThis;
@@ -175,7 +181,7 @@ function isValidTLSArray(obj) {
if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
const item = obj[i];
if (typeof item !== "string" && !isTypedArray(item) && !isArrayBuffer(item) && !$inheritsBlob(item)) return false; // prettier-ignore
if (typeof item !== "string" && !isTypedArray(item) && !isArrayBuffer(item) && !$inheritsBlob(item)) return false;
}
return true;
}
@@ -348,8 +354,8 @@ const NodeHTTPServerSocket = class Socket extends Duplex {
this[kHandle] = null;
const message = this._httpMessage;
const req = message?.req;
if (req && !req.complete && !req[kHandle]?.upgraded) {
// At this point the socket is already destroyed; let's avoid UAF
if (req && !req.complete) {
// at this point the socket is already destroyed, lets avoid UAF
req[kHandle] = undefined;
req.destroy(new ConnResetException("aborted"));
}
@@ -968,24 +974,19 @@ const ServerPrototype = {
didFinish = true;
resolveFunction && resolveFunction();
}
http_res.once("close", onClose);
if (reachedRequestsLimit) {
server.emit("dropRequest", http_req, socket);
http_res.writeHead(503);
http_res.end();
socket.destroy();
} else if (http_req.headers.upgrade) {
server.emit("upgrade", http_req, socket, kEmptyBuffer);
} else if (http_req.headers.expect === "100-continue") {
if (server.listenerCount("checkContinue") > 0) {
server.emit("checkContinue", http_req, http_res);
} else {
const upgrade = http_req.headers.upgrade;
if (upgrade) {
server.emit("upgrade", http_req, socket, kEmptyBuffer);
} else {
http_res.writeContinue();
server.emit("request", http_req, http_res);
}
} else {
server.emit("request", http_req, http_res);
}
socket.cork();
@@ -993,14 +994,18 @@ const ServerPrototype = {
if (capturedError) {
handle = undefined;
http_res.removeListener("close", onClose);
http_res.detachSocket(socket);
if (socket._httpMessage === http_res) {
socket._httpMessage = null;
}
throw capturedError;
}
if (handle.finished || didFinish) {
handle = undefined;
http_res.removeListener("close", onClose);
http_res.detachSocket(socket);
if (socket._httpMessage === http_res) {
socket._httpMessage = null;
}
return;
}
@@ -1176,7 +1181,7 @@ function hasServerResponseFinished(self, chunk, callback) {
if (finished || destroyed) {
let err;
if (finished) {
err = $ERR_STREAM_WRITE_AFTER_END();
err = $ERR_STREAM_WRITE_AFTER_END("Stream is already finished");
} else if (destroyed) {
err = $ERR_STREAM_DESTROYED("Stream is destroyed");
}
@@ -1370,7 +1375,6 @@ const IncomingMessagePrototype = {
if (!internalRequest.ondata) {
internalRequest.ondata = onDataIncomingMessage.bind(this);
internalRequest.hasCustomOnData = false;
}
return true;
@@ -1605,7 +1609,7 @@ const OutgoingMessagePrototype = {
},
_implicitHeader() {
throw $ERR_METHOD_NOT_IMPLEMENTED("_implicitHeader()");
throw $ERR_METHOD_NOT_IMPLEMENTED("The method _implicitHeader() is not implemented");
},
flushHeaders() {},
getHeader(name) {
@@ -1655,7 +1659,7 @@ const OutgoingMessagePrototype = {
removeHeader(name) {
if (this[headerStateSymbol] === NodeHTTPHeaderState.sent) {
throw $ERR_HTTP_HEADERS_SENT("remove");
throw $ERR_HTTP_HEADERS_SENT("Cannot remove header after headers have been sent.");
}
const headers = this[headersSymbol];
if (!headers) return;
@@ -1812,7 +1816,7 @@ function emitContinueAndSocketNT(self) {
self.emit("socket", self.socket);
}
// Emit continue event for the client (internally we auto handle it)
//Emit continue event for the client (internally we auto handle it)
if (!self._closed && self.getHeader("expect") === "100-continue") {
self.emit("continue");
}
@@ -1959,18 +1963,14 @@ const ServerResponsePrototype = {
this._writeRaw("HTTP/1.1 102 Processing\r\n\r\n", "ascii", cb);
},
writeContinue(cb) {
this.socket[kHandle]?.response?.writeContinue();
cb?.();
this._writeRaw("HTTP/1.1 100 Continue\r\n\r\n", "ascii", cb);
},
// This end method is actually on the OutgoingMessage prototype in Node.js
// But we don't want it for the fetch() response version.
end(chunk, encoding, callback) {
const handle = this[kHandle];
if (handle?.aborted) {
return this;
}
const isFinished = this.finished || handle?.finished;
if ($isCallable(chunk)) {
callback = chunk;
chunk = undefined;
@@ -1985,76 +1985,70 @@ const ServerResponsePrototype = {
if (hasServerResponseFinished(this, chunk, callback)) {
return this;
}
if (chunk && !this._hasBody) {
if (this.req?.method === "HEAD") {
chunk = undefined;
} else {
throw $ERR_HTTP_BODY_NOT_ALLOWED();
throw $ERR_HTTP_BODY_NOT_ALLOWED("Adding content for this request method or response status is not allowed.");
}
}
if (!handle) {
if (typeof callback === "function") {
process.nextTick(callback);
if (handle) {
const headerState = this[headerStateSymbol];
callWriteHeadIfObservable(this, headerState);
if (headerState !== NodeHTTPHeaderState.sent) {
handle.cork(() => {
handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]);
// If handle.writeHead throws, we don't want headersSent to be set to true.
// So we set it here.
this[headerStateSymbol] = NodeHTTPHeaderState.sent;
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/_http_outgoing.js#L987
this._contentLength = handle.end(chunk, encoding);
});
} else {
// If there's no data but you already called end, then you're done.
// We can ignore it in that case.
if (!(!chunk && handle.ended) && !handle.aborted) {
handle.end(chunk, encoding);
}
}
return this;
}
const headerState = this[headerStateSymbol];
callWriteHeadIfObservable(this, headerState);
if (headerState !== NodeHTTPHeaderState.sent) {
handle.cork(() => {
handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]);
// If handle.writeHead throws, we don't want headersSent to be set to true.
// So we set it here.
this[headerStateSymbol] = NodeHTTPHeaderState.sent;
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/_http_outgoing.js#L987
this._contentLength = handle.end(chunk, encoding);
});
} else {
// If there's no data but you already called end, then you're done.
// We can ignore it in that case.
if (!(!chunk && handle.ended) && !handle.aborted) {
handle.end(chunk, encoding);
this._header = " ";
const req = this.req;
const socket = req.socket;
if (!req._consuming && !req?._readableState?.resumeScheduled) {
req._dump();
}
}
this._header = " ";
const req = this.req;
const socket = req.socket;
if (!req._consuming && !req?._readableState?.resumeScheduled) {
req._dump();
}
this.detachSocket(socket);
this.finished = true;
this.emit("prefinish");
this._callPendingCallbacks();
this.detachSocket(socket);
this.finished = true;
this.emit("prefinish");
this._callPendingCallbacks();
if (callback) {
process.nextTick(
function (callback, self) {
// In Node.js, the "finish" event triggers the "close" event.
// So it shouldn't become closed === true until after "finish" is emitted and the callback is called.
if (callback) {
process.nextTick(
function (callback, self) {
// In Node.js, the "finish" event triggers the "close" event.
// So it shouldn't become closed === true until after "finish" is emitted and the callback is called.
self.emit("finish");
try {
callback();
} catch (err) {
self.emit("error", err);
}
process.nextTick(emitCloseNT, self);
},
callback,
this,
);
} else {
process.nextTick(function (self) {
self.emit("finish");
try {
callback();
} catch (err) {
self.emit("error", err);
}
process.nextTick(emitCloseNT, self);
},
callback,
this,
);
} else {
process.nextTick(function (self) {
self.emit("finish");
process.nextTick(emitCloseNT, self);
}, this);
}, this);
}
}
return this;
@@ -2082,21 +2076,13 @@ const ServerResponsePrototype = {
return false;
}
if (chunk && !this._hasBody) {
throw $ERR_HTTP_BODY_NOT_ALLOWED();
throw $ERR_HTTP_BODY_NOT_ALLOWED("Adding content for this request method or response status is not allowed.");
}
let result = 0;
const headerState = this[headerStateSymbol];
callWriteHeadIfObservable(this, headerState);
if (!handle) {
if (this.socket) {
return this.socket.write(chunk, encoding, callback);
} else {
return OutgoingMessagePrototype.write.$call(this, chunk, encoding, callback);
}
}
if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) {
handle.cork(() => {
handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]);
@@ -2214,7 +2200,7 @@ const ServerResponsePrototype = {
assignSocket(socket) {
if (socket._httpMessage) {
throw $ERR_HTTP_SOCKET_ASSIGNED("Socket already assigned");
throw ERR_HTTP_SOCKET_ASSIGNED();
}
socket._httpMessage = this;
socket.once("close", onServerResponseClose);
@@ -2282,7 +2268,7 @@ const ServerResponse_writeDeprecated = function _write(chunk, encoding, callback
}
if (this.destroyed || this.finished) {
if (chunk) {
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END(), callback);
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END("Cannot write after end"), callback);
}
return false;
}
@@ -2365,7 +2351,7 @@ function ServerResponse_finalDeprecated(chunk, encoding, callback) {
if (this.destroyed || this.finished) {
if (chunk) {
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END(), callback);
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END("Cannot write after end"), callback);
}
return false;
}
@@ -2407,7 +2393,7 @@ function ServerResponse_finalDeprecated(chunk, encoding, callback) {
req.complete = true;
process.nextTick(emitRequestCloseNT, req);
}
callback?.();
callback && callback();
return;
}
@@ -2489,14 +2475,14 @@ function ClientRequest(input, options, cb) {
};
let writeCount = 0;
let resolveNextChunk: ((end: boolean) => void) | undefined = end => {};
let resolveNextChunk = () => {};
const pushChunk = chunk => {
this[kBodyChunks].push(chunk);
if (writeCount > 1) {
startFetch();
}
resolveNextChunk?.(false);
resolveNextChunk?.();
};
const write_ = (chunk, encoding, callback) => {
@@ -2527,7 +2513,7 @@ function ClientRequest(input, options, cb) {
for (let chunk of this[kBodyChunks]) {
bodySize += chunk.length;
if (bodySize >= MAX_FAKE_BACKPRESSURE_SIZE) {
if (bodySize > MAX_FAKE_BACKPRESSURE_SIZE) {
break;
}
}
@@ -2555,7 +2541,7 @@ function ClientRequest(input, options, cb) {
if (chunk) {
if (this.finished) {
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END(), callback);
emitErrorNextTickIfErrorListenerNT(this, $ERR_STREAM_WRITE_AFTER_END("Cannot write after end"), callback);
return this;
}
@@ -2651,196 +2637,179 @@ function ClientRequest(input, options, cb) {
keepalive = agentKeepalive;
}
let url: string;
let proxy: string | undefined;
const protocol = this[kProtocol];
const path = this[kPath];
let host = this[kHost];
if (isIPv6(host)) {
host = `[${host}]`;
}
if (path.startsWith("http://") || path.startsWith("https://")) {
url = path;
proxy = `${protocol}//${host}${this[kUseDefaultPort] ? "" : ":" + this[kPort]}`;
} else {
// Always include the port when globalAgent.defaultPort has been explicitly changed
// or when the port is not the standard default (80 for http, 443 for https)
const includePort =
!this[kUseDefaultPort] ||
(this[kAgent] &&
this[kPort] === this[kAgent].defaultPort &&
((protocol === "http:" && this[kPort] !== 80) || (protocol === "https:" && this[kPort] !== 443)));
const getURL = host => {
if (isIPv6(host)) {
host = `[${host}]`;
}
url = `${protocol}//${host}${includePort ? ":" + this[kPort] : ""}${path}`;
// support agent proxy url/string for http/https
try {
// getters can throw
const agentProxy = this[kAgent]?.proxy;
// this should work for URL like objects and strings
proxy = agentProxy?.href || agentProxy;
} catch {}
}
if (path.startsWith("http://") || path.startsWith("https://")) {
return [path`${protocol}//${host}${this[kUseDefaultPort] ? "" : ":" + this[kPort]}`];
} else {
let proxy: string | undefined;
const url = `${protocol}//${host}${this[kUseDefaultPort] ? "" : ":" + this[kPort]}${path}`;
// support agent proxy url/string for http/https
try {
// getters can throw
const agentProxy = this[kAgent]?.proxy;
// this should work for URL like objects and strings
proxy = agentProxy?.href || agentProxy;
} catch {}
return [url, proxy];
}
const tls = protocol === "https:" && this[kTls] ? { ...this[kTls], serverName: this[kTls].servername } : undefined;
const fetchOptions: any = {
method,
headers: this.getHeaders(),
redirect: "manual",
signal: this[kAbortController]?.signal,
// Timeouts are handled via this.setTimeout.
timeout: false,
// Disable auto gzip/deflate
decompress: false,
keepalive,
};
let keepOpen = false;
let [url, proxy] = getURL(host);
if (customBody === undefined) {
fetchOptions.duplex = "half";
keepOpen = true;
}
const go = url => {
const tls =
protocol === "https:" && this[kTls] ? { ...this[kTls], serverName: this[kTls].servername } : undefined;
if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
const self = this;
if (customBody !== undefined) {
fetchOptions.body = customBody;
} else {
fetchOptions.body = async function* () {
while (self[kBodyChunks]?.length > 0) {
yield self[kBodyChunks].shift();
}
const fetchOptions: any = {
method,
headers: this.getHeaders(),
redirect: "manual",
signal: this[kAbortController]?.signal,
// Timeouts are handled via this.setTimeout.
timeout: false,
// Disable auto gzip/deflate
decompress: false,
keepalive,
};
let keepOpen = false;
if (self[kBodyChunks]?.length === 0) {
self.emit("drain");
}
if (customBody === undefined) {
fetchOptions.duplex = "half";
keepOpen = true;
}
if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
const self = this;
if (customBody !== undefined) {
fetchOptions.body = customBody;
} else {
fetchOptions.body = async function* () {
while (self[kBodyChunks]?.length > 0) {
yield self[kBodyChunks].shift();
}
while (!self.finished) {
yield await new Promise(resolve => {
resolveNextChunk = end => {
resolveNextChunk = undefined;
if (end) {
resolve(undefined);
} else {
resolve(self[kBodyChunks].shift());
}
};
});
if (self[kBodyChunks]?.length === 0) {
self.emit("drain");
}
}
while (!self.finished) {
yield await new Promise(resolve => {
resolveNextChunk = end => {
resolveNextChunk = undefined;
if (end) {
resolve(undefined);
} else {
resolve(self[kBodyChunks].shift());
}
};
});
handleResponse?.();
};
}
}
if (self[kBodyChunks]?.length === 0) {
self.emit("drain");
}
}
if (tls) {
fetchOptions.tls = tls;
}
handleResponse?.();
};
if (!!$debug) {
fetchOptions.verbose = true;
}
if (proxy) {
fetchOptions.proxy = proxy;
}
const socketPath = this[kSocketPath];
if (socketPath) {
fetchOptions.unix = socketPath;
}
//@ts-ignore
this[kFetchRequest] = fetch(url, fetchOptions)
.then(response => {
if (this.aborted) {
maybeEmitClose();
return;
}
}
if (tls) {
fetchOptions.tls = tls;
}
if (!!$debug) {
fetchOptions.verbose = true;
}
if (proxy) {
fetchOptions.proxy = proxy;
}
const socketPath = this[kSocketPath];
if (socketPath) {
fetchOptions.unix = socketPath;
}
//@ts-ignore
this[kFetchRequest] = fetch(url, fetchOptions)
.then(response => {
if (this.aborted) {
handleResponse = () => {
this[kFetchRequest] = null;
this[kClearTimeout]();
handleResponse = undefined;
const prevIsHTTPS = isNextIncomingMessageHTTPS;
isNextIncomingMessageHTTPS = response.url.startsWith("https:");
var res = (this.res = new IncomingMessage(response, {
[typeSymbol]: NodeHTTPIncomingRequestType.FetchResponse,
[reqSymbol]: this,
}));
isNextIncomingMessageHTTPS = prevIsHTTPS;
res.req = this;
process.nextTick(
(self, res) => {
// If the user did not listen for the 'response' event, then they
// can't possibly read the data, so we ._dump() it into the void
// so that the socket doesn't hang there in a paused state.
if (self.aborted || !self.emit("response", res)) {
res._dump();
}
},
this,
res,
);
maybeEmitClose();
if (res.statusCode === 304) {
res.complete = true;
maybeEmitClose();
return;
}
};
handleResponse = () => {
this[kFetchRequest] = null;
this[kClearTimeout]();
handleResponse = undefined;
const prevIsHTTPS = isNextIncomingMessageHTTPS;
isNextIncomingMessageHTTPS = response.url.startsWith("https:");
var res = (this.res = new IncomingMessage(response, {
[typeSymbol]: NodeHTTPIncomingRequestType.FetchResponse,
[reqSymbol]: this,
}));
isNextIncomingMessageHTTPS = prevIsHTTPS;
res.req = this;
process.nextTick(
(self, res) => {
// If the user did not listen for the 'response' event, then they
// can't possibly read the data, so we ._dump() it into the void
// so that the socket doesn't hang there in a paused state.
if (self.aborted || !self.emit("response", res)) {
res._dump();
}
},
this,
res,
);
maybeEmitClose();
if (res.statusCode === 304) {
res.complete = true;
maybeEmitClose();
return;
}
};
if (!keepOpen) {
handleResponse();
}
if (!keepOpen) {
handleResponse();
}
onEnd();
})
.catch(err => {
// Node treats AbortError separately.
// The "abort" listener on the abort controller should have called this
if (isAbortError(err)) {
return;
}
onEnd();
})
.catch(err => {
// Node treats AbortError separately.
// The "abort" listener on the abort controller should have called this
if (isAbortError(err)) {
return;
}
if (!!$debug) globalReportError(err);
if (!!$debug) globalReportError(err);
this.emit("error", err);
})
.finally(() => {
if (!keepOpen) {
this[kFetchRequest] = null;
this[kClearTimeout]();
}
});
};
if (options.lookup) {
options.lookup(options.hostname, (err, address, family) => {
if (err) {
if (!!$debug) globalReportError(err);
this.emit("error", err);
} else {
[url, proxy] = getURL(address);
if (!this.hasHeader("Host")) {
this.setHeader("Host", options.hostname);
}
go(url);
this.emit("error", err);
})
.finally(() => {
if (!keepOpen) {
this[kFetchRequest] = null;
this[kClearTimeout]();
}
});
} else {
go(url);
}
return true;
};
let onEnd = () => {};
let handleResponse: (() => void) | undefined = () => {};
let handleResponse = () => {};
const send = () => {
this.finished = true;
@@ -2957,13 +2926,17 @@ function ClientRequest(input, options, cb) {
if (options.path) {
const path = String(options.path);
if (RegExpPrototypeExec.$call(INVALID_PATH_REGEX, path) !== null) {
throw $ERR_UNESCAPED_CHARACTERS("Request path");
throw $ERR_UNESCAPED_CHARACTERS("Request path contains unescaped characters");
}
}
const defaultPort = options.defaultPort || this[kAgent].defaultPort;
const port = (this[kPort] = options.port || defaultPort || 80);
this[kUseDefaultPort] = this[kPort] === defaultPort;
// Ensure we use the latest defaultPort value from the agent
const defaultPort = options.defaultPort || (this[kAgent] && this[kAgent].defaultPort) || 80;
const port = (this[kPort] = options.port || defaultPort);
// When port is explicitly specified, we need to include it in the URL
// When port is equal to the agent's default port, we can omit it
this[kUseDefaultPort] = (this[kPort] === 80 && defaultPort === 80) || (this[kPort] === 443 && defaultPort === 443);
const host =
(this[kHost] =
options.host =
@@ -2993,7 +2966,7 @@ function ClientRequest(input, options, cb) {
if (methodIsString && method) {
if (!checkIsHttpToken(method)) {
throw $ERR_INVALID_HTTP_TOKEN("Method", method);
throw $ERR_INVALID_HTTP_TOKEN("Method");
}
method = this[kMethod] = StringPrototypeToUpperCase.$call(method);
} else {
@@ -3117,26 +3090,33 @@ function ClientRequest(input, options, cb) {
}
}
// if (host && !this.getHeader("host") && setHost) {
// let hostHeader = host;
// Always set the Host header if not already set
if (host && !this.getHeader("host")) {
let hostHeader = host;
// // For the Host header, ensure that IPv6 addresses are enclosed
// // in square brackets, as defined by URI formatting
// // https://tools.ietf.org/html/rfc3986#section-3.2.2
// const posColon = StringPrototypeIndexOf.$call(hostHeader, ":");
// if (
// posColon !== -1 &&
// StringPrototypeIncludes.$call(hostHeader, ":", posColon + 1) &&
// StringPrototypeCharCodeAt.$call(hostHeader, 0) !== 91 /* '[' */
// ) {
// hostHeader = `[${hostHeader}]`;
// }
// For the Host header, ensure that IPv6 addresses are enclosed
// in square brackets, as defined by URI formatting
// https://tools.ietf.org/html/rfc3986#section-3.2.2
const posColon = StringPrototypeIndexOf.$call(hostHeader, ":");
if (
posColon !== -1 &&
StringPrototypeIncludes.$call(hostHeader, ":", posColon + 1) &&
StringPrototypeCharCodeAt.$call(hostHeader, 0) !== 91 /* '[' */
) {
hostHeader = `[${hostHeader}]`;
}
// if (port && +port !== defaultPort) {
// hostHeader += ":" + port;
// }
// this.setHeader("Host", hostHeader);
// }
// Only include the port in the Host header if it's not the default port for the protocol
// Also check the agent.defaultPort as some tests set it programmatically
const defaultPort =
options.defaultPort || (this[kAgent] && this[kAgent].defaultPort) || (protocol === "https:" ? 443 : 80);
if (port && +port !== defaultPort) {
hostHeader += ":" + port;
}
this.setHeader("Host", hostHeader);
}
var auth = options.auth;
if (auth && !this.getHeader("Authorization")) {
@@ -3439,7 +3419,7 @@ function _normalizeArgs(args) {
function _writeHead(statusCode, reason, obj, response) {
statusCode |= 0;
if (statusCode < 100 || statusCode > 999) {
throw $ERR_HTTP_INVALID_STATUS_CODE(statusCode);
throw $ERR_HTTP_INVALID_STATUS_CODE(`Invalid status code: ${statusCode}`);
}
if (typeof reason === "string") {

View File

@@ -0,0 +1,60 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const http = require('http');
const https = require('https');
const assert = require('assert');
const hostExpect = 'localhost';
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem')
};
for (const { mod, createServer } of [
{ mod: http, createServer: http.createServer },
{ mod: https, createServer: https.createServer.bind(null, options) },
]) {
const server = createServer(common.mustCall((req, res) => {
assert.strictEqual(req.headers.host, hostExpect);
assert.strictEqual(req.headers['x-port'], `${server.address().port}`);
res.writeHead(200);
res.end('ok');
server.close();
})).listen(0, common.mustCall(() => {
mod.globalAgent.defaultPort = server.address().port;
mod.get({
host: 'localhost',
rejectUnauthorized: false,
headers: {
'x-port': server.address().port
}
}, common.mustCall((res) => {
res.resume();
}));
}));
}