Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/runtime/webcore/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ fn bun_fetch_preconnect(
return Err(global_object
.err(
jsc::ErrorCode::INVALID_ARG_TYPE,
format_args!("fetch() URL must not be a blank string."),
format_args!("fetch() URL must have a hostname"),
)
.throw());
}
Expand Down Expand Up @@ -310,10 +310,18 @@ impl StringOrURL {
}

let out = jsc::URL::href_from_js(value, global_this)?;
if out.tag() == BunStringTag::Dead {
return Ok(None);
if out.tag() != BunStringTag::Dead {
return Ok(Some(out));
}
// RequestInit objects keep the url property path; other values stringify.
if value.is_object() {
if let Some(url_prop) = value.fast_get(global_this, jsc::BuiltinName::Url)? {
if !url_prop.is_undefined() {
return Ok(None);
}
}
}
Ok(Some(out))
Ok(Some(BunString::from_js(value, global_this)?))
}
}

Expand Down Expand Up @@ -588,6 +596,17 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
url_type = URLType::Blob;
}

if url_type == URLType::Remote
&& (url.is_http() || url.is_https() || url.is_s3())
&& url.hostname.is_empty()
{
let err = ctx.to_type_error(
jsc::ErrorCode::INVALID_URL,
format_args!("fetch() URL must have a hostname"),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}

// **Start with the harmless ones.**

// "method"
Expand Down
3 changes: 3 additions & 0 deletions test/js/web/fetch/fetch-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ describe.concurrent("fetch() early rejections are reported when unhandled", () =
const cases: [name: string, code: string, expectedStderr: string][] = [
["no arguments", `fetch()`, "fetch() expects a string but received no arguments"],
["blank url", `fetch("")`, "fetch() URL must not be a blank string"],
["number url", `fetch(123)`, "fetch() URL is invalid"],
["undefined url", `fetch(undefined)`, "fetch() URL is invalid"],
["relative toString url", `fetch({ toString() { return "/some_path"; } })`, "fetch() URL is invalid"],
["invalid url", `fetch("not a url")`, "fetch() URL is invalid"],
["unsupported protocol", `fetch("gopher://example.com/")`, "protocol must be http:, https: or s3:"],
// No host may be read behind the second scheme, and the request still has to be refused.
Expand Down