summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2020-07-24 02:37:11 +0100
committerGitHub <noreply@github.com>2020-07-23 21:37:11 -0400
commita8f74aa381c99e9c3c3d8fdfde02919966a3a824 (patch)
treed1dc35a1d56bb1845f2389313eda5887682a6632
parentb61347b2552cb7159ee3e82c5e9ca5a76e73c3f8 (diff)
fix: Improve URL compatibility (#6807)
- Fix protocol regex. - Truncate repeated leading slashes in file paths. - Make drive letter support platform-independent. - Drop the hostname if a drive letter is parsed. - Fix drive letter normalization and basing. - Allow basing over the host. - Fix same-protocol basing. - Remove Windows UNC path support. - Reverts #6418. This is non-standard. Wouldn't be too much of a problem but it makes other parts of the spec hard to realize.
-rw-r--r--cli/rt/11_url.js298
-rw-r--r--cli/tests/unit/url_test.ts189
-rw-r--r--std/path/from_file_url_test.ts26
-rw-r--r--std/path/posix.ts3
-rw-r--r--std/path/win32.ts2
5 files changed, 263 insertions, 255 deletions
diff --git a/cli/rt/11_url.js b/cli/rt/11_url.js
index 435d3454e..202d35e81 100644
--- a/cli/rt/11_url.js
+++ b/cli/rt/11_url.js
@@ -1,7 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
- const { build } = window.__bootstrap.build;
const { getRandomValues } = window.__bootstrap.crypto;
const { customInspect } = window.__bootstrap.console;
const { sendSync } = window.__bootstrap.dispatchJson;
@@ -264,74 +263,118 @@
return [capture, rest];
}
- function parse(url, isBase = true) {
+ function parse(url, baseParts = null) {
const parts = {};
let restUrl;
- [parts.protocol, restUrl] = takePattern(url.trim(), /^([a-z]+):/);
- if (isBase && parts.protocol == "") {
- return undefined;
+ let usedNonBase = false;
+ [parts.protocol, restUrl] = takePattern(
+ url.trim(),
+ /^([A-Za-z][+-.0-9A-Za-z]*):/,
+ );
+ parts.protocol = parts.protocol.toLowerCase();
+ if (parts.protocol == "") {
+ if (baseParts == null) {
+ return null;
+ }
+ parts.protocol = baseParts.protocol;
+ } else if (
+ parts.protocol != baseParts?.protocol ||
+ !specialSchemes.includes(parts.protocol)
+ ) {
+ usedNonBase = true;
}
const isSpecial = specialSchemes.includes(parts.protocol);
if (parts.protocol == "file") {
parts.slashes = "//";
parts.username = "";
parts.password = "";
- [parts.hostname, restUrl] = takePattern(restUrl, /^[/\\]{2}([^/\\?#]*)/);
- parts.port = "";
- if (build.os == "windows" && parts.hostname == "") {
- // UNC paths. e.g. "\\\\localhost\\foo\\bar" on Windows should be
- // representable as `new URL("file:////localhost/foo/bar")` which is
- // equivalent to: `new URL("file://localhost/foo/bar")`.
+ if (usedNonBase || restUrl.match(/^[/\\]{2}/)) {
[parts.hostname, restUrl] = takePattern(
restUrl,
- /^[/\\]{2,}([^/\\?#]*)/,
+ /^[/\\]{2}([^/\\?#]*)/,
);
+ usedNonBase = true;
+ } else {
+ parts.hostname = baseParts.hostname;
}
+ parts.port = "";
} else {
- let restAuthority;
- if (isSpecial) {
- parts.slashes = "//";
- [restAuthority, restUrl] = takePattern(
- restUrl,
- /^[/\\]{2,}([^/\\?#]*)/,
+ if (usedNonBase || restUrl.match(/^[/\\]{2}/)) {
+ let restAuthority;
+ if (isSpecial) {
+ parts.slashes = "//";
+ [restAuthority, restUrl] = takePattern(
+ restUrl,
+ /^[/\\]*([^/\\?#]*)/,
+ );
+ } else {
+ parts.slashes = restUrl.match(/^[/\\]{2}/) ? "//" : "";
+ [restAuthority, restUrl] = takePattern(
+ restUrl,
+ /^[/\\]{2}([^/\\?#]*)/,
+ );
+ }
+ let restAuthentication;
+ [restAuthentication, restAuthority] = takePattern(
+ restAuthority,
+ /^(.*)@/,
+ );
+ [parts.username, restAuthentication] = takePattern(
+ restAuthentication,
+ /^([^:]*)/,
+ );
+ parts.username = encodeUserinfo(parts.username);
+ [parts.password] = takePattern(restAuthentication, /^:(.*)/);
+ parts.password = encodeUserinfo(parts.password);
+ [parts.hostname, restAuthority] = takePattern(
+ restAuthority,
+ /^(\[[0-9a-fA-F.:]{2,}\]|[^:]+)/,
);
+ [parts.port] = takePattern(restAuthority, /^:(.*)/);
+ if (!isValidPort(parts.port)) {
+ return null;
+ }
+ if (parts.hostname == "" && isSpecial) {
+ return null;
+ }
+ usedNonBase = true;
} else {
- parts.slashes = restUrl.match(/^[/\\]{2}/) ? "//" : "";
- [restAuthority, restUrl] = takePattern(restUrl, /^[/\\]{2}([^/\\?#]*)/);
- }
- let restAuthentication;
- [restAuthentication, restAuthority] = takePattern(
- restAuthority,
- /^(.*)@/,
- );
- [parts.username, restAuthentication] = takePattern(
- restAuthentication,
- /^([^:]*)/,
- );
- parts.username = encodeUserinfo(parts.username);
- [parts.password] = takePattern(restAuthentication, /^:(.*)/);
- parts.password = encodeUserinfo(parts.password);
- [parts.hostname, restAuthority] = takePattern(
- restAuthority,
- /^(\[[0-9a-fA-F.:]{2,}\]|[^:]+)/,
- );
- [parts.port] = takePattern(restAuthority, /^:(.*)/);
- if (!isValidPort(parts.port)) {
- return undefined;
- }
- if (parts.hostname == "" && isSpecial && isBase) {
- return undefined;
+ parts.username = baseParts.username;
+ parts.password = baseParts.password;
+ parts.hostname = baseParts.hostname;
+ parts.port = baseParts.port;
}
}
try {
parts.hostname = encodeHostname(parts.hostname, isSpecial);
} catch {
- return undefined;
+ return null;
}
[parts.path, restUrl] = takePattern(restUrl, /^([^?#]*)/);
parts.path = encodePathname(parts.path.replace(/\\/g, "/"));
- [parts.query, restUrl] = takePattern(restUrl, /^(\?[^#]*)/);
- parts.query = encodeSearch(parts.query);
+ if (usedNonBase) {
+ parts.path = normalizePath(parts.path, parts.protocol == "file");
+ } else {
+ if (parts.path != "") {
+ usedNonBase = true;
+ }
+ parts.path = resolvePathFromBase(
+ parts.path,
+ baseParts.path || "/",
+ baseParts.protocol == "file",
+ );
+ }
+ // Drop the hostname if a drive letter is parsed.
+ if (parts.protocol == "file" && parts.path.match(/^\/+[A-Za-z]:(\/|$)/)) {
+ parts.hostname = "";
+ }
+ if (usedNonBase || restUrl.startsWith("?")) {
+ [parts.query, restUrl] = takePattern(restUrl, /^(\?[^#]*)/);
+ parts.query = encodeSearch(parts.query);
+ usedNonBase = true;
+ } else {
+ parts.query = baseParts.query;
+ }
[parts.hash] = takePattern(restUrl, /^(#.*)/);
parts.hash = encodeHash(parts.hash);
return parts;
@@ -348,22 +391,26 @@
// Keep it outside of URL to avoid any attempts of access.
const blobURLMap = new Map();
- function isAbsolutePath(path) {
- return path.startsWith("/");
- }
-
// Resolves `.`s and `..`s where possible.
// Preserves repeating and trailing `/`s by design.
- // On Windows, drive letter paths will be given a leading slash, and also a
- // trailing slash if there are no other components e.g. "C:" -> "/C:/".
- function normalizePath(path, isFilePath = false) {
- if (build.os == "windows" && isFilePath) {
- path = path.replace(/^\/*([A-Za-z]:)(\/|$)/, "/$1/");
- }
- const isAbsolute = isAbsolutePath(path);
+ // Assumes drive letter file paths will have a leading slash.
+ function normalizePath(path, isFilePath) {
+ const isAbsolute = path.startsWith("/");
path = path.replace(/^\//, "");
const pathSegments = path.split("/");
+ let driveLetter = null;
+ if (isFilePath && pathSegments[0].match(/^[A-Za-z]:$/)) {
+ driveLetter = pathSegments.shift();
+ }
+
+ if (isFilePath && isAbsolute) {
+ while (pathSegments.length > 1 && pathSegments[0] == "") {
+ pathSegments.shift();
+ }
+ }
+
+ let ensureTrailingSlash = false;
const newPathSegments = [];
for (let i = 0; i < pathSegments.length; i++) {
const previous = newPathSegments[newPathSegments.length - 1];
@@ -373,64 +420,67 @@
(previous != undefined || isAbsolute)
) {
newPathSegments.pop();
- } else if (pathSegments[i] != ".") {
+ ensureTrailingSlash = true;
+ } else if (pathSegments[i] == ".") {
+ ensureTrailingSlash = true;
+ } else {
newPathSegments.push(pathSegments[i]);
+ ensureTrailingSlash = false;
}
}
+ if (driveLetter != null) {
+ newPathSegments.unshift(driveLetter);
+ }
+ if (newPathSegments.length == 0 && !isAbsolute) {
+ newPathSegments.push(".");
+ ensureTrailingSlash = false;
+ }
let newPath = newPathSegments.join("/");
- if (!isAbsolute) {
- if (newPathSegments.length == 0) {
- newPath = ".";
- }
- } else {
+ if (isAbsolute) {
newPath = `/${newPath}`;
}
+ if (ensureTrailingSlash) {
+ newPath = newPath.replace(/\/*$/, "/");
+ }
return newPath;
}
// Standard URL basing logic, applied to paths.
- function resolvePathFromBase(
- path,
- basePath,
- isFilePath = false,
- ) {
- let normalizedPath = normalizePath(path, isFilePath);
- let normalizedBasePath = normalizePath(basePath, isFilePath);
-
- let driveLetterPrefix = "";
- if (build.os == "windows" && isFilePath) {
- let driveLetter;
- let baseDriveLetter;
- [driveLetter, normalizedPath] = takePattern(
- normalizedPath,
- /^(\/[A-Za-z]:)(?=\/)/,
- );
- [baseDriveLetter, normalizedBasePath] = takePattern(
- normalizedBasePath,
- /^(\/[A-Za-z]:)(?=\/)/,
- );
- driveLetterPrefix = driveLetter || baseDriveLetter;
- }
-
- if (isAbsolutePath(normalizedPath)) {
- return `${driveLetterPrefix}${normalizedPath}`;
- }
- if (!isAbsolutePath(normalizedBasePath)) {
- throw new TypeError("Base path must be absolute.");
- }
-
- // Special case.
- if (path == "") {
- return `${driveLetterPrefix}${normalizedBasePath}`;
+ function resolvePathFromBase(path, basePath, isFilePath) {
+ let basePrefix;
+ let suffix;
+ const baseDriveLetter = basePath.match(/^\/+[A-Za-z]:(?=\/|$)/)?.[0];
+ if (isFilePath && path.match(/^\/+[A-Za-z]:(\/|$)/)) {
+ basePrefix = "";
+ suffix = path;
+ } else if (path.startsWith("/")) {
+ if (isFilePath && baseDriveLetter) {
+ basePrefix = baseDriveLetter;
+ suffix = path;
+ } else {
+ basePrefix = "";
+ suffix = path;
+ }
+ } else if (path != "") {
+ basePath = normalizePath(basePath, isFilePath);
+ path = normalizePath(path, isFilePath);
+ // Remove everything after the last `/` in `basePath`.
+ if (baseDriveLetter && isFilePath) {
+ basePrefix = `${baseDriveLetter}${
+ basePath.slice(baseDriveLetter.length).replace(/[^\/]*$/, "")
+ }`;
+ } else {
+ basePrefix = basePath.replace(/[^\/]*$/, "");
+ }
+ basePrefix = basePrefix.replace(/\/*$/, "/");
+ // If `normalizedPath` ends with `.` or `..`, add a trailing slash.
+ suffix = path.replace(/(?<=(^|\/)(\.|\.\.))$/, "/");
+ } else {
+ basePrefix = basePath;
+ suffix = "";
}
-
- // Remove everything after the last `/` in `normalizedBasePath`.
- const prefix = normalizedBasePath.replace(/[^\/]*$/, "");
- // If `normalizedPath` ends with `.` or `..`, add a trailing slash.
- const suffix = normalizedPath.replace(/(?<=(^|\/)(\.|\.\.))$/, "/");
-
- return `${driveLetterPrefix}${normalizePath(prefix + suffix)}`;
+ return normalizePath(basePrefix + suffix, isFilePath);
}
function isValidPort(value) {
@@ -628,46 +678,22 @@
}
constructor(url, base) {
- let baseParts;
+ let baseParts = null;
+ new.target;
if (base) {
- baseParts = typeof base === "string" ? parse(base) : parts.get(base);
- if (baseParts === undefined) {
+ baseParts = base instanceof URL ? parts.get(base) : parse(base);
+ if (baseParts == null) {
throw new TypeError("Invalid base URL.");
}
}
- const urlParts = typeof url === "string"
- ? parse(url, !baseParts)
- : parts.get(url);
- if (urlParts == undefined) {
- throw new TypeError("Invalid URL.");
- }
-
- if (urlParts.protocol) {
- urlParts.path = normalizePath(
- urlParts.path,
- urlParts.protocol == "file",
- );
- parts.set(this, urlParts);
- } else if (baseParts) {
- parts.set(this, {
- protocol: baseParts.protocol,
- slashes: baseParts.slashes,
- username: baseParts.username,
- password: baseParts.password,
- hostname: baseParts.hostname,
- port: baseParts.port,
- path: resolvePathFromBase(
- urlParts.path,
- baseParts.path || "/",
- baseParts.protocol == "file",
- ),
- query: urlParts.query,
- hash: urlParts.hash,
- });
- } else {
+ const urlParts = url instanceof URL
+ ? parts.get(url)
+ : parse(url, baseParts);
+ if (urlParts == null) {
throw new TypeError("Invalid URL.");
}
+ parts.set(this, urlParts);
this.#updateSearchParams();
}
diff --git a/cli/tests/unit/url_test.ts b/cli/tests/unit/url_test.ts
index b50f6a25b..ed8ae5500 100644
--- a/cli/tests/unit/url_test.ts
+++ b/cli/tests/unit/url_test.ts
@@ -27,6 +27,23 @@ unitTest(function urlParsing(): void {
);
});
+unitTest(function urlProtocolParsing(): void {
+ assertEquals(new URL("Aa+-.1://foo").protocol, "aa+-.1:");
+ assertEquals(new URL("aA+-.1://foo").protocol, "aa+-.1:");
+ assertThrows(() => new URL("1://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("+://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("-://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL(".://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("_://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("=://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("!://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL(`"://foo`), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("$://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("%://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("^://foo"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("*://foo"), TypeError, "Invalid URL.");
+});
+
unitTest(function urlAuthenticationParsing(): void {
const specialUrl = new URL("http://foo:bar@baz");
assertEquals(specialUrl.username, "foo");
@@ -201,46 +218,35 @@ unitTest(function urlBackSlashes(): void {
);
});
+unitTest(function urlProtocolSlashes(): void {
+ assertEquals(new URL("http:foo").href, "http://foo/");
+ assertEquals(new URL("http://foo").href, "http://foo/");
+ assertEquals(new URL("file:foo").href, "file:///foo");
+ assertEquals(new URL("file://foo").href, "file://foo/");
+ assertEquals(new URL("abcd:foo").href, "abcd:foo");
+ assertEquals(new URL("abcd://foo").href, "abcd://foo");
+});
+
unitTest(function urlRequireHost(): void {
assertEquals(new URL("file:///").href, "file:///");
- assertThrows(() => {
- new URL("ftp:///");
- });
- assertThrows(() => {
- new URL("http:///");
- });
- assertThrows(() => {
- new URL("https:///");
- });
- assertThrows(() => {
- new URL("ws:///");
- });
- assertThrows(() => {
- new URL("wss:///");
- });
+ assertThrows(() => new URL("ftp:///"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("http:///"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("https:///"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("ws:///"), TypeError, "Invalid URL.");
+ assertThrows(() => new URL("wss:///"), TypeError, "Invalid URL.");
});
unitTest(function urlDriveLetter() {
- assertEquals(
- new URL("file:///C:").href,
- Deno.build.os == "windows" ? "file:///C:/" : "file:///C:",
- );
- assertEquals(new URL("http://example.com/C:").href, "http://example.com/C:");
-});
-
-unitTest(function urlUncHostname() {
- assertEquals(
- new URL("file:////").href,
- Deno.build.os == "windows" ? "file:///" : "file:////",
- );
- assertEquals(
- new URL("file:////server").href,
- Deno.build.os == "windows" ? "file://server/" : "file:////server",
- );
- assertEquals(
- new URL("file:////server/file").href,
- Deno.build.os == "windows" ? "file://server/file" : "file:////server/file",
- );
+ assertEquals(new URL("file:///C:").href, "file:///C:");
+ assertEquals(new URL("file:///C:/").href, "file:///C:/");
+ assertEquals(new URL("file:///C:/..").href, "file:///C:/");
+ // Don't recognise drive letters with extra leading slashes.
+ assertEquals(new URL("file:////C:/..").href, "file:///");
+ // Drop the hostname if a drive letter is parsed.
+ assertEquals(new URL("file://foo/C:").href, "file:///C:");
+ // Don't recognise drive letters in non-file protocols.
+ assertEquals(new URL("http://foo/C:/..").href, "http://foo/");
+ assertEquals(new URL("abcd://foo/C:/..").href, "abcd://foo/");
});
unitTest(function urlHostnameUpperCase() {
@@ -254,6 +260,12 @@ unitTest(function urlEmptyPath() {
assertEquals(new URL("abcd://foo").pathname, "");
});
+unitTest(function urlPathRepeatedSlashes() {
+ assertEquals(new URL("http://foo//bar//").pathname, "//bar//");
+ assertEquals(new URL("file://foo///bar//").pathname, "/bar//");
+ assertEquals(new URL("abcd://foo//bar//").pathname, "//bar//");
+});
+
unitTest(function urlTrim() {
assertEquals(new URL(" http://example.com ").href, "http://example.com/");
});
@@ -284,66 +296,59 @@ unitTest(function urlEncoding() {
);
});
-unitTest(function urlBaseURL(): void {
- const base = new URL(
- "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat",
- );
- const url = new URL("/foo/bar?baz=foo#qux", base);
- assertEquals(url.href, "https://foo:bar@baz.qat:8000/foo/bar?baz=foo#qux");
+unitTest(function urlBase(): void {
+ assertEquals(new URL("d", new URL("http://foo/a?b#c")).href, "http://foo/d");
- assertEquals(
- new URL("D", "https://foo.bar/path/a/b/c/d").href,
- "https://foo.bar/path/a/b/c/D",
- );
+ assertEquals(new URL("", "http://foo/a/b?c#d").href, "http://foo/a/b?c");
+ assertEquals(new URL("", "file://foo/a/b?c#d").href, "file://foo/a/b?c");
+ assertEquals(new URL("", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?c");
- assertEquals(new URL("D", "https://foo.bar").href, "https://foo.bar/D");
- assertEquals(new URL("D", "https://foo.bar/").href, "https://foo.bar/D");
+ assertEquals(new URL("#e", "http://foo/a/b?c#d").href, "http://foo/a/b?c#e");
+ assertEquals(new URL("#e", "file://foo/a/b?c#d").href, "file://foo/a/b?c#e");
+ assertEquals(new URL("#e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?c#e");
- assertEquals(
- new URL("/d", "https://foo.bar/path/a/b/c/d").href,
- "https://foo.bar/d",
- );
-});
+ assertEquals(new URL("?e", "http://foo/a/b?c#d").href, "http://foo/a/b?e");
+ assertEquals(new URL("?e", "file://foo/a/b?c#d").href, "file://foo/a/b?e");
+ assertEquals(new URL("?e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?e");
-unitTest(function urlBaseString(): void {
- const url = new URL(
- "/foo/bar?baz=foo#qux",
- "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat",
- );
- assertEquals(url.href, "https://foo:bar@baz.qat:8000/foo/bar?baz=foo#qux");
-});
+ assertEquals(new URL("e", "http://foo/a/b?c#d").href, "http://foo/a/e");
+ assertEquals(new URL("e", "file://foo/a/b?c#d").href, "file://foo/a/e");
+ assertEquals(new URL("e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/e");
+
+ assertEquals(new URL(".", "http://foo/a/b?c#d").href, "http://foo/a/");
+ assertEquals(new URL(".", "file://foo/a/b?c#d").href, "file://foo/a/");
+ assertEquals(new URL(".", "abcd://foo/a/b?c#d").href, "abcd://foo/a/");
+
+ assertEquals(new URL("..", "http://foo/a/b?c#d").href, "http://foo/");
+ assertEquals(new URL("..", "file://foo/a/b?c#d").href, "file://foo/");
+ assertEquals(new URL("..", "abcd://foo/a/b?c#d").href, "abcd://foo/");
-unitTest(function urlRelativeWithBase(): void {
- assertEquals(new URL("", "file:///a/a/a").href, "file:///a/a/a");
- assertEquals(new URL(".", "file:///a/a/a").href, "file:///a/a/");
- assertEquals(new URL("..", "file:///a/a/a").href, "file:///a/");
- assertEquals(new URL("b", "file:///a/a/a").href, "file:///a/a/b");
- assertEquals(new URL("b", "file:///a/a/a/").href, "file:///a/a/a/b");
- assertEquals(new URL("b/", "file:///a/a/a").href, "file:///a/a/b/");
- assertEquals(new URL("../b", "file:///a/a/a").href, "file:///a/b");
+ assertEquals(new URL("/e", "http://foo/a/b?c#d").href, "http://foo/e");
+ assertEquals(new URL("/e", "file://foo/a/b?c#d").href, "file://foo/e");
+ assertEquals(new URL("/e", "abcd://foo/a/b?c#d").href, "abcd://foo/e");
+
+ assertEquals(new URL("//bar", "http://foo/a/b?c#d").href, "http://bar/");
+ assertEquals(new URL("//bar", "file://foo/a/b?c#d").href, "file://bar/");
+ assertEquals(new URL("//bar", "abcd://foo/a/b?c#d").href, "abcd://bar");
+
+ assertEquals(new URL("efgh:", "http://foo/a/b?c#d").href, "efgh:");
+ assertEquals(new URL("efgh:", "file://foo/a/b?c#d").href, "efgh:");
+ assertEquals(new URL("efgh:", "abcd://foo/a/b?c#d").href, "efgh:");
});
unitTest(function urlDriveLetterBase() {
- assertEquals(
- new URL("/b", "file:///C:/a/b").href,
- Deno.build.os == "windows" ? "file:///C:/b" : "file:///b",
- );
- assertEquals(
- new URL("D:", "file:///C:/a/b").href,
- Deno.build.os == "windows" ? "file:///D:/" : "file:///C:/a/D:",
- );
- assertEquals(
- new URL("/D:", "file:///C:/a/b").href,
- Deno.build.os == "windows" ? "file:///D:/" : "file:///D:",
- );
- assertEquals(
- new URL("D:/b", "file:///C:/a/b").href,
- Deno.build.os == "windows" ? "file:///D:/b" : "file:///C:/a/D:/b",
- );
+ assertEquals(new URL("/b", "file:///C:/a/b").href, "file:///C:/b");
+ assertEquals(new URL("/D:", "file:///C:/a/b").href, "file:///D:");
});
-unitTest(function emptyBasePath(): void {
- assertEquals(new URL("", "http://example.com").href, "http://example.com/");
+unitTest(function urlSameProtocolBase() {
+ assertEquals(new URL("http:", "http://foo/a").href, "http://foo/a");
+ assertEquals(new URL("file:", "file://foo/a").href, "file://foo/a");
+ assertEquals(new URL("abcd:", "abcd://foo/a").href, "abcd:");
+
+ assertEquals(new URL("http:b", "http://foo/a").href, "http://foo/b");
+ assertEquals(new URL("file:b", "file://foo/a").href, "file://foo/b");
+ assertEquals(new URL("abcd:b", "abcd://foo/a").href, "abcd:b");
});
unitTest(function deletingAllParamsRemovesQuestionMarkFromURL(): void {
@@ -391,12 +396,6 @@ unitTest(function protocolNotHttpOrFile() {
assertEquals(url.origin, "null");
});
-unitTest(function createBadUrl(): void {
- assertThrows(() => {
- new URL("0.0.0.0:8080");
- });
-});
-
unitTest(function throwForInvalidPortConstructor(): void {
const urls = [
// If port is greater than 2^16 − 1, validation error, return failure.
@@ -416,14 +415,6 @@ unitTest(function throwForInvalidPortConstructor(): void {
new URL("https://baz.qat:0");
});
-unitTest(function throwForInvalidSchemeConstructor(): void {
- assertThrows(
- () => new URL("invalid_scheme://baz.qat"),
- TypeError,
- "Invalid URL.",
- );
-});
-
unitTest(function doNotOverridePortIfInvalid(): void {
const initialPort = "3000";
const ports = [
diff --git a/std/path/from_file_url_test.ts b/std/path/from_file_url_test.ts
index 8bbc4e986..7a0432f68 100644
--- a/std/path/from_file_url_test.ts
+++ b/std/path/from_file_url_test.ts
@@ -7,14 +7,13 @@ Deno.test("[path] fromFileUrl", function () {
assertEquals(posix.fromFileUrl("file:///home/foo"), "/home/foo");
assertEquals(posix.fromFileUrl("https://example.com/foo"), "/foo");
assertEquals(posix.fromFileUrl("file:///"), "/");
- // FIXME(nayeemrmn): Remove the condition. UNC paths are supported here when
- // run on Windows (matching the underlying URL class), but
- // `posix.fromFileUrl()` should not support them under any circumstance.
- if (Deno.build.os != "windows") {
- assertEquals(posix.fromFileUrl("file:////"), "//");
- assertEquals(posix.fromFileUrl("file:////server"), "//server");
- assertEquals(posix.fromFileUrl("file:////server/file"), "//server/file");
- }
+ // Drive letters are supported platform-independently to align with the WHATWG
+ // URL specification.
+ assertEquals(posix.fromFileUrl("file:///c:"), "c:/");
+ assertEquals(posix.fromFileUrl("file:///c:/"), "c:/");
+ assertEquals(posix.fromFileUrl("file:///C:/"), "C:/");
+ assertEquals(posix.fromFileUrl("file:///C:/Users/"), "C:/Users/");
+ assertEquals(posix.fromFileUrl("file:///C:foo/bar"), "/C:foo/bar");
});
Deno.test("[path] fromFileUrl (win32)", function () {
@@ -22,18 +21,9 @@ Deno.test("[path] fromFileUrl (win32)", function () {
assertEquals(win32.fromFileUrl("file:///home/foo"), "\\home\\foo");
assertEquals(win32.fromFileUrl("https://example.com/foo"), "\\foo");
assertEquals(win32.fromFileUrl("file:///"), "\\");
- // FIXME(nayeemrmn): Remove the condition. UNC paths are only supported here
- // when run on Windows (matching the underlying URL class), but
- // `win32.fromFileUrl()` should support them under every circumstance.
- if (Deno.build.os == "windows") {
- assertEquals(win32.fromFileUrl("file:////"), "\\");
- assertEquals(win32.fromFileUrl("file:////server"), "\\");
- assertEquals(win32.fromFileUrl("file:////server/file"), "\\file");
- }
- assertEquals(win32.fromFileUrl("file:///c"), "\\c");
assertEquals(win32.fromFileUrl("file:///c:"), "c:\\");
assertEquals(win32.fromFileUrl("file:///c:/"), "c:\\");
assertEquals(win32.fromFileUrl("file:///C:/"), "C:\\");
assertEquals(win32.fromFileUrl("file:///C:/Users/"), "C:\\Users\\");
- assertEquals(win32.fromFileUrl("file:///C:cwd/another"), "\\C:cwd\\another");
+ assertEquals(win32.fromFileUrl("file:///C:foo/bar"), "\\C:foo\\bar");
});
diff --git a/std/path/posix.ts b/std/path/posix.ts
index 03d07a84a..3c4262203 100644
--- a/std/path/posix.ts
+++ b/std/path/posix.ts
@@ -435,5 +435,6 @@ export function parse(path: string): ParsedPath {
* are ignored.
*/
export function fromFileUrl(url: string | URL): string {
- return new URL(String(url)).pathname;
+ return (url instanceof URL ? url : new URL(url)).pathname
+ .replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/");
}
diff --git a/std/path/win32.ts b/std/path/win32.ts
index 66ed1ff14..cf0e69537 100644
--- a/std/path/win32.ts
+++ b/std/path/win32.ts
@@ -914,7 +914,7 @@ export function parse(path: string): ParsedPath {
* are ignored.
*/
export function fromFileUrl(url: string | URL): string {
- return new URL(String(url)).pathname
+ return (url instanceof URL ? url : new URL(url)).pathname
.replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/")
.replace(/\//g, "\\");
}