Compare commits

...

1 Commits

Author SHA1 Message Date
Ashcon Partovi
18a1148d30 fix: test-http-server-multiheaders.js 2025-03-21 14:39:31 -07:00
3 changed files with 679 additions and 325 deletions

View File

@@ -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[] = {
@@ -1297,6 +1299,141 @@ 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);
}
}
}
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;
if (lowercaseName == "host") {
headerValue = values[0];
} else if (lowercaseName == "cookie") {
headerValue = joinHeaderValues(values, ASCIILiteral("; "));
} else {
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);
@@ -1368,6 +1505,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)),

File diff suppressed because it is too large Load Diff

View 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'],
]
});
});