fix: test-http-response-setheaders.js

This commit is contained in:
Ashcon Partovi
2025-03-19 14:23:18 -07:00
parent e054c11e10
commit b58a8ea979
2 changed files with 250 additions and 8 deletions

View File

@@ -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,
@@ -198,7 +198,7 @@ function validateMsecs(numberlike: any, field: string) {
if (numberlike > TIMEOUT_MAX) {
process.emitWarning(
`${numberlike} does not fit into a 32-bit signed integer.` + `\nTimer duration was truncated to ${TIMEOUT_MAX}.`,
"TimeoutOverflowWarning"
"TimeoutOverflowWarning",
);
return TIMEOUT_MAX;
}
@@ -1668,7 +1668,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];
@@ -1683,6 +1683,41 @@ const OutgoingMessagePrototype = {
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;
@@ -1930,9 +1965,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;
@@ -2208,6 +2241,41 @@ const ServerResponsePrototype = {
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;
},
assignSocket(socket) {
if (socket._httpMessage) {
throw ERR_HTTP_SOCKET_ASSIGNED();

View 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();
}));
});
}));
}