0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 12:16:11 -05:00

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.
This commit is contained in:
Nayeem Rahman 2020-07-24 02:37:11 +01:00 committed by GitHub
parent b61347b255
commit a8f74aa381
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 265 additions and 257 deletions

View file

@ -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,}([^/\\?#]*)/,
);
}
} else {
let restAuthority;
if (isSpecial) {
parts.slashes = "//";
[restAuthority, restUrl] = takePattern(
restUrl,
/^[/\\]{2,}([^/\\?#]*)/,
/^[/\\]{2}([^/\\?#]*)/,
);
usedNonBase = true;
} else {
parts.slashes = restUrl.match(/^[/\\]{2}/) ? "//" : "";
[restAuthority, restUrl] = takePattern(restUrl, /^[/\\]{2}([^/\\?#]*)/);
parts.hostname = baseParts.hostname;
}
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.port = "";
} else {
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.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;
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 = "";
}
if (isAbsolutePath(normalizedPath)) {
return `${driveLetterPrefix}${normalizedPath}`;
}
if (!isAbsolutePath(normalizedBasePath)) {
throw new TypeError("Base path must be absolute.");
}
// Special case.
if (path == "") {
return `${driveLetterPrefix}${normalizedBasePath}`;
}
// 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();
}

View file

@ -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");
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(".", "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/");
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 = [

View file

@ -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");
});

View file

@ -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/");
}

View file

@ -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, "\\");
}