Skip to content

Commit 5cdc883

Browse files
goffrieConvex, Inc.
authored andcommitted
Allow arbitrary schemes & username/password in "new URL()" parser (#37597)
GitOrigin-RevId: 00504773f3efd91efc7989a83b60b4f145108884
1 parent 5b05d68 commit 5cdc883

File tree

6 files changed

+179
-204
lines changed

6 files changed

+179
-204
lines changed

crates/isolate/src/ops/http.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -241,35 +241,26 @@ struct UrlInfo {
241241
href: String,
242242
pathname: String,
243243
port: String,
244-
protocol: String,
245244
search: String,
245+
username: String,
246+
password: String,
246247
}
247248

248249
impl TryFrom<Url> for UrlInfo {
249250
type Error = anyhow::Error;
250251

251252
fn try_from(value: Url) -> Result<Self, Self::Error> {
252-
if value.username() != "" || value.password().is_some() {
253-
anyhow::bail!("Unsupported URL with username and password")
254-
}
255-
256-
if value.scheme() != "http" && value.scheme() != "https" {
257-
anyhow::bail!(
258-
"Unsupported URL scheme -- http and https are supported (scheme was {})",
259-
value.scheme()
260-
)
261-
}
262-
263253
let url_info = UrlInfo {
264-
scheme: value[Position::BeforeScheme..Position::AfterScheme].to_string(),
254+
scheme: value.scheme().to_string(),
265255
hash: value[Position::BeforeFragment..Position::AfterFragment].to_string(),
266256
host: value[Position::BeforeHost..Position::BeforePath].to_string(),
267257
hostname: value[Position::BeforeHost..Position::AfterHost].to_string(),
268258
href: value.to_string(),
269259
pathname: value[Position::BeforePath..Position::AfterPath].to_string(),
270260
port: value[Position::BeforePort..Position::AfterPort].to_string(),
271-
protocol: value[..Position::AfterScheme].to_string(),
272261
search: value[Position::BeforeQuery..Position::AfterQuery].to_string(),
262+
username: value.username().to_owned(),
263+
password: value.password().unwrap_or_default().to_owned(),
273264
};
274265
Ok(url_info)
275266
}

crates/isolate/src/tests/js_builtins.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,6 @@ async fn test_url(rt: TestRuntime) -> anyhow::Result<()> {
2121
must_let!(let ConvexValue::String(r) = t.query("js_builtins/url", assert_obj!()).await?);
2222
assert_eq!(String::from(r), "success".to_string());
2323

24-
assert_contains(
25-
&t.query_js_error("js_builtins/url:passwordNotImplemented", assert_obj!())
26-
.await?,
27-
"Not implemented: get password",
28-
);
29-
30-
assert_contains(
31-
&t.query_js_error("js_builtins/url:usernameNotImplemented", assert_obj!())
32-
.await?,
33-
"Not implemented: get username",
34-
);
35-
36-
assert_contains(
37-
&t.query_js_error(
38-
"js_builtins/url:unsupportUrlUsernameAndPassword",
39-
assert_obj!(),
40-
)
41-
.await?,
42-
"Unsupported URL with username and password",
43-
);
44-
45-
assert_contains(
46-
&t.query_js_error("js_builtins/url:unsupportedUrlProtocol", assert_obj!())
47-
.await?,
48-
"Unsupported URL scheme",
49-
);
50-
5124
assert_contains(
5225
&t.query_js_error("js_builtins/url:setHostUnimplemented", assert_obj!())
5326
.await?,

npm-packages/udf-runtime/src/00_url.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,10 @@ type UrlInfo = {
165165
hostname: string;
166166
pathname: string;
167167
port: string;
168-
protocol: string;
169168
search: string;
170169
href: string;
170+
username: string;
171+
password: string;
171172
};
172173

173174
type URLInfoResult =
@@ -259,11 +260,20 @@ class URL {
259260
}
260261

261262
get origin() {
262-
return `${this.protocol}//${this.host}`;
263+
switch (this._urlInfo.scheme) {
264+
case "ftp":
265+
case "http":
266+
case "https":
267+
case "ws":
268+
case "wss":
269+
return `${this._urlInfo.scheme}://${this.host}`;
270+
default:
271+
return "null";
272+
}
263273
}
264274

265275
get password() {
266-
return throwNotImplementedMethodError("get password", "URL");
276+
return this._urlInfo.password;
267277
}
268278

269279
set password(_password: string) {
@@ -293,7 +303,7 @@ class URL {
293303
}
294304

295305
get protocol() {
296-
return this._urlInfo.protocol.toString() + ":";
306+
return this._urlInfo.scheme.toString() + ":";
297307
}
298308

299309
set protocol(_protocol: string) {
@@ -323,7 +333,7 @@ class URL {
323333
}
324334

325335
get username() {
326-
return throwNotImplementedMethodError("get username", "URL");
336+
return this._urlInfo.username;
327337
}
328338

329339
set username(_username: string) {
@@ -357,6 +367,8 @@ class URL {
357367
href: this.href,
358368
origin: this.origin,
359369
protocol: this.protocol,
370+
username: this.username,
371+
password: this.password,
360372
host: this.host,
361373
hostname: this.hostname,
362374
port: this.port,

npm-packages/udf-runtime/src/23_request.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ export interface RequestInit {
5858

5959
const _contentLength = Symbol("[[contentLength]]");
6060

61+
const validateURL = (s: string | URL) => {
62+
const url = new URL(s);
63+
const protocol = url.protocol;
64+
if (protocol !== "http:" && protocol !== "https:") {
65+
throw new TypeError(
66+
`Unsupported URL scheme -- http and https are supported (scheme was ${protocol.slice(0, protocol.length - 1)})`,
67+
);
68+
}
69+
return url.href;
70+
};
71+
6172
export class Request {
6273
private readonly _headers: Headers;
6374
private readonly _url: string;
@@ -76,7 +87,7 @@ export class Request {
7687
this._signal = new AbortSignal();
7788
if (input instanceof Request) {
7889
// Copy initial values from the request. Options can still override them.
79-
this._url = new URL(input.url).href;
90+
this._url = validateURL(input.url);
8091
this._method = input.method;
8192
this._headers = new Headers(input.headers);
8293
this._signal = input.signal;
@@ -85,7 +96,7 @@ export class Request {
8596
// * If this object has a Request.mode of navigate, the mode value is converted to same-origin.
8697
} else if (input instanceof URL || typeof input === "string") {
8798
const href = input instanceof URL ? input.href : input;
88-
this._url = new URL(href).href;
99+
this._url = validateURL(href);
89100
// Use default values.
90101
this._method = "GET";
91102
this._headers = new Headers([]);

npm-packages/udf-tests/convex/adversarial.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ export const useNotImplementedBuiltin = query(async () => {
347347
try {
348348
const url = new URL("https://baz.qat:8000/qux/quux?foo=bar&baz=12#qat");
349349
// This should throw an uncatchable "Not implemented" error
350-
url.password;
350+
url.password = "foo";
351351
} catch (e) {
352352
return "Caught an error!";
353353
}

0 commit comments

Comments
 (0)