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:
parent
b61347b255
commit
a8f74aa381
5 changed files with 265 additions and 257 deletions
238
cli/rt/11_url.js
238
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,40 +263,56 @@
|
|||
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 {
|
||||
if (usedNonBase || restUrl.match(/^[/\\]{2}/)) {
|
||||
let restAuthority;
|
||||
if (isSpecial) {
|
||||
parts.slashes = "//";
|
||||
[restAuthority, restUrl] = takePattern(
|
||||
restUrl,
|
||||
/^[/\\]{2,}([^/\\?#]*)/,
|
||||
/^[/\\]*([^/\\?#]*)/,
|
||||
);
|
||||
} else {
|
||||
parts.slashes = restUrl.match(/^[/\\]{2}/) ? "//" : "";
|
||||
[restAuthority, restUrl] = takePattern(restUrl, /^[/\\]{2}([^/\\?#]*)/);
|
||||
[restAuthority, restUrl] = takePattern(
|
||||
restUrl,
|
||||
/^[/\\]{2}([^/\\?#]*)/,
|
||||
);
|
||||
}
|
||||
let restAuthentication;
|
||||
[restAuthentication, restAuthority] = takePattern(
|
||||
|
@ -317,21 +332,49 @@
|
|||
);
|
||||
[parts.port] = takePattern(restAuthority, /^:(.*)/);
|
||||
if (!isValidPort(parts.port)) {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
if (parts.hostname == "" && isSpecial && isBase) {
|
||||
return undefined;
|
||||
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, "/"));
|
||||
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;
|
||||
}
|
||||
|
||||
if (isAbsolutePath(normalizedPath)) {
|
||||
return `${driveLetterPrefix}${normalizedPath}`;
|
||||
} 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(/[^\/]*$/, "");
|
||||
}
|
||||
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(/[^\/]*$/, "");
|
||||
basePrefix = basePrefix.replace(/\/*$/, "/");
|
||||
// If `normalizedPath` ends with `.` or `..`, add a trailing slash.
|
||||
const suffix = normalizedPath.replace(/(?<=(^|\/)(\.|\.\.))$/, "/");
|
||||
|
||||
return `${driveLetterPrefix}${normalizePath(prefix + suffix)}`;
|
||||
suffix = path.replace(/(?<=(^|\/)(\.|\.\.))$/, "/");
|
||||
} else {
|
||||
basePrefix = basePath;
|
||||
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) {
|
||||
const urlParts = url instanceof URL
|
||||
? parts.get(url)
|
||||
: parse(url, baseParts);
|
||||
if (urlParts == null) {
|
||||
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 {
|
||||
throw new TypeError("Invalid URL.");
|
||||
}
|
||||
|
||||
this.#updateSearchParams();
|
||||
}
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
|
|
@ -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/");
|
||||
}
|
||||
|
|
|
@ -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, "\\");
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue