fix(cli/rt): Fix file URL to path conversion on Windows (#6920)

This commit is contained in:
Nayeem Rahman 2020-07-30 23:37:26 +01:00 committed by GitHub
parent 0da4779b17
commit 6e7208bec2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 62 additions and 40 deletions

View file

@ -64,23 +64,18 @@
} }
function pathFromURLWin32(url) { function pathFromURLWin32(url) {
const hostname = url.hostname; let path = decodeURIComponent(
const pathname = decodeURIComponent(url.pathname.replace(/\//g, "\\")); url.pathname
.replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/")
if (hostname !== "") { .replace(/\//g, "\\"),
//TODO(actual-size) Node adds a punycode decoding step, we should consider adding this );
return `\\\\${hostname}${pathname}`; 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;
const validPath = /^\\(?<driveLetter>[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);
} }
function pathFromURLPosix(url) { function pathFromURLPosix(url) {

View file

@ -1,31 +1,49 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { posix, win32 } from "./mod.ts"; import { posix, win32 } from "./mod.ts";
import { assertEquals } from "../testing/asserts.ts"; import { assertEquals, assertThrows } from "../testing/asserts.ts";
Deno.test("[path] fromFileUrl", function () { Deno.test("[path] fromFileUrl", function () {
assertEquals(posix.fromFileUrl(new URL("file:///home/foo")), "/home/foo"); 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"), "/home/foo");
assertEquals(posix.fromFileUrl("file:///home/foo%20bar"), "/home/foo bar"); assertEquals(posix.fromFileUrl("file:///home/foo%20bar"), "/home/foo bar");
assertEquals(posix.fromFileUrl("https://example.com/foo"), "/foo"); assertEquals(posix.fromFileUrl("file://localhost/foo"), "/foo");
assertEquals(posix.fromFileUrl("file:///"), "/"); assertEquals(posix.fromFileUrl("file:///C:"), "/C:");
// Drive letters are supported platform-independently to align with the WHATWG assertEquals(posix.fromFileUrl("file:///C:/"), "/C:/");
// URL specification. assertEquals(posix.fromFileUrl("file:///C:/Users/"), "/C:/Users/");
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"); 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 () { Deno.test("[path] fromFileUrl (win32)", function () {
assertEquals(win32.fromFileUrl(new URL("file:///home/foo")), "\\home\\foo"); 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"), "\\home\\foo");
assertEquals(win32.fromFileUrl("file:///home/foo%20bar"), "\\home\\foo bar"); assertEquals(win32.fromFileUrl("file:///home/foo%20bar"), "\\home\\foo bar");
assertEquals(win32.fromFileUrl("https://example.com/foo"), "\\foo"); assertEquals(win32.fromFileUrl("file://localhost/foo"), "\\\\localhost\\foo");
assertEquals(win32.fromFileUrl("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:/"), "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:/Users/"), "C:\\Users\\");
assertEquals(win32.fromFileUrl("file:///C:foo/bar"), "\\C:foo\\bar"); 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.",
);
}); });

View file

@ -430,11 +430,11 @@ export function parse(path: string): ParsedPath {
/** Converts a file URL to a path string. /** Converts a file URL to a path string.
* *
* fromFileUrl("file:///home/foo"); // "/home/foo" * 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 { 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);
.replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/")); if (url.protocol != "file:") {
throw new TypeError("Must be a file URL.");
}
return decodeURIComponent(url.pathname);
} }

View file

@ -907,16 +907,25 @@ export function parse(path: string): ParsedPath {
/** Converts a file URL to a path string. /** Converts a file URL to a path string.
* *
* fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo"
* fromFileUrl("file:///home/foo"); // "\\home\\foo" * fromFileUrl("file:///home/foo"); // "\\home\\foo"
* * fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo"
* Note that non-file URLs are treated as file URLs and irrelevant components * fromFileUrl("file://localhost/home/foo"); // "\\\\localhost\\home\\foo"
* are ignored.
*/ */
export function fromFileUrl(url: string | URL): string { export function fromFileUrl(url: string | URL): string {
return decodeURIComponent( url = url instanceof URL ? url : new URL(url);
(url instanceof URL ? url : new URL(url)).pathname if (url.protocol != "file:") {
throw new TypeError("Must be a file URL.");
}
let path = decodeURIComponent(
url.pathname
.replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/") .replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/")
.replace(/\//g, "\\"), .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;
} }