diff --git a/cli/rt/06_util.js b/cli/rt/06_util.js index 086275bd87..3570cb1fa6 100644 --- a/cli/rt/06_util.js +++ b/cli/rt/06_util.js @@ -64,23 +64,18 @@ } function pathFromURLWin32(url) { - const hostname = url.hostname; - const pathname = decodeURIComponent(url.pathname.replace(/\//g, "\\")); - - if (hostname !== "") { - //TODO(actual-size) Node adds a punycode decoding step, we should consider adding this - return `\\\\${hostname}${pathname}`; + let path = decodeURIComponent( + url.pathname + .replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/") + .replace(/\//g, "\\"), + ); + if (url.hostname != "") { + // Note: The `URL` implementation guarantees that the drive letter and + // hostname are mutually exclusive. Otherwise it would not have been valid + // to append the hostname and path like this. + path = `\\\\${url.hostname}${path}`; } - - const validPath = /^\\(?[A-Za-z]):\\/; - const matches = validPath.exec(pathname); - - if (!matches?.groups?.driveLetter) { - throw new TypeError("A URL with the file schema must be absolute."); - } - - // we don't want a leading slash on an absolute path in Windows - return pathname.slice(1); + return path; } function pathFromURLPosix(url) { diff --git a/std/path/from_file_url_test.ts b/std/path/from_file_url_test.ts index 8fe47b27f3..b1a1af99c0 100644 --- a/std/path/from_file_url_test.ts +++ b/std/path/from_file_url_test.ts @@ -1,31 +1,49 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { posix, win32 } from "./mod.ts"; -import { assertEquals } from "../testing/asserts.ts"; +import { assertEquals, assertThrows } from "../testing/asserts.ts"; Deno.test("[path] fromFileUrl", function () { assertEquals(posix.fromFileUrl(new URL("file:///home/foo")), "/home/foo"); + assertEquals(posix.fromFileUrl("file:///"), "/"); assertEquals(posix.fromFileUrl("file:///home/foo"), "/home/foo"); assertEquals(posix.fromFileUrl("file:///home/foo%20bar"), "/home/foo bar"); - assertEquals(posix.fromFileUrl("https://example.com/foo"), "/foo"); - assertEquals(posix.fromFileUrl("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://localhost/foo"), "/foo"); + 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"); + assertThrows( + () => posix.fromFileUrl("http://localhost/foo"), + TypeError, + "Must be a file URL.", + ); + assertThrows( + () => posix.fromFileUrl("abcd://localhost/foo"), + TypeError, + "Must be a file URL.", + ); }); Deno.test("[path] fromFileUrl (win32)", function () { assertEquals(win32.fromFileUrl(new URL("file:///home/foo")), "\\home\\foo"); + assertEquals(win32.fromFileUrl("file:///"), "\\"); assertEquals(win32.fromFileUrl("file:///home/foo"), "\\home\\foo"); assertEquals(win32.fromFileUrl("file:///home/foo%20bar"), "\\home\\foo bar"); - assertEquals(win32.fromFileUrl("https://example.com/foo"), "\\foo"); - assertEquals(win32.fromFileUrl("file:///"), "\\"); - assertEquals(win32.fromFileUrl("file:///c:"), "c:\\"); - assertEquals(win32.fromFileUrl("file:///c:/"), "c:\\"); + assertEquals(win32.fromFileUrl("file://localhost/foo"), "\\\\localhost\\foo"); + assertEquals(win32.fromFileUrl("file:///C:"), "C:\\"); assertEquals(win32.fromFileUrl("file:///C:/"), "C:\\"); + // Drop the hostname if a drive letter is parsed. + assertEquals(win32.fromFileUrl("file://localhost/C:/"), "C:\\"); assertEquals(win32.fromFileUrl("file:///C:/Users/"), "C:\\Users\\"); assertEquals(win32.fromFileUrl("file:///C:foo/bar"), "\\C:foo\\bar"); + assertThrows( + () => win32.fromFileUrl("http://localhost/foo"), + TypeError, + "Must be a file URL.", + ); + assertThrows( + () => win32.fromFileUrl("abcd://localhost/foo"), + TypeError, + "Must be a file URL.", + ); }); diff --git a/std/path/posix.ts b/std/path/posix.ts index fca7f081bd..afbc9303f2 100644 --- a/std/path/posix.ts +++ b/std/path/posix.ts @@ -430,11 +430,11 @@ export function parse(path: string): ParsedPath { /** Converts a file URL to a path string. * * fromFileUrl("file:///home/foo"); // "/home/foo" - * - * Note that non-file URLs are treated as file URLs and irrelevant components - * are ignored. */ export function fromFileUrl(url: string | URL): string { - return decodeURIComponent((url instanceof URL ? url : new URL(url)).pathname - .replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/")); + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + return decodeURIComponent(url.pathname); } diff --git a/std/path/win32.ts b/std/path/win32.ts index 0283f4b9ce..eed1cbdb82 100644 --- a/std/path/win32.ts +++ b/std/path/win32.ts @@ -907,16 +907,25 @@ export function parse(path: string): ParsedPath { /** Converts a file URL to a path string. * - * fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo" * fromFileUrl("file:///home/foo"); // "\\home\\foo" - * - * Note that non-file URLs are treated as file URLs and irrelevant components - * are ignored. + * fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo" + * fromFileUrl("file://localhost/home/foo"); // "\\\\localhost\\home\\foo" */ export function fromFileUrl(url: string | URL): string { - return decodeURIComponent( - (url instanceof URL ? url : new URL(url)).pathname + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + let path = decodeURIComponent( + url.pathname .replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/") .replace(/\//g, "\\"), ); + if (url.hostname != "") { + // Note: The `URL` implementation guarantees that the drive letter and + // hostname are mutually exclusive. Otherwise it would not have been valid + // to append the hostname and path like this. + path = `\\\\${url.hostname}${path}`; + } + return path; }