mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
21 Commits
claude/rep
...
fix-node-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df509cc783 | ||
|
|
1fe01a8750 | ||
|
|
e9ff7a618a | ||
|
|
aaebe43299 | ||
|
|
04000a9822 | ||
|
|
0cd7cd6528 | ||
|
|
9a1a080d49 | ||
|
|
62de1caa02 | ||
|
|
124fe6cbac | ||
|
|
93bac50410 | ||
|
|
91b16deebf | ||
|
|
f5af325b24 | ||
|
|
3010388b2d | ||
|
|
d89a6eb948 | ||
|
|
7ebdf39223 | ||
|
|
99f667685e | ||
|
|
c0716aebfe | ||
|
|
b58a8ea979 | ||
|
|
e054c11e10 | ||
|
|
53cbffe733 | ||
|
|
e462ba3b64 |
@@ -485,6 +485,13 @@ namespace uWS
|
||||
}
|
||||
headers++;
|
||||
|
||||
/* Check for empty headers (only for HTTP 1.0 requests) */
|
||||
if (isAncientHTTP && *postPaddedBuffer == '\r' && postPaddedBuffer[1] == '\n') {
|
||||
/* End of headers immediately after request line */
|
||||
headers->key = std::string_view(nullptr, 0);
|
||||
return (unsigned int) ((postPaddedBuffer + 2) - start);
|
||||
}
|
||||
|
||||
for (unsigned int i = 1; i < UWS_HTTP_MAX_HEADERS_COUNT - 1; i++) {
|
||||
/* Lower case and consume the field name */
|
||||
preliminaryKey = postPaddedBuffer;
|
||||
@@ -599,8 +606,8 @@ namespace uWS
|
||||
req->bf.add(h->key);
|
||||
}
|
||||
|
||||
/* Break if no host header (but we can have empty string which is different from nullptr) */
|
||||
if (!req->getHeader("host").data()) {
|
||||
/* Host header is required for HTTP/1.1 but not for HTTP/1.0 */
|
||||
if (!isAncientHTTP && !req->getHeader("host").data()) {
|
||||
return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
|
||||
}
|
||||
|
||||
@@ -609,8 +616,16 @@ namespace uWS
|
||||
* the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt
|
||||
* to perform request smuggling (Section 11.2) or response splitting (Section 11.1) and
|
||||
* ought to be handled as an error. */
|
||||
std::string_view transferEncodingString = req->getHeader("transfer-encoding");
|
||||
std::string_view contentLengthString = req->getHeader("content-length");
|
||||
/* Skip header checks for HTTP 1.0 requests with no headers */
|
||||
std::string_view transferEncodingString;
|
||||
std::string_view contentLengthString;
|
||||
|
||||
/* Only try to get headers if we have any */
|
||||
HttpRequest::Header *firstHeader = req->headers + 1; // Skip request line (first header)
|
||||
if (firstHeader->key.length() > 0) {
|
||||
transferEncodingString = req->getHeader("transfer-encoding");
|
||||
contentLengthString = req->getHeader("content-length");
|
||||
}
|
||||
|
||||
auto transferEncodingStringLen = transferEncodingString.length();
|
||||
auto contentLengthStringLen = contentLengthString.length();
|
||||
|
||||
@@ -6559,7 +6559,7 @@ pub const NodeHTTPResponse = struct {
|
||||
if (status_code_value != .undefined) {
|
||||
break :brk globalObject.validateIntegerRange(status_code_value, i32, 200, .{
|
||||
.min = 100,
|
||||
.max = 599,
|
||||
.max = 999,
|
||||
}) catch return error.JSError;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <JavaScriptCore/JSFunction.h>
|
||||
#include <JavaScriptCore/IteratorOperations.h>
|
||||
#include "wtf/URL.h"
|
||||
#include "JSFetchHeaders.h"
|
||||
#include "JSDOMExceptionHandling.h"
|
||||
@@ -44,6 +45,7 @@ JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterRemoteAddress);
|
||||
BUN_DECLARE_HOST_FUNCTION(Bun__drainMicrotasksFromJS);
|
||||
JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterDuplex);
|
||||
JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterDuplex);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsHTTPProcessArrayHeaders);
|
||||
|
||||
// Create a static hash table of values containing an onclose DOMAttributeGetterSetter and a close function
|
||||
static const HashTableValue JSNodeHTTPServerSocketPrototypeTableValues[] = {
|
||||
@@ -1282,6 +1284,160 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPGetHeader, (JSGlobalObject * globalObject, CallFr
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsHTTPProcessArrayHeaders, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue headersArrayValue = callFrame->argument(0);
|
||||
JSValue targetValue = callFrame->argument(1);
|
||||
|
||||
if (UNLIKELY(!isArray(globalObject, headersArrayValue) || !targetValue.isObject())) {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
auto* headersArray = jsCast<JSArray*>(headersArrayValue);
|
||||
auto* targetObject = targetValue.getObject();
|
||||
|
||||
struct HeaderEntry {
|
||||
String originalName;
|
||||
Vector<String> values;
|
||||
};
|
||||
HashMap<String, HeaderEntry> headerMap;
|
||||
|
||||
auto addHeaderEntry = [&](const String& name, const String& value) {
|
||||
String lowercaseName = name.convertToASCIILowercase();
|
||||
auto result = headerMap.ensure(lowercaseName, [&name] {
|
||||
HeaderEntry entry;
|
||||
entry.originalName = name;
|
||||
return entry;
|
||||
});
|
||||
|
||||
result.iterator->value.values.append(value);
|
||||
};
|
||||
|
||||
auto joinHeaderValues = [](const Vector<String>& values, ASCIILiteral delimiter) -> String {
|
||||
if (UNLIKELY(values.isEmpty()))
|
||||
return emptyString();
|
||||
|
||||
if (UNLIKELY(values.size() == 1))
|
||||
return values[0];
|
||||
|
||||
StringBuilder builder;
|
||||
builder.append(values[0]);
|
||||
|
||||
for (size_t i = 1; i < values.size(); i++) {
|
||||
builder.append(delimiter);
|
||||
builder.append(values[i]);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
};
|
||||
|
||||
bool isNestedArray = false;
|
||||
if (headersArray->length() > 0) {
|
||||
JSValue firstItem = headersArray->getIndex(globalObject, 0);
|
||||
isNestedArray = isArray(globalObject, firstItem);
|
||||
}
|
||||
|
||||
if (isNestedArray) {
|
||||
// Process array of form [['key', 'value'], ['key2', 'value2']]
|
||||
forEachInIterable(globalObject, headersArrayValue, [&](JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue entryValue) {
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (UNLIKELY(!isArray(globalObject, entryValue)))
|
||||
return;
|
||||
|
||||
auto* entryArray = jsCast<JSArray*>(entryValue);
|
||||
if (UNLIKELY(entryArray->length() < 2))
|
||||
return;
|
||||
|
||||
JSValue nameValue = entryArray->getIndex(globalObject, 0);
|
||||
JSValue valueValue = entryArray->getIndex(globalObject, 1);
|
||||
|
||||
if (UNLIKELY(!nameValue.isString() || !valueValue.isString()))
|
||||
return;
|
||||
|
||||
String name = nameValue.toWTFString(globalObject);
|
||||
if (UNLIKELY(scope.exception()))
|
||||
return;
|
||||
|
||||
String value = valueValue.toWTFString(globalObject);
|
||||
if (UNLIKELY(scope.exception()))
|
||||
return;
|
||||
|
||||
addHeaderEntry(name, value);
|
||||
});
|
||||
} else {
|
||||
// Process array of form ['key', 'value', 'key2', 'value2']
|
||||
unsigned arrayLength = headersArray->length();
|
||||
if (arrayLength % 2 == 0) {
|
||||
for (unsigned i = 0; i < arrayLength; i += 2) {
|
||||
JSValue nameValue = headersArray->getIndex(globalObject, i);
|
||||
JSValue valueValue = headersArray->getIndex(globalObject, i + 1);
|
||||
|
||||
if (UNLIKELY(!nameValue.isString() || !valueValue.isString()))
|
||||
continue;
|
||||
|
||||
auto entryScope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
String name = nameValue.toWTFString(globalObject);
|
||||
if (UNLIKELY(entryScope.exception()))
|
||||
continue;
|
||||
|
||||
String value = valueValue.toWTFString(globalObject);
|
||||
if (UNLIKELY(entryScope.exception()))
|
||||
continue;
|
||||
|
||||
addHeaderEntry(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set of header names that should only use the first value when multiple values are sent
|
||||
// These are based on RFC2616 and the multipleForbidden list in Node.js HTTP tests
|
||||
static const HashSet<String> singleValueHeaders = {
|
||||
"host"_s,
|
||||
"content-type"_s,
|
||||
"user-agent"_s,
|
||||
"referer"_s,
|
||||
"authorization"_s,
|
||||
"proxy-authorization"_s,
|
||||
"if-modified-since"_s,
|
||||
"if-unmodified-since"_s,
|
||||
"from"_s,
|
||||
"location"_s,
|
||||
"max-forwards"_s
|
||||
};
|
||||
|
||||
for (auto& entry : headerMap) {
|
||||
const String& lowercaseName = entry.key;
|
||||
const HeaderEntry& headerEntry = entry.value;
|
||||
const Vector<String>& values = headerEntry.values;
|
||||
|
||||
if (UNLIKELY(values.isEmpty()))
|
||||
continue;
|
||||
|
||||
String headerName = headerEntry.originalName;
|
||||
String headerValue;
|
||||
|
||||
// Headers that should only use the first value
|
||||
if (singleValueHeaders.contains(lowercaseName)) {
|
||||
headerValue = values[0];
|
||||
} else if (lowercaseName == "cookie") {
|
||||
// Cookie headers use semicolon+space as separator
|
||||
headerValue = joinHeaderValues(values, ASCIILiteral("; "));
|
||||
} else {
|
||||
// All other headers use comma+space as separator
|
||||
headerValue = joinHeaderValues(values, ASCIILiteral(", "));
|
||||
}
|
||||
|
||||
targetObject->putDirect(vm, Identifier::fromString(vm, headerName), jsString(vm, headerValue), 0);
|
||||
}
|
||||
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsHTTPSetHeader, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
@@ -1353,6 +1509,9 @@ JSValue createNodeHTTPInternalBinding(Zig::GlobalObject* globalObject)
|
||||
obj->putDirect(
|
||||
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "assignEventCallback"_s)),
|
||||
JSC::JSFunction::create(vm, globalObject, 2, "assignEventCallback"_s, jsHTTPAssignEventCallback, ImplementationVisibility::Public), 0);
|
||||
obj->putDirect(
|
||||
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "processArrayHeaders"_s)),
|
||||
JSC::JSFunction::create(vm, globalObject, 2, "processArrayHeaders"_s, jsHTTPProcessArrayHeaders, ImplementationVisibility::Public), 0);
|
||||
|
||||
obj->putDirect(
|
||||
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "setRequestTimeout"_s)),
|
||||
|
||||
@@ -1760,7 +1760,8 @@ void WebCore__FetchHeaders__copyTo(WebCore__FetchHeaders* headers, StringPointer
|
||||
*values = { i, value.length() };
|
||||
i += value.length();
|
||||
} else {
|
||||
ASSERT_WITH_MESSAGE(value.containsOnlyASCII(), "Header value must be ASCII. This should already be validated before calling this function.");
|
||||
// HTTP headers can contain non-ASCII characters according to RFC 7230
|
||||
// Non-ASCII content should be properly encoded
|
||||
WTF::CString valueCString = value.utf8();
|
||||
memcpy(&buf[i], valueCString.data(), valueCString.length());
|
||||
*values = { i, static_cast<uint32_t>(valueCString.length()) };
|
||||
|
||||
@@ -714,11 +714,13 @@ pub const Response = struct {
|
||||
|
||||
if (response_init.fastGet(globalThis, .status)) |status_value| {
|
||||
const number = status_value.coerceToInt64(globalThis);
|
||||
if ((200 <= number and number < 600) or number == 101) {
|
||||
// Even though the fetch spec says the range is [200, 599], there are some websites
|
||||
// that use status codes up to 999, like linkedin.com, so allow it.
|
||||
if ((200 <= number and number <= 999) or number == 101) {
|
||||
result.status_code = @as(u16, @truncate(@as(u32, @intCast(number))));
|
||||
} else {
|
||||
if (!globalThis.hasException()) {
|
||||
const err = globalThis.createRangeErrorInstance("The status provided ({d}) must be 101 or in the range of [200, 599]", .{number});
|
||||
const err = globalThis.createRangeErrorInstance("The status provided ({d}) must be 101 or in the range of [200, 999]", .{number});
|
||||
return globalThis.throwValue(err);
|
||||
}
|
||||
return error.JSError;
|
||||
|
||||
@@ -15,6 +15,9 @@ function urlToHttpOptions(url) {
|
||||
if (url.username || url.password) {
|
||||
options.auth = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`;
|
||||
}
|
||||
if ("headers" in url) {
|
||||
options.headers = url.headers;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ const enum NodeHTTPIncomingRequestType {
|
||||
NodeHTTPResponse,
|
||||
}
|
||||
const enum NodeHTTPHeaderState {
|
||||
none,
|
||||
assigned,
|
||||
sent,
|
||||
none = 0,
|
||||
assigned = 1 << 0,
|
||||
sent = 1 << 1,
|
||||
}
|
||||
const enum NodeHTTPBodyReadState {
|
||||
none,
|
||||
@@ -61,6 +61,7 @@ const kInternalSocketData = Symbol.for("::bunternal::");
|
||||
const serverSymbol = Symbol.for("::bunternal::");
|
||||
const kPendingCallbacks = Symbol("pendingCallbacks");
|
||||
const kRequest = Symbol("request");
|
||||
const kTrailers = Symbol("kTrailers");
|
||||
|
||||
const kEmptyObject = Object.freeze(Object.create(null));
|
||||
|
||||
@@ -90,6 +91,7 @@ const {
|
||||
webRequestOrResponseHasBodyValue,
|
||||
getCompleteWebRequestOrResponseBodyValueAsArrayBuffer,
|
||||
drainMicrotasks,
|
||||
processArrayHeaders,
|
||||
} = $cpp("NodeHTTP.cpp", "createNodeHTTPInternalBinding") as {
|
||||
getHeader: (headers: Headers, name: string) => string | undefined;
|
||||
setHeader: (headers: Headers, name: string, value: string) => void;
|
||||
@@ -97,6 +99,7 @@ const {
|
||||
assignEventCallback: (req: Request, callback: (event: number) => void) => void;
|
||||
setRequestTimeout: (req: Request, timeout: number) => void;
|
||||
setServerIdleTimeout: (server: any, timeout: number) => void;
|
||||
processArrayHeaders: (headersArray: any[], targetObj: Record<string, string>) => void;
|
||||
Response: (typeof globalThis)["Response"];
|
||||
Request: (typeof globalThis)["Request"];
|
||||
Headers: (typeof globalThis)["Headers"];
|
||||
@@ -130,7 +133,7 @@ function checkInvalidHeaderChar(val: string) {
|
||||
return RegExpPrototypeExec.$call(headerCharRegex, val) !== null;
|
||||
}
|
||||
|
||||
const validateHeaderName = (name, label) => {
|
||||
const validateHeaderName = name => {
|
||||
if (typeof name !== "string" || !name || !checkIsHttpToken(name)) {
|
||||
throw $ERR_INVALID_HTTP_TOKEN(`The arguments Header name is invalid. Received ${name}`);
|
||||
}
|
||||
@@ -193,6 +196,16 @@ function validateMsecs(numberlike: any, field: string) {
|
||||
throw $ERR_INVALID_ARG_TYPE(field, "number", numberlike);
|
||||
}
|
||||
|
||||
// Ensure that msecs fits into signed int32
|
||||
const TIMEOUT_MAX = 2 ** 31 - 1;
|
||||
if (numberlike > TIMEOUT_MAX) {
|
||||
process.emitWarning(
|
||||
`${numberlike} does not fit into a 32-bit signed integer.` + `\nTimer duration was truncated to ${TIMEOUT_MAX}.`,
|
||||
"TimeoutOverflowWarning",
|
||||
);
|
||||
return TIMEOUT_MAX;
|
||||
}
|
||||
|
||||
return numberlike;
|
||||
}
|
||||
|
||||
@@ -559,7 +572,7 @@ Agent.prototype.createConnection = function () {
|
||||
};
|
||||
|
||||
Agent.prototype.getName = function (options = kEmptyObject) {
|
||||
let name = `http:${options.host || "localhost"}:`;
|
||||
let name = `${options.host || "localhost"}:`;
|
||||
if (options.port) name += options.port;
|
||||
name += ":";
|
||||
if (options.localAddress) name += options.localAddress;
|
||||
@@ -617,7 +630,7 @@ const Server = function Server(options, callback) {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
options = {};
|
||||
} else if (options == null || typeof options === "object") {
|
||||
} else if (options == null || (typeof options === "object" && !Array.isArray(options))) {
|
||||
options = { ...options };
|
||||
this[tlsSymbol] = null;
|
||||
let key = options.key;
|
||||
@@ -676,7 +689,7 @@ const Server = function Server(options, callback) {
|
||||
this[tlsSymbol] = null;
|
||||
}
|
||||
} else {
|
||||
throw new Error("bun-http-polyfill: invalid arguments");
|
||||
throw $ERR_INVALID_ARG_TYPE("options", "object or function", options);
|
||||
}
|
||||
|
||||
this[optionsSymbol] = options;
|
||||
@@ -755,6 +768,7 @@ const ServerPrototype = {
|
||||
return;
|
||||
}
|
||||
this[serverSymbol] = undefined;
|
||||
this.listening = false;
|
||||
if (typeof optionalCallback === "function") this.once("close", optionalCallback);
|
||||
server.stop();
|
||||
},
|
||||
@@ -1237,6 +1251,7 @@ function IncomingMessage(req, defaultIncomingOpts) {
|
||||
this._dumped = false;
|
||||
this.complete = false;
|
||||
this._closed = false;
|
||||
this[kTrailers] = null;
|
||||
|
||||
// (url, method, headers, rawHeaders, handle, hasBody)
|
||||
if (req === kHandle) {
|
||||
@@ -1500,10 +1515,34 @@ const IncomingMessagePrototype = {
|
||||
// noop
|
||||
},
|
||||
get trailers() {
|
||||
return kEmptyObject;
|
||||
if (!this[kTrailers]) {
|
||||
this[kTrailers] = Object.create(null);
|
||||
}
|
||||
return this[kTrailers];
|
||||
},
|
||||
set trailers(value) {
|
||||
// noop
|
||||
this[kTrailers] = value;
|
||||
},
|
||||
_addHeaderLines(headers, n) {
|
||||
if (headers?.length) {
|
||||
let dest;
|
||||
if (this.complete) {
|
||||
dest = this.trailers;
|
||||
} else {
|
||||
dest = this.headers;
|
||||
}
|
||||
|
||||
if (dest) {
|
||||
for (let i = 0; i < n; i += 2) {
|
||||
this._addHeaderLine(headers[i], headers[i + 1], dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_addHeaderLine(field, value, dest) {
|
||||
if (dest[field] === undefined) {
|
||||
dest[field] = value;
|
||||
}
|
||||
},
|
||||
setTimeout(msecs, callback) {
|
||||
this.take;
|
||||
@@ -1602,6 +1641,23 @@ const OutgoingMessagePrototype = {
|
||||
usesChunkedEncodingByDefault: true,
|
||||
_closed: false,
|
||||
|
||||
get _headerNames() {
|
||||
process.emitWarning("OutgoingMessage.prototype._headerNames is deprecated", "DeprecationWarning", "DEP0066");
|
||||
|
||||
const headers = this[headersSymbol];
|
||||
if (!headers) return null;
|
||||
|
||||
const out = Object.create(null);
|
||||
for (const key of headers.keys()) {
|
||||
out[key.toLowerCase()] = key;
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
set _headerNames(val) {
|
||||
process.emitWarning("OutgoingMessage.prototype._headerNames is deprecated", "DeprecationWarning", "DEP0066");
|
||||
},
|
||||
|
||||
appendHeader(name, value) {
|
||||
var headers = (this[headersSymbol] ??= new Headers());
|
||||
headers.append(name, value);
|
||||
@@ -1658,7 +1714,7 @@ const OutgoingMessagePrototype = {
|
||||
},
|
||||
|
||||
removeHeader(name) {
|
||||
if (this[headerStateSymbol] === NodeHTTPHeaderState.sent) {
|
||||
if (this[headerStateSymbol] >= NodeHTTPHeaderState.assigned) {
|
||||
throw $ERR_HTTP_HEADERS_SENT("Cannot remove header after headers have been sent.");
|
||||
}
|
||||
const headers = this[headersSymbol];
|
||||
@@ -1668,11 +1724,48 @@ const OutgoingMessagePrototype = {
|
||||
|
||||
setHeader(name, value) {
|
||||
validateHeaderName(name);
|
||||
validateHeaderValue(name, value);
|
||||
|
||||
const headers = (this[headersSymbol] ??= new Headers());
|
||||
setHeader(headers, name, value);
|
||||
return this;
|
||||
},
|
||||
|
||||
setHeaders(headers) {
|
||||
if (this[headerStateSymbol] >= NodeHTTPHeaderState.assigned) {
|
||||
throw $ERR_HTTP_HEADERS_SENT("set");
|
||||
}
|
||||
|
||||
if (!headers || Array.isArray(headers) || typeof headers.keys !== "function" || typeof headers.get !== "function") {
|
||||
throw $ERR_INVALID_ARG_TYPE("headers", ["Headers", "Map"], headers);
|
||||
}
|
||||
|
||||
// Headers object joins multiple cookies with a comma when using
|
||||
// the getter to retrieve the value,
|
||||
// unless iterating over the headers directly.
|
||||
// We also cannot safely split by comma.
|
||||
// To avoid setHeader overwriting the previous value we push
|
||||
// set-cookie values in array and set them all at once.
|
||||
const cookies = [];
|
||||
|
||||
for (const [key, value] of headers) {
|
||||
if (key === "set-cookie") {
|
||||
if (Array.isArray(value)) {
|
||||
cookies.push(...value);
|
||||
} else {
|
||||
cookies.push(value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
this.setHeader(key, value);
|
||||
}
|
||||
if (cookies.length) {
|
||||
this.setHeader("set-cookie", cookies);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
hasHeader(name) {
|
||||
const headers = this[headersSymbol];
|
||||
if (!headers) return false;
|
||||
@@ -1701,6 +1794,10 @@ const OutgoingMessagePrototype = {
|
||||
// even if it will be rescheduled we don't want to leak an existing timer.
|
||||
clearTimeout(this[timeoutTimerSymbol]);
|
||||
|
||||
if (callback) {
|
||||
this.on("timeout", callback);
|
||||
}
|
||||
|
||||
if (msecs === 0) {
|
||||
if (callback != null) {
|
||||
if (!$isCallable(callback)) validateFunction(callback, "callback");
|
||||
@@ -1711,9 +1808,13 @@ const OutgoingMessagePrototype = {
|
||||
} else {
|
||||
this[timeoutTimerSymbol] = setTimeout(onTimeout.bind(this), msecs).unref();
|
||||
|
||||
if (callback != null) {
|
||||
if (!$isCallable(callback)) validateFunction(callback, "callback");
|
||||
this.once("timeout", callback);
|
||||
// Node.js compatibility: also delegate to socket if available
|
||||
if (!this[fakeSocketSymbol]) {
|
||||
this.once("socket", function socketSetTimeoutOnConnect(socket) {
|
||||
socket.setTimeout(msecs);
|
||||
});
|
||||
} else {
|
||||
this[fakeSocketSymbol].setTimeout(msecs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1730,7 +1831,11 @@ const OutgoingMessagePrototype = {
|
||||
},
|
||||
|
||||
set socket(value) {
|
||||
const prev = this[fakeSocketSymbol];
|
||||
this[fakeSocketSymbol] = value;
|
||||
if (!prev && value) {
|
||||
this.emit("socket", value);
|
||||
}
|
||||
},
|
||||
|
||||
get chunkedEncoding() {
|
||||
@@ -1797,7 +1902,7 @@ function onNodeHTTPServerSocketTimeout() {
|
||||
if (!reqTimeout && !resTimeout && !serverTimeout) this.destroy();
|
||||
}
|
||||
|
||||
function onTimeout() {
|
||||
function handleRequestTimeout() {
|
||||
this[timeoutTimerSymbol] = undefined;
|
||||
this[kAbortController]?.abort();
|
||||
const handle = this[kHandle];
|
||||
@@ -1920,9 +2025,7 @@ const ServerResponsePrototype = {
|
||||
_removedContLen: false,
|
||||
_hasBody: true,
|
||||
get headersSent() {
|
||||
return (
|
||||
this[headerStateSymbol] === NodeHTTPHeaderState.sent || this[headerStateSymbol] === NodeHTTPHeaderState.assigned
|
||||
);
|
||||
return this[headerStateSymbol] >= NodeHTTPHeaderState.assigned;
|
||||
},
|
||||
set headersSent(value) {
|
||||
this[headerStateSymbol] = value ? NodeHTTPHeaderState.sent : NodeHTTPHeaderState.none;
|
||||
@@ -1993,6 +2096,12 @@ const ServerResponsePrototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// Update bytesWritten on the socket to ensure res.connection.bytesWritten works
|
||||
if (chunk && this.socket) {
|
||||
const byteLength = chunk instanceof Buffer ? chunk.length : Buffer.byteLength(chunk, encoding || "utf8");
|
||||
this.socket.bytesWritten += byteLength;
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
const headerState = this[headerStateSymbol];
|
||||
callWriteHeadIfObservable(this, headerState);
|
||||
@@ -2097,6 +2206,12 @@ const ServerResponsePrototype = {
|
||||
result = handle.write(chunk, encoding);
|
||||
}
|
||||
|
||||
// Update bytesWritten on the socket to ensure res.connection.bytesWritten works
|
||||
if (chunk && this.socket) {
|
||||
const byteLength = chunk instanceof Buffer ? chunk.length : Buffer.byteLength(chunk, encoding || "utf8");
|
||||
this.socket.bytesWritten += byteLength;
|
||||
}
|
||||
|
||||
if (result < 0) {
|
||||
if (callback) {
|
||||
// The write was buffered due to backpressure.
|
||||
@@ -2186,6 +2301,13 @@ const ServerResponsePrototype = {
|
||||
} else {
|
||||
handle.write(data, encoding, callback);
|
||||
}
|
||||
|
||||
// Update bytesWritten on the socket to ensure res.connection.bytesWritten works
|
||||
if (data && this.socket) {
|
||||
const dataByteLength =
|
||||
byteLength || (data instanceof Buffer ? data.length : Buffer.byteLength(data, encoding || "utf8"));
|
||||
this.socket.bytesWritten += dataByteLength;
|
||||
}
|
||||
},
|
||||
|
||||
writeHead(statusCode, statusMessage, headers) {
|
||||
@@ -2193,6 +2315,43 @@ const ServerResponsePrototype = {
|
||||
_writeHead(statusCode, statusMessage, headers, this);
|
||||
updateHasBody(this, statusCode);
|
||||
this[headerStateSymbol] = NodeHTTPHeaderState.assigned;
|
||||
} else {
|
||||
throw $ERR_HTTP_HEADERS_SENT("write");
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
setHeaders(headers) {
|
||||
if (this[headerStateSymbol] >= NodeHTTPHeaderState.assigned) {
|
||||
throw $ERR_HTTP_HEADERS_SENT("set");
|
||||
}
|
||||
|
||||
if (!headers || Array.isArray(headers) || typeof headers.keys !== "function" || typeof headers.get !== "function") {
|
||||
throw $ERR_INVALID_ARG_TYPE("headers", ["Headers", "Map"], headers);
|
||||
}
|
||||
|
||||
// Headers object joins multiple cookies with a comma when using
|
||||
// the getter to retrieve the value,
|
||||
// unless iterating over the headers directly.
|
||||
// We also cannot safely split by comma.
|
||||
// To avoid setHeader overwriting the previous value we push
|
||||
// set-cookie values in array and set them all at once.
|
||||
const cookies = [];
|
||||
|
||||
for (const [key, value] of headers) {
|
||||
if (key === "set-cookie") {
|
||||
if (Array.isArray(value)) {
|
||||
cookies.push(...value);
|
||||
} else {
|
||||
cookies.push(value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
this.setHeader(key, value);
|
||||
}
|
||||
if (cookies.length) {
|
||||
this.setHeader("set-cookie", cookies);
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -2649,7 +2808,15 @@ function ClientRequest(input, options, cb) {
|
||||
url = path;
|
||||
proxy = `${protocol}//${host}${this[kUseDefaultPort] ? "" : ":" + this[kPort]}`;
|
||||
} else {
|
||||
url = `${protocol}//${host}${this[kUseDefaultPort] ? "" : ":" + this[kPort]}${path}`;
|
||||
// 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)));
|
||||
|
||||
url = `${protocol}//${host}${includePort ? ":" + this[kPort] : ""}${path}`;
|
||||
// support agent proxy url/string for http/https
|
||||
try {
|
||||
// getters can throw
|
||||
@@ -2922,9 +3089,13 @@ function ClientRequest(input, options, cb) {
|
||||
}
|
||||
}
|
||||
|
||||
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 =
|
||||
@@ -3071,56 +3242,74 @@ function ClientRequest(input, options, cb) {
|
||||
|
||||
const { headers } = options;
|
||||
const headersArray = $isJSArray(headers);
|
||||
if (!headersArray) {
|
||||
if (headers) {
|
||||
for (let key in headers) {
|
||||
this.setHeader(key, headers[key]);
|
||||
}
|
||||
if (headersArray) {
|
||||
// Use the native implementation to process array headers efficiently
|
||||
// This will correctly handle array style headers:
|
||||
// - Join multiple header values with commas (except cookies with semicolons)
|
||||
// - Only use the first host header value
|
||||
const processedHeaders = {};
|
||||
processArrayHeaders(headers, processedHeaders);
|
||||
|
||||
// Now set all processed headers on this request
|
||||
for (const key in processedHeaders) {
|
||||
this.setHeader(key, processedHeaders[key]);
|
||||
}
|
||||
|
||||
// if (host && !this.getHeader("host") && setHost) {
|
||||
// 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}]`;
|
||||
// }
|
||||
|
||||
// if (port && +port !== defaultPort) {
|
||||
// hostHeader += ":" + port;
|
||||
// }
|
||||
// this.setHeader("Host", hostHeader);
|
||||
// }
|
||||
|
||||
var auth = options.auth;
|
||||
if (auth && !this.getHeader("Authorization")) {
|
||||
this.setHeader("Authorization", "Basic " + Buffer.from(auth).toString("base64"));
|
||||
} else if (headers) {
|
||||
// Handle headers as an object
|
||||
for (let key in headers) {
|
||||
this.setHeader(key, headers[key]);
|
||||
}
|
||||
|
||||
// if (this.getHeader("expect")) {
|
||||
// if (this._header) {
|
||||
// throw new ERR_HTTP_HEADERS_SENT("render");
|
||||
// }
|
||||
|
||||
// this._storeHeader(
|
||||
// this.method + " " + this.path + " HTTP/1.1\r\n",
|
||||
// this[kOutHeaders],
|
||||
// );
|
||||
// }
|
||||
// } else {
|
||||
// this._storeHeader(
|
||||
// this.method + " " + this.path + " HTTP/1.1\r\n",
|
||||
// options.headers,
|
||||
// );
|
||||
}
|
||||
|
||||
// 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}]`;
|
||||
}
|
||||
|
||||
// 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")) {
|
||||
this.setHeader("Authorization", "Basic " + Buffer.from(auth).toString("base64"));
|
||||
}
|
||||
|
||||
// if (this.getHeader("expect")) {
|
||||
// if (this._header) {
|
||||
// throw new ERR_HTTP_HEADERS_SENT("render");
|
||||
// }
|
||||
|
||||
// this._storeHeader(
|
||||
// this.method + " " + this.path + " HTTP/1.1\r\n",
|
||||
// this[kOutHeaders],
|
||||
// );
|
||||
// }
|
||||
// } else {
|
||||
// this._storeHeader(
|
||||
// this.method + " " + this.path + " HTTP/1.1\r\n",
|
||||
// options.headers,
|
||||
// );
|
||||
|
||||
// this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders);
|
||||
|
||||
const { signal: _signal, ...optsWithoutSignal } = options;
|
||||
@@ -3188,6 +3377,20 @@ const ClientRequestPrototype = {
|
||||
constructor: ClientRequest,
|
||||
__proto__: OutgoingMessage.prototype,
|
||||
|
||||
clearTimeout(callback) {
|
||||
const timeoutTimer = this[kTimeoutTimer];
|
||||
if (timeoutTimer) {
|
||||
clearTimeout(timeoutTimer);
|
||||
this[kTimeoutTimer] = undefined;
|
||||
if (callback) {
|
||||
this.removeListener("timeout", callback);
|
||||
} else {
|
||||
this.removeAllListeners("timeout");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
get path() {
|
||||
return this[kPath];
|
||||
},
|
||||
@@ -3418,7 +3621,7 @@ function _writeHead(statusCode, reason, obj, response) {
|
||||
let k;
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length % 2 !== 0) {
|
||||
throw new Error("raw headers must have an even number of elements");
|
||||
throw $ERR_INVALID_ARG_VALUE("headers", obj, "must be an object or an array with even number of elements");
|
||||
}
|
||||
|
||||
for (let n = 0; n < obj.length; n += 2) {
|
||||
|
||||
@@ -125,6 +125,16 @@ const testCases: TestCase[] = [
|
||||
description: "Valid GET request with HTTP/1.0",
|
||||
expectedStatus: [[200, 299]],
|
||||
},
|
||||
{
|
||||
request: "GET / HTTP/1.0\r\n\r\n",
|
||||
description: "Valid GET request with HTTP/1.0 and no Host header",
|
||||
expectedStatus: [[200, 299]],
|
||||
},
|
||||
{
|
||||
request: "GET / HTTP/1.0\r\n\r\n",
|
||||
description: "Valid GET request with HTTP/1.0 and no headers",
|
||||
expectedStatus: [[200, 299]],
|
||||
},
|
||||
{
|
||||
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
|
||||
description: "Valid GET request for a proxy URL",
|
||||
@@ -155,6 +165,11 @@ const testCases: TestCase[] = [
|
||||
description: "Valid GET request for an HTTP proxy URL",
|
||||
expectedStatus: [[200, 299]],
|
||||
},
|
||||
{
|
||||
request: "GET HTTP://example.com/ HTTP/1.1\r\n\r\n",
|
||||
description: "Invalid GET request with HTTP/1.1 and no headers",
|
||||
expectedStatus: [[400, 499]],
|
||||
},
|
||||
{
|
||||
request: "GET HTTP/1.1\r\nHost: example.com\r\n\r\n",
|
||||
description: "Invalid GET request target (space)",
|
||||
|
||||
@@ -303,7 +303,7 @@ describe.todoIf(isBroken && isIntelMacOS)(
|
||||
},
|
||||
);
|
||||
|
||||
[200, 200n, 303, 418, 599, 599n].forEach(statusCode => {
|
||||
[200, 200n, 303, 418, 599, 599n, 999, 999n].forEach(statusCode => {
|
||||
it(`should response with HTTP status code (${statusCode})`, async () => {
|
||||
await runTest(
|
||||
{
|
||||
@@ -320,7 +320,7 @@ describe.todoIf(isBroken && isIntelMacOS)(
|
||||
});
|
||||
});
|
||||
|
||||
[-200, 42, 100, 102, 12345, Math.PI, 999, 600, 199, 199n, 600n, 100n, 102n].forEach(statusCode => {
|
||||
[-200, 42, 100, 102, 12345, Math.PI, 199, 199n, 100n, 102n].forEach(statusCode => {
|
||||
it(`should error on invalid HTTP status code (${statusCode})`, async () => {
|
||||
await runTest(
|
||||
{
|
||||
|
||||
55
test/js/node/test/parallel/test-http-agent-getname.js
Normal file
55
test/js/node/test/parallel/test-http-agent-getname.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
const agent = new http.Agent();
|
||||
|
||||
// Default to localhost
|
||||
assert.strictEqual(
|
||||
agent.getName({
|
||||
port: 80,
|
||||
localAddress: '192.168.1.1'
|
||||
}),
|
||||
'localhost:80:192.168.1.1'
|
||||
);
|
||||
|
||||
// empty argument
|
||||
assert.strictEqual(
|
||||
agent.getName(),
|
||||
'localhost::'
|
||||
);
|
||||
|
||||
// empty options
|
||||
assert.strictEqual(
|
||||
agent.getName({}),
|
||||
'localhost::'
|
||||
);
|
||||
|
||||
// pass all arguments
|
||||
assert.strictEqual(
|
||||
agent.getName({
|
||||
host: '0.0.0.0',
|
||||
port: 80,
|
||||
localAddress: '192.168.1.1'
|
||||
}),
|
||||
'0.0.0.0:80:192.168.1.1'
|
||||
);
|
||||
|
||||
// unix socket
|
||||
const socketPath = tmpdir.resolve('foo', 'bar');
|
||||
assert.strictEqual(
|
||||
agent.getName({
|
||||
socketPath
|
||||
}),
|
||||
`localhost:::${socketPath}`
|
||||
);
|
||||
|
||||
for (const family of [0, null, undefined, 'bogus'])
|
||||
assert.strictEqual(agent.getName({ family }), 'localhost::');
|
||||
|
||||
for (const family of [4, 6])
|
||||
assert.strictEqual(agent.getName({ family }), `localhost:::${family}`);
|
||||
55
test/js/node/test/parallel/test-http-byteswritten.js
Normal file
55
test/js/node/test/parallel/test-http-byteswritten.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const body = 'hello world\n';
|
||||
|
||||
const httpServer = http.createServer(common.mustCall(function(req, res) {
|
||||
httpServer.close();
|
||||
|
||||
res.on('finish', common.mustCall(function() {
|
||||
assert.strictEqual(typeof req.connection.bytesWritten, 'number');
|
||||
assert(req.connection.bytesWritten > 0);
|
||||
}));
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
|
||||
// Write 1.5mb to cause some requests to buffer
|
||||
// Also, mix up the encodings a bit.
|
||||
const chunk = '7'.repeat(1024);
|
||||
const bchunk = Buffer.from(chunk);
|
||||
for (let i = 0; i < 1024; i++) {
|
||||
res.write(chunk);
|
||||
res.write(bchunk);
|
||||
res.write(chunk, 'hex');
|
||||
}
|
||||
// Get .bytesWritten while buffer is not empty
|
||||
assert(res.connection.bytesWritten > 0);
|
||||
|
||||
res.end(body);
|
||||
}));
|
||||
|
||||
httpServer.listen(0, function() {
|
||||
http.get({ port: this.address().port });
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('node:assert');
|
||||
const http = require('node:http');
|
||||
|
||||
const headers = { foo: 'Bar' };
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
assert.strictEqual(req.url, '/ping?q=term');
|
||||
assert.strictEqual(req.headers?.foo, headers.foo);
|
||||
req.resume();
|
||||
req.on('end', () => {
|
||||
res.writeHead(200);
|
||||
res.end('pong');
|
||||
});
|
||||
}));
|
||||
|
||||
server.listen(0, common.localhostIPv4, () => {
|
||||
const { address, port } = server.address();
|
||||
const url = new URL(`http://${address}:${port}/ping?q=term`);
|
||||
url.headers = headers;
|
||||
const clientReq = http.request(url);
|
||||
clientReq.on('close', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
clientReq.end();
|
||||
});
|
||||
23
test/js/node/test/parallel/test-http-correct-hostname.js
Normal file
23
test/js/node/test/parallel/test-http-correct-hostname.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/* eslint-disable node-core/crypto-check */
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const http = require('http');
|
||||
const modules = { http };
|
||||
|
||||
if (common.hasCrypto) {
|
||||
const https = require('https');
|
||||
modules.https = https;
|
||||
}
|
||||
|
||||
Object.keys(modules).forEach((module) => {
|
||||
const doNotCall = common.mustNotCall(
|
||||
`${module}.request should not connect to ${module}://example.com%60x.example.com`
|
||||
);
|
||||
const req = modules[module].request(`${module}://example.com%60x.example.com`, doNotCall);
|
||||
assert.equal(req.headers.host, 'example.com`x.example.com');
|
||||
req.abort();
|
||||
});
|
||||
60
test/js/node/test/parallel/test-http-default-port.js
Normal file
60
test/js/node/test/parallel/test-http-default-port.js
Normal 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();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
21
test/js/node/test/parallel/test-http-header-obstext.js
Normal file
21
test/js/node/test/parallel/test-http-header-obstext.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// This test ensures that the http-parser can handle UTF-8 characters
|
||||
// in the http header.
|
||||
|
||||
const http = require('http');
|
||||
const assert = require('assert');
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('ok');
|
||||
}));
|
||||
server.listen(0, () => {
|
||||
http.get({
|
||||
port: server.address().port,
|
||||
headers: { 'Test': 'Düsseldorf' }
|
||||
}, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
16
test/js/node/test/parallel/test-http-listening.js
Normal file
16
test/js/node/test/parallel/test-http-listening.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer();
|
||||
|
||||
assert.strictEqual(server.listening, false);
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
assert.strictEqual(server.listening, true);
|
||||
|
||||
server.close(common.mustCall(() => {
|
||||
assert.strictEqual(server.listening, false);
|
||||
}));
|
||||
}));
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
const { OutgoingMessage } = require('http');
|
||||
|
||||
const warn = 'OutgoingMessage.prototype._headerNames is deprecated';
|
||||
common.expectWarning('DeprecationWarning', warn, 'DEP0066');
|
||||
|
||||
{
|
||||
// Tests for _headerNames set method
|
||||
const outgoingMessage = new OutgoingMessage();
|
||||
outgoingMessage._headerNames = {
|
||||
'x-flow-id': '61bba6c5-28a3-4eab-9241-2ecaa6b6a1fd'
|
||||
};
|
||||
}
|
||||
30
test/js/node/test/parallel/test-http-outgoing-settimeout.js
Normal file
30
test/js/node/test/parallel/test-http-outgoing-settimeout.js
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const { OutgoingMessage } = require('http');
|
||||
|
||||
{
|
||||
// Tests for settimeout method with socket
|
||||
const expectedMsecs = 42;
|
||||
const outgoingMessage = new OutgoingMessage();
|
||||
outgoingMessage.socket = {
|
||||
setTimeout: common.mustCall((msecs) => {
|
||||
assert.strictEqual(msecs, expectedMsecs);
|
||||
})
|
||||
};
|
||||
outgoingMessage.setTimeout(expectedMsecs);
|
||||
}
|
||||
|
||||
{
|
||||
// Tests for settimeout method without socket
|
||||
const expectedMsecs = 23;
|
||||
const outgoingMessage = new OutgoingMessage();
|
||||
outgoingMessage.setTimeout(expectedMsecs);
|
||||
|
||||
outgoingMessage.emit('socket', {
|
||||
setTimeout: common.mustCall((msecs) => {
|
||||
assert.strictEqual(msecs, expectedMsecs);
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
|
||||
const server = http.createServer(common.mustCall(function(req, res) {
|
||||
res.writeHead(200);
|
||||
res.end('ok');
|
||||
}));
|
||||
|
||||
server.listen(0, function() {
|
||||
const agent = new http.Agent();
|
||||
agent.defaultPort = this.address().port;
|
||||
|
||||
// Options marked as explicitly undefined for readability
|
||||
// in this test, they should STAY undefined as options should not
|
||||
// be mutable / modified
|
||||
const options = {
|
||||
host: undefined,
|
||||
hostname: common.localhostIPv4,
|
||||
port: undefined,
|
||||
defaultPort: undefined,
|
||||
path: undefined,
|
||||
method: undefined,
|
||||
agent: agent
|
||||
};
|
||||
|
||||
http.request(options, function(res) {
|
||||
res.resume();
|
||||
server.close();
|
||||
assert.strictEqual(options.host, undefined);
|
||||
assert.strictEqual(options.hostname, common.localhostIPv4);
|
||||
assert.strictEqual(options.port, undefined);
|
||||
assert.strictEqual(options.defaultPort, undefined);
|
||||
assert.strictEqual(options.path, undefined);
|
||||
assert.strictEqual(options.method, undefined);
|
||||
}).end();
|
||||
});
|
||||
65
test/js/node/test/parallel/test-http-request-methods.js
Normal file
65
test/js/node/test/parallel/test-http-request-methods.js
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
const http = require('http');
|
||||
|
||||
// Test that the DELETE, PATCH and PURGE verbs get passed through correctly
|
||||
|
||||
['DELETE', 'PATCH', 'PURGE'].forEach(function(method, index) {
|
||||
const server = http.createServer(common.mustCall(function(req, res) {
|
||||
assert.strictEqual(req.method, method);
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.write('hello ');
|
||||
res.write('world\n');
|
||||
res.end();
|
||||
}));
|
||||
server.listen(0);
|
||||
|
||||
server.on('listening', common.mustCall(function() {
|
||||
const c = net.createConnection(this.address().port);
|
||||
let server_response = '';
|
||||
|
||||
c.setEncoding('utf8');
|
||||
|
||||
c.on('connect', function() {
|
||||
c.write(`${method} / HTTP/1.0\r\n\r\n`);
|
||||
});
|
||||
|
||||
c.on('data', function(chunk) {
|
||||
console.log(chunk);
|
||||
server_response += chunk;
|
||||
});
|
||||
|
||||
c.on('end', common.mustCall(function() {
|
||||
assert.ok(server_response.includes('hello '));
|
||||
assert.ok(server_response.includes('world\n'));
|
||||
c.end();
|
||||
}));
|
||||
|
||||
c.on('close', function() {
|
||||
server.close();
|
||||
});
|
||||
}));
|
||||
});
|
||||
48
test/js/node/test/parallel/test-http-response-no-headers.js
Normal file
48
test/js/node/test/parallel/test-http-response-no-headers.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
const expected = {
|
||||
'1.0': 'I AM THE WALRUS',
|
||||
'1.1': ''
|
||||
};
|
||||
|
||||
function test(httpVersion, callback) {
|
||||
const server = net.createServer(function(conn) {
|
||||
const reply = `HTTP/${httpVersion} 200 OK\r\n\r\n${expected[httpVersion]}`;
|
||||
|
||||
conn.end(reply);
|
||||
});
|
||||
|
||||
server.listen(0, '127.0.0.1', common.mustCall(function() {
|
||||
const options = {
|
||||
host: '127.0.0.1',
|
||||
port: this.address().port
|
||||
};
|
||||
|
||||
const req = http.get(options, common.mustCall(function(res) {
|
||||
let body = '';
|
||||
|
||||
res.on('data', function(data) {
|
||||
body += data;
|
||||
});
|
||||
|
||||
res.on('aborted', common.mustNotCall());
|
||||
res.on('end', common.mustCall(function() {
|
||||
assert.strictEqual(body, expected[httpVersion]);
|
||||
server.close();
|
||||
if (callback) process.nextTick(callback);
|
||||
}));
|
||||
}));
|
||||
|
||||
req.on('error', function(err) {
|
||||
throw err;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
test('1.0', function() {
|
||||
test('1.1');
|
||||
});
|
||||
174
test/js/node/test/parallel/test-http-response-setheaders.js
Normal file
174
test/js/node/test/parallel/test-http-response-setheaders.js
Normal file
@@ -0,0 +1,174 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const assert = require('assert');
|
||||
|
||||
{
|
||||
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
|
||||
res.writeHead(200); // Headers already sent
|
||||
const headers = new globalThis.Headers({ foo: '1' });
|
||||
assert.throws(() => {
|
||||
res.setHeaders(headers);
|
||||
}, {
|
||||
code: 'ERR_HTTP_HEADERS_SENT'
|
||||
});
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert.strictEqual(res.headers.foo, undefined);
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
|
||||
assert.throws(() => {
|
||||
res.setHeaders(['foo', '1']);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
assert.throws(() => {
|
||||
res.setHeaders({ foo: '1' });
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
assert.throws(() => {
|
||||
res.setHeaders(null);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
assert.throws(() => {
|
||||
res.setHeaders(undefined);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
assert.throws(() => {
|
||||
res.setHeaders('test');
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
assert.throws(() => {
|
||||
res.setHeaders(1);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert.strictEqual(res.headers.foo, undefined);
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
|
||||
const headers = new globalThis.Headers({ foo: '1', bar: '2' });
|
||||
res.setHeaders(headers);
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
assert.strictEqual(res.headers.foo, '1');
|
||||
assert.strictEqual(res.headers.bar, '2');
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
|
||||
const headers = new globalThis.Headers({ foo: '1', bar: '2' });
|
||||
res.setHeaders(headers);
|
||||
res.writeHead(200, ['foo', '3']);
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
assert.strictEqual(res.headers.foo, '3'); // Override by writeHead
|
||||
assert.strictEqual(res.headers.bar, '2');
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
|
||||
const headers = new Map([['foo', '1'], ['bar', '2']]);
|
||||
res.setHeaders(headers);
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
assert.strictEqual(res.headers.foo, '1');
|
||||
assert.strictEqual(res.headers.bar, '2');
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
|
||||
const headers = new Headers();
|
||||
headers.append('Set-Cookie', 'a=b');
|
||||
headers.append('Set-Cookie', 'c=d');
|
||||
res.setHeaders(headers);
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert(Array.isArray(res.headers['set-cookie']));
|
||||
assert.strictEqual(res.headers['set-cookie'].length, 2);
|
||||
assert.strictEqual(res.headers['set-cookie'][0], 'a=b');
|
||||
assert.strictEqual(res.headers['set-cookie'][1], 'c=d');
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
|
||||
const headers = new Map();
|
||||
headers.set('Set-Cookie', ['a=b', 'c=d']);
|
||||
res.setHeaders(headers);
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert(Array.isArray(res.headers['set-cookie']));
|
||||
assert.strictEqual(res.headers['set-cookie'].length, 2);
|
||||
assert.strictEqual(res.headers['set-cookie'][0], 'a=b');
|
||||
assert.strictEqual(res.headers['set-cookie'][1], 'c=d');
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
90
test/js/node/test/parallel/test-http-response-statuscode.js
Normal file
90
test/js/node/test/parallel/test-http-response-statuscode.js
Normal file
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
const MAX_REQUESTS = 13;
|
||||
let reqNum = 0;
|
||||
|
||||
function test(res, header, code) {
|
||||
assert.throws(() => {
|
||||
res.writeHead(header);
|
||||
}, {
|
||||
code: 'ERR_HTTP_INVALID_STATUS_CODE',
|
||||
name: 'RangeError',
|
||||
message: `Invalid status code: ${code}`
|
||||
});
|
||||
}
|
||||
|
||||
const server = http.Server(common.mustCall(function(req, res) {
|
||||
switch (reqNum) {
|
||||
case 0:
|
||||
test(res, -1, '-1');
|
||||
break;
|
||||
case 1:
|
||||
test(res, Infinity, 'Infinity');
|
||||
break;
|
||||
case 2:
|
||||
test(res, NaN, 'NaN');
|
||||
break;
|
||||
case 3:
|
||||
test(res, {}, '{}');
|
||||
break;
|
||||
case 4:
|
||||
test(res, 99, '99');
|
||||
break;
|
||||
case 5:
|
||||
test(res, 1000, '1000');
|
||||
break;
|
||||
case 6:
|
||||
test(res, '1000', '"1000"');
|
||||
break;
|
||||
case 7:
|
||||
test(res, null, 'null');
|
||||
break;
|
||||
case 8:
|
||||
test(res, true, 'true');
|
||||
break;
|
||||
case 9:
|
||||
test(res, [], '[]');
|
||||
break;
|
||||
case 10:
|
||||
test(res, 'this is not valid', '"this is not valid"');
|
||||
break;
|
||||
case 11:
|
||||
test(res, '404 this is not valid either', '"404 this is not valid either"');
|
||||
break;
|
||||
case 12:
|
||||
assert.throws(() => { res.writeHead(); },
|
||||
{
|
||||
code: 'ERR_HTTP_INVALID_STATUS_CODE',
|
||||
name: 'RangeError',
|
||||
message: 'Invalid status code: undefined'
|
||||
});
|
||||
this.close();
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unexpected request');
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.end();
|
||||
}, MAX_REQUESTS));
|
||||
server.listen();
|
||||
|
||||
const countdown = new Countdown(MAX_REQUESTS, () => server.close());
|
||||
|
||||
server.on('listening', function makeRequest() {
|
||||
http.get({
|
||||
port: this.address().port
|
||||
}, (res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
res.on('end', () => {
|
||||
countdown.dec();
|
||||
reqNum = MAX_REQUESTS - countdown.remaining;
|
||||
if (countdown.remaining > 0)
|
||||
makeRequest.call(this);
|
||||
});
|
||||
res.resume();
|
||||
});
|
||||
});
|
||||
80
test/js/node/test/parallel/test-http-server-multiheaders.js
Normal file
80
test/js/node/test/parallel/test-http-server-multiheaders.js
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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';
|
||||
// Verify that the HTTP server implementation handles multiple instances
|
||||
// of the same header as per RFC2616: joining the handful of fields by ', '
|
||||
// that support it, and dropping duplicates for other fields.
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer(function(req, res) {
|
||||
assert.strictEqual(req.headers.accept, 'abc, def, ghijklmnopqrst');
|
||||
assert.strictEqual(req.headers.host, 'foo');
|
||||
assert.strictEqual(req.headers['www-authenticate'], 'foo, bar, baz');
|
||||
assert.strictEqual(req.headers['proxy-authenticate'], 'foo, bar, baz');
|
||||
assert.strictEqual(req.headers['x-foo'], 'bingo');
|
||||
assert.strictEqual(req.headers['x-bar'], 'banjo, bango');
|
||||
assert.strictEqual(req.headers['sec-websocket-protocol'], 'chat, share');
|
||||
assert.strictEqual(req.headers['sec-websocket-extensions'],
|
||||
'foo; 1, bar; 2, baz');
|
||||
assert.strictEqual(req.headers.constructor, 'foo, bar, baz');
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('EOF');
|
||||
|
||||
server.close();
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
http.get({
|
||||
host: 'localhost',
|
||||
port: this.address().port,
|
||||
path: '/',
|
||||
headers: [
|
||||
['accept', 'abc'],
|
||||
['accept', 'def'],
|
||||
['Accept', 'ghijklmnopqrst'],
|
||||
['host', 'foo'],
|
||||
['Host', 'bar'],
|
||||
['hOst', 'baz'],
|
||||
['www-authenticate', 'foo'],
|
||||
['WWW-Authenticate', 'bar'],
|
||||
['WWW-AUTHENTICATE', 'baz'],
|
||||
['proxy-authenticate', 'foo'],
|
||||
['Proxy-Authenticate', 'bar'],
|
||||
['PROXY-AUTHENTICATE', 'baz'],
|
||||
['x-foo', 'bingo'],
|
||||
['x-bar', 'banjo'],
|
||||
['x-bar', 'bango'],
|
||||
['sec-websocket-protocol', 'chat'],
|
||||
['sec-websocket-protocol', 'share'],
|
||||
['sec-websocket-extensions', 'foo; 1'],
|
||||
['sec-websocket-extensions', 'bar; 2'],
|
||||
['sec-websocket-extensions', 'baz'],
|
||||
['constructor', 'foo'],
|
||||
['constructor', 'bar'],
|
||||
['constructor', 'baz'],
|
||||
]
|
||||
});
|
||||
});
|
||||
107
test/js/node/test/parallel/test-http-server-multiheaders2.js
Normal file
107
test/js/node/test/parallel/test-http-server-multiheaders2.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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';
|
||||
// Verify that the HTTP server implementation handles multiple instances
|
||||
// of the same header as per RFC2616: joining the handful of fields by ', '
|
||||
// that support it, and dropping duplicates for other fields.
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const multipleAllowed = [
|
||||
'Accept',
|
||||
'Accept-Charset',
|
||||
'Accept-Encoding',
|
||||
'Accept-Language',
|
||||
'Cookie',
|
||||
'DAV', // GH-2750
|
||||
'Pragma', // GH-715
|
||||
'Link', // GH-1187
|
||||
'WWW-Authenticate', // GH-1083
|
||||
'Proxy-Authenticate', // GH-4052
|
||||
'Sec-Websocket-Extensions', // GH-2764
|
||||
'Sec-Websocket-Protocol', // GH-2764
|
||||
'Via', // GH-6660
|
||||
|
||||
// not a special case, just making sure it's parsed correctly
|
||||
'X-Forwarded-For',
|
||||
|
||||
// Make sure that unspecified headers is treated as multiple
|
||||
'Some-Random-Header',
|
||||
'X-Some-Random-Header',
|
||||
];
|
||||
|
||||
const multipleForbidden = [
|
||||
'Content-Type',
|
||||
'User-Agent',
|
||||
'Referer',
|
||||
'Host',
|
||||
'Authorization',
|
||||
'Proxy-Authorization',
|
||||
'If-Modified-Since',
|
||||
'If-Unmodified-Since',
|
||||
'From',
|
||||
'Location',
|
||||
'Max-Forwards',
|
||||
|
||||
// Special case, tested differently
|
||||
// 'Content-Length',
|
||||
];
|
||||
|
||||
const server = http.createServer(function(req, res) {
|
||||
for (const header of multipleForbidden) {
|
||||
assert.strictEqual(req.headers[header.toLowerCase()], 'foo',
|
||||
`header parsed incorrectly: ${header}`);
|
||||
}
|
||||
for (const header of multipleAllowed) {
|
||||
const sep = (header.toLowerCase() === 'cookie' ? '; ' : ', ');
|
||||
assert.strictEqual(req.headers[header.toLowerCase()], `foo${sep}bar`,
|
||||
`header parsed incorrectly: ${header}`);
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('EOF');
|
||||
|
||||
server.close();
|
||||
});
|
||||
|
||||
function makeHeader(value) {
|
||||
return function(header) {
|
||||
return [header, value];
|
||||
};
|
||||
}
|
||||
|
||||
const headers = []
|
||||
.concat(multipleAllowed.map(makeHeader('foo')))
|
||||
.concat(multipleForbidden.map(makeHeader('foo')))
|
||||
.concat(multipleAllowed.map(makeHeader('bar')))
|
||||
.concat(multipleForbidden.map(makeHeader('bar')));
|
||||
|
||||
server.listen(0, function() {
|
||||
http.get({
|
||||
host: 'localhost',
|
||||
port: this.address().port,
|
||||
path: '/',
|
||||
headers: headers,
|
||||
});
|
||||
});
|
||||
51
test/js/node/test/parallel/test-http-timeout-overflow.js
Normal file
51
test/js/node/test/parallel/test-http-timeout-overflow.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer(common.mustCall(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('OK');
|
||||
}));
|
||||
|
||||
server.listen(0, function() {
|
||||
function callback() {}
|
||||
|
||||
const req = http.request({
|
||||
port: this.address().port,
|
||||
path: '/',
|
||||
agent: false
|
||||
}, function(res) {
|
||||
req.clearTimeout(callback);
|
||||
|
||||
res.on('end', common.mustCall(function() {
|
||||
server.close();
|
||||
}));
|
||||
|
||||
res.resume();
|
||||
});
|
||||
|
||||
// Overflow signed int32
|
||||
req.setTimeout(0xffffffff, callback);
|
||||
req.end();
|
||||
});
|
||||
78
test/js/node/test/parallel/test-http-wget.js
Normal file
78
test/js/node/test/parallel/test-http-wget.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
const http = require('http');
|
||||
|
||||
// `wget` sends an HTTP/1.0 request with Connection: Keep-Alive
|
||||
//
|
||||
// Sending back a chunked response to an HTTP/1.0 client would be wrong,
|
||||
// so what has to happen in this case is that the connection is closed
|
||||
// by the server after the entity body if the Content-Length was not
|
||||
// sent.
|
||||
//
|
||||
// If the Content-Length was sent, we can probably safely honor the
|
||||
// keep-alive request, even though HTTP 1.0 doesn't say that the
|
||||
// connection can be kept open. Presumably any client sending this
|
||||
// header knows that it is extending HTTP/1.0 and can handle the
|
||||
// response. We don't test that here however, just that if the
|
||||
// content-length is not provided, that the connection is in fact
|
||||
// closed.
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.write('hello ');
|
||||
res.write('world\n');
|
||||
res.end();
|
||||
});
|
||||
server.listen(0);
|
||||
|
||||
server.on('listening', common.mustCall(() => {
|
||||
const c = net.createConnection(server.address().port);
|
||||
let server_response = '';
|
||||
|
||||
c.setEncoding('utf8');
|
||||
|
||||
c.on('connect', () => {
|
||||
c.write('GET / HTTP/1.0\r\n' +
|
||||
'Connection: Keep-Alive\r\n\r\n');
|
||||
});
|
||||
|
||||
c.on('data', (chunk) => {
|
||||
console.log(chunk);
|
||||
server_response += chunk;
|
||||
});
|
||||
|
||||
c.on('end', common.mustCall(() => {
|
||||
assert.ok(server_response.includes('hello '));
|
||||
assert.ok(server_response.includes('world\n'));
|
||||
console.log('got end');
|
||||
c.end();
|
||||
}));
|
||||
|
||||
c.on('close', common.mustCall(() => {
|
||||
console.log('got close');
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
103
test/js/node/test/parallel/test-http-write-head.js
Normal file
103
test/js/node/test/parallel/test-http-write-head.js
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
// Verify that ServerResponse.writeHead() works as setHeader.
|
||||
// Issue 5036 on github.
|
||||
|
||||
const s = http.createServer(common.mustCall((req, res) => {
|
||||
res.setHeader('test', '1');
|
||||
|
||||
// toLowerCase() is used on the name argument, so it must be a string.
|
||||
// Non-String header names should throw
|
||||
assert.throws(
|
||||
() => res.setHeader(0xf00, 'bar'),
|
||||
{
|
||||
code: 'ERR_INVALID_HTTP_TOKEN',
|
||||
name: 'TypeError',
|
||||
}
|
||||
);
|
||||
|
||||
// Undefined value should throw, via 979d0ca8
|
||||
assert.throws(
|
||||
() => res.setHeader('foo', undefined),
|
||||
{
|
||||
code: 'ERR_HTTP_INVALID_HEADER_VALUE',
|
||||
name: 'TypeError',
|
||||
}
|
||||
);
|
||||
|
||||
assert.throws(() => {
|
||||
res.writeHead(200, ['invalid', 'headers', 'args']);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_VALUE'
|
||||
});
|
||||
|
||||
res.writeHead(200, { Test: '2' });
|
||||
|
||||
assert.throws(() => {
|
||||
res.writeHead(100, {});
|
||||
}, {
|
||||
code: 'ERR_HTTP_HEADERS_SENT',
|
||||
name: 'Error',
|
||||
});
|
||||
|
||||
res.end();
|
||||
}));
|
||||
|
||||
s.listen(0, common.mustCall(runTest));
|
||||
|
||||
function runTest() {
|
||||
http.get({ port: this.address().port }, common.mustCall((response) => {
|
||||
response.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response.headers.test, '2');
|
||||
s.close();
|
||||
}));
|
||||
response.resume();
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.writeHead(220, [ 'test', '1' ]); // 220 is not a standard status code
|
||||
assert.strictEqual(res.statusMessage, 'unknown');
|
||||
|
||||
assert.throws(() => res.writeHead(200, [ 'test2', '2' ]), {
|
||||
code: 'ERR_HTTP_HEADERS_SENT',
|
||||
name: 'Error',
|
||||
});
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port }, (res) => {
|
||||
assert.strictEqual(res.headers.test, '1');
|
||||
assert.strictEqual('test2' in res.headers, false);
|
||||
res.resume().on('end', common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const { IncomingMessage } = require('http');
|
||||
const assert = require('assert');
|
||||
|
||||
// Headers setter function set a header correctly
|
||||
{
|
||||
const im = new IncomingMessage();
|
||||
im.headers = { key: 'value' };
|
||||
assert.deepStrictEqual(im.headers, { key: 'value' });
|
||||
}
|
||||
|
||||
// Trailers setter function set a header correctly
|
||||
{
|
||||
const im = new IncomingMessage();
|
||||
im.trailers = { key: 'value' };
|
||||
assert.deepStrictEqual(im.trailers, { key: 'value' });
|
||||
}
|
||||
|
||||
// _addHeaderLines function set a header correctly
|
||||
{
|
||||
const im = new IncomingMessage();
|
||||
im.headers = { key1: 'value1' };
|
||||
im._addHeaderLines(['key2', 'value2'], 2);
|
||||
assert.deepStrictEqual(im.headers, { key1: 'value1', key2: 'value2' });
|
||||
}
|
||||
@@ -1331,12 +1331,12 @@ describe("Response", () => {
|
||||
it("should work with bigint", () => {
|
||||
var r = new Response("hello status", { status: 200n });
|
||||
expect(r.status).toBe(200);
|
||||
r = new Response("hello status", { status: 599n });
|
||||
expect(r.status).toBe(599);
|
||||
r = new Response("hello status", { status: 999n });
|
||||
expect(r.status).toBe(999);
|
||||
r = new Response("hello status", { status: BigInt(200) });
|
||||
expect(r.status).toBe(200);
|
||||
r = new Response("hello status", { status: BigInt(599) });
|
||||
expect(r.status).toBe(599);
|
||||
r = new Response("hello status", { status: BigInt(999) });
|
||||
expect(r.status).toBe(999);
|
||||
});
|
||||
testBlobInterface(data => new Response(data), true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user