diff --git a/src/runtime/webcore/fetch.rs b/src/runtime/webcore/fetch.rs index ce129c78bee5..6018f32a0a82 100644 --- a/src/runtime/webcore/fetch.rs +++ b/src/runtime/webcore/fetch.rs @@ -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()); } @@ -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)?)) } } @@ -588,6 +596,17 @@ fn fetch_impl( 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" diff --git a/test/js/web/fetch/fetch-args.test.ts b/test/js/web/fetch/fetch-args.test.ts index cc46c4d327f6..045e282c777d 100644 --- a/test/js/web/fetch/fetch-args.test.ts +++ b/test/js/web/fetch/fetch-args.test.ts @@ -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.