Skip to content

Commit

Permalink
Fix some argument validation issues in fetch() (#13337)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner authored Aug 16, 2024
1 parent 98a709f commit 766a9cf
Show file tree
Hide file tree
Showing 14 changed files with 988 additions and 545 deletions.
18 changes: 16 additions & 2 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2585,7 +2585,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp

// we have to clone the request headers here since they will soon belong to a different request
if (!request_object.hasFetchHeaders()) {
request_object.setFetchHeaders(JSC.FetchHeaders.createFromUWS(ctx.server.?.globalThis, req));
request_object.setFetchHeaders(JSC.FetchHeaders.createFromUWS(req));
}

// This object dies after the stack frame is popped
Expand Down Expand Up @@ -5498,6 +5498,10 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
data_value = headers_value;
}

if (globalThis.hasException()) {
return JSValue.jsUndefined();
}

if (opts.fastGet(globalThis, .headers)) |headers_value| {
if (headers_value.isEmptyOrUndefinedOrNull()) {
break :getter;
Expand All @@ -5512,10 +5516,16 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
}
break :brk null;
} orelse {
JSC.throwInvalidArguments("upgrade options.headers must be a Headers or an object", .{}, globalThis, exception);
if (!globalThis.hasException()) {
JSC.throwInvalidArguments("upgrade options.headers must be a Headers or an object", .{}, globalThis, exception);
}
return JSValue.jsUndefined();
};

if (globalThis.hasException()) {
return JSValue.jsUndefined();
}

if (fetch_headers_to_use.fastGet(.SecWebSocketProtocol)) |protocol| {
sec_websocket_protocol = protocol;
}
Expand All @@ -5529,6 +5539,10 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
resp.writeStatus("101 Switching Protocols");
fetch_headers_to_use.toUWSResponse(comptime ssl_enabled, resp);
}

if (globalThis.hasException()) {
return JSValue.jsUndefined();
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/ErrorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ export default [

// Bun-specific
["ERR_FORMDATA_PARSE_ERROR", TypeError, "TypeError"],
["ERR_BODY_ALREADY_USED", Error, "Error"],
] as ErrorCodeMapping;
49 changes: 40 additions & 9 deletions src/bun.js/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1558,23 +1558,53 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__createFromJS(JSC__JSGlobalObject*
EnsureStillAliveScope argument0 = JSC::JSValue::decode(argument0_);

auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm());
throwScope.assertNoException();

// Note that we use IDLDOMString here rather than IDLByteString: while headers
// should be ASCII only, we want the headers->fill implementation to discover
// and error on invalid names and values
using TargetType = IDLUnion<IDLSequence<IDLSequence<IDLDOMString>>, IDLRecord<IDLDOMString, IDLDOMString>>;
using Converter = std::optional<Converter<TargetType>::ReturnType>;

auto init = argument0.value().isUndefined() ? Converter() : Converter(convert<TargetType>(*lexicalGlobalObject, argument0.value()));
RETURN_IF_EXCEPTION(throwScope, nullptr);

// if the headers are empty, return null
if (!init) {
return nullptr;
}

// [["", ""]] should be considered empty and return null
if (std::holds_alternative<Vector<Vector<String>>>(init.value())) {
const auto& sequence = std::get<Vector<Vector<String>>>(init.value());

if (sequence.size() == 0) {
return nullptr;
}
} else {
// {} should be considered empty and return null
const auto& record = std::get<Vector<KeyValuePair<String, String>>>(init.value());
if (record.size() == 0) {
return nullptr;
}
}

auto* headers = new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} });
headers->relaxAdoptionRequirement();
if (init) {
// `fill` doesn't set an exception on the VM if it fails, it returns an
// ExceptionOr<void>. So we need to check for the exception and, if set,
// translate it to JSValue and throw it.
WebCore::propagateException(*lexicalGlobalObject, throwScope,
headers->fill(WTFMove(init.value())));

// `fill` doesn't set an exception on the VM if it fails, it returns an
// ExceptionOr<void>. So we need to check for the exception and, if set,
// translate it to JSValue and throw it.
WebCore::propagateException(*lexicalGlobalObject, throwScope,
headers->fill(WTFMove(init.value())));

// If there's an exception, it will be thrown by the above call to fill().
// in that case, let's also free the headers to make memory leaks harder.
if (throwScope.exception()) {
headers->deref();
return nullptr;
}

return headers;
}

Expand Down Expand Up @@ -1737,7 +1767,7 @@ WebCore::FetchHeaders* WebCore__FetchHeaders__createFromPicoHeaders_(const void*
}
return headers;
}
WebCore::FetchHeaders* WebCore__FetchHeaders__createFromUWS(JSC__JSGlobalObject* arg0, void* arg1)
WebCore::FetchHeaders* WebCore__FetchHeaders__createFromUWS(void* arg1)
{
uWS::HttpRequest req = *reinterpret_cast<uWS::HttpRequest*>(arg1);

Expand Down Expand Up @@ -1810,11 +1840,12 @@ bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* headers, const ZigString*
} else
return result.releaseReturnValue();
}
void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2, JSC__JSGlobalObject* global)
extern "C" void WebCore__FetchHeaders__put(WebCore__FetchHeaders* headers, HTTPHeaderName name, const ZigString* arg2, JSC__JSGlobalObject* global)
{
auto throwScope = DECLARE_THROW_SCOPE(global->vm());
throwScope.assertNoException(); // can't throw an exception when there's already one.
WebCore::propagateException(*global, throwScope,
headers->set(Zig::toString(*arg1), Zig::toStringCopy(*arg2)));
headers->set(name, Zig::toStringCopy(*arg2)));
}
void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* headers, const ZigString* arg1, JSC__JSGlobalObject* global)
{
Expand Down
43 changes: 18 additions & 25 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1170,22 +1170,29 @@ pub const FetchHeaders = opaque {
});
}

extern "C" fn WebCore__FetchHeaders__createFromJS(*JSC.JSGlobalObject, JSValue) ?*FetchHeaders;
/// Construct a `Headers` object from a JSValue.
///
/// This can be:
/// - Array<[String, String]>
/// - Record<String, String>.
///
/// Throws an exception if invalid.
///
/// If empty, returns null.
pub fn createFromJS(
global: *JSGlobalObject,
value: JSValue,
) ?*FetchHeaders {
return shim.cppFn("createFromJS", .{
global,
value,
});
return WebCore__FetchHeaders__createFromJS(global, value);
}

pub fn putDefault(this: *FetchHeaders, name_: []const u8, value: []const u8, global: *JSGlobalObject) void {
if (this.has(&ZigString.init(name_), global)) {
pub fn putDefault(this: *FetchHeaders, name_: HTTPHeaderName, value: []const u8, global: *JSGlobalObject) void {
if (this.fastHas(name_)) {
return;
}

this.put_(&ZigString.init(name_), &ZigString.init(value), global);
this.put(name_, value, global);
}

pub fn from(
Expand All @@ -1211,11 +1218,9 @@ pub const FetchHeaders = opaque {
}

pub fn createFromUWS(
global: *JSGlobalObject,
uws_request: *anyopaque,
) *FetchHeaders {
return shim.cppFn("createFromUWS", .{
global,
uws_request,
});
}
Expand Down Expand Up @@ -1273,27 +1278,15 @@ pub const FetchHeaders = opaque {
});
}

pub fn put_(
this: *FetchHeaders,
name_: *const ZigString,
value: *const ZigString,
global: *JSGlobalObject,
) void {
return shim.cppFn("put_", .{
this,
name_,
value,
global,
});
}
extern fn WebCore__FetchHeaders__put(this: *FetchHeaders, name_: HTTPHeaderName, value: *const ZigString, global: *JSGlobalObject) void;

pub fn put(
this: *FetchHeaders,
name_: []const u8,
name_: HTTPHeaderName,
value: []const u8,
global: *JSGlobalObject,
) void {
this.put_(&ZigString.init(name_), &ZigString.init(value), global);
WebCore__FetchHeaders__put(this, name_, &ZigString.init(value), global);
}

pub fn get_(
Expand Down Expand Up @@ -3982,7 +3975,7 @@ pub const JSValue = enum(JSValueReprInt) {
/// If the object is a subclass of the type or has mutated the structure, return null.
/// Note: this may return null for direct instances of the type if the user adds properties to the object.
pub fn asDirect(value: JSValue, comptime ZigType: type) ?*ZigType {
bun.assert(value.isCell()); // you must have already checked this.
bun.debugAssert(value.isCell()); // you must have already checked this.

return ZigType.fromJSDirect(value);
}
Expand Down
3 changes: 1 addition & 2 deletions src/bun.js/bindings/headers.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/bun.js/bindings/headers.zig

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions src/bun.js/bindings/webcore/FetchHeaders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,24 @@ ExceptionOr<bool> FetchHeaders::has(const String& name) const
return m_headers.contains(name);
}

ExceptionOr<void> FetchHeaders::set(const HTTPHeaderName name, const String& value)
{
String normalizedValue = value.trim(isHTTPSpace);
auto canWriteResult = canWriteHeader(name, normalizedValue, normalizedValue, m_guard);
if (canWriteResult.hasException())
return canWriteResult.releaseException();
if (!canWriteResult.releaseReturnValue())
return {};

++m_updateCounter;
m_headers.set(name, normalizedValue);

if (m_guard == FetchHeaders::Guard::RequestNoCors)
removePrivilegedNoCORSRequestHeaders(m_headers);

return {};
}

ExceptionOr<void> FetchHeaders::set(const String& name, const String& value)
{
String normalizedValue = value.trim(isHTTPSpace);
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/webcore/FetchHeaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class FetchHeaders : public RefCounted<FetchHeaders> {
ExceptionOr<String> get(const String&) const;
ExceptionOr<bool> has(const String&) const;
ExceptionOr<void> set(const String& name, const String& value);
ExceptionOr<void> set(const HTTPHeaderName name, const String& value);

ExceptionOr<void> fill(const Init&);
ExceptionOr<void> fill(const FetchHeaders&);
Expand Down
5 changes: 1 addition & 4 deletions src/bun.js/webcore/body.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1131,10 +1131,7 @@ pub fn BodyMixin(comptime Type: type) type {
}

fn handleBodyAlreadyUsed(globalObject: *JSC.JSGlobalObject) JSValue {
return JSC.JSPromise.rejectedPromiseValue(
globalObject,
ZigString.static("Body already used").toErrorInstance(globalObject),
);
return globalObject.ERR_BODY_ALREADY_USED("Body already used", .{}).reject();
}

pub fn getArrayBuffer(
Expand Down
Loading

0 comments on commit 766a9cf

Please sign in to comment.