Skip to content

Commit 5a3f3e4

Browse files
mischnicljharb
authored andcommitted
Add fileURLToPath
1 parent b449442 commit 5a3f3e4

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,10 @@ an anchor tag. Examples:
106106
url.resolve('/one/two/three', 'four') // '/one/two/four'
107107
url.resolve('http://example.com/', '/one') // 'http://example.com/one'
108108
url.resolve('http://example.com/one', '/two') // 'http://example.com/two'
109+
110+
### url.fileURLToPath(url)
111+
112+
Take a string or WHATWG `URL` object representing a filepath, and return the POSIX
113+
filepath as a string.
114+
115+
url.fileURLToPath('file:///etc/hosts') // '/etc/hosts'

test/index.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,3 +2055,74 @@ relativeTests2.forEach(function (relativeTest) {
20552055
assert.equal(actual, expected, 'format(' + relativeTest[1] + ') == ' + expected + '\nactual:' + actual);
20562056
});
20572057
});
2058+
2059+
var fileURLToPathTestCases = [
2060+
// Lowercase ascii alpha
2061+
{ path: '/foo', fileURL: 'file:///foo' },
2062+
// Uppercase ascii alpha
2063+
{ path: '/FOO', fileURL: 'file:///FOO' },
2064+
// dir
2065+
{ path: '/dir/foo', fileURL: 'file:///dir/foo' },
2066+
// trailing separator
2067+
{ path: '/dir/', fileURL: 'file:///dir/' },
2068+
// dot
2069+
{ path: '/foo.mjs', fileURL: 'file:///foo.mjs' },
2070+
// space
2071+
{ path: '/foo bar', fileURL: 'file:///foo%20bar' },
2072+
// question mark
2073+
{ path: '/foo?bar', fileURL: 'file:///foo%3Fbar' },
2074+
// number sign
2075+
{ path: '/foo#bar', fileURL: 'file:///foo%23bar' },
2076+
// ampersand
2077+
{ path: '/foo&bar', fileURL: 'file:///foo&bar' },
2078+
// equals
2079+
{ path: '/foo=bar', fileURL: 'file:///foo=bar' },
2080+
// colon
2081+
{ path: '/foo:bar', fileURL: 'file:///foo:bar' },
2082+
// semicolon
2083+
{ path: '/foo;bar', fileURL: 'file:///foo;bar' },
2084+
// percent
2085+
{ path: '/foo%bar', fileURL: 'file:///foo%25bar' },
2086+
// backslash
2087+
{ path: '/foo\\bar', fileURL: 'file:///foo%5Cbar' },
2088+
// backspace
2089+
{ path: '/foo\bbar', fileURL: 'file:///foo%08bar' },
2090+
// tab
2091+
{ path: '/foo\tbar', fileURL: 'file:///foo%09bar' },
2092+
// newline
2093+
{ path: '/foo\nbar', fileURL: 'file:///foo%0Abar' },
2094+
// carriage return
2095+
{ path: '/foo\rbar', fileURL: 'file:///foo%0Dbar' },
2096+
// latin1
2097+
{ path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
2098+
// Euro sign (BMP code point)
2099+
{ path: '/€', fileURL: 'file:///%E2%82%AC' },
2100+
// Rocket emoji (non-BMP code point)
2101+
{ path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' }
2102+
];
2103+
2104+
fileURLToPathTestCases.forEach(function (fileURLToPathTestCase) {
2105+
test('fileURLToPath(' + fileURLToPathTestCase.fileURL + ')', function () {
2106+
var fromString = url.fileURLToPath(fileURLToPathTestCase.fileURL);
2107+
assert.strictEqual(fromString, fileURLToPathTestCase.path);
2108+
var fromURL = url.fileURLToPath(new URL(fileURLToPathTestCase.fileURL));
2109+
assert.strictEqual(fromURL, fileURLToPathTestCase.path);
2110+
});
2111+
});
2112+
2113+
[
2114+
'https://host/y',
2115+
'file://host/a',
2116+
new URL('https://host/y'),
2117+
'file:///a%2F/',
2118+
'',
2119+
null,
2120+
undefined,
2121+
1,
2122+
{},
2123+
true
2124+
].forEach(function (val) {
2125+
test('fileURLToPath(' + val + ')', function () {
2126+
assert['throws'](function () { url.fileURLToPath(val); }, TypeError);
2127+
});
2128+
});

url.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,4 +770,38 @@ exports.resolve = urlResolve;
770770
exports.resolveObject = urlResolveObject;
771771
exports.format = urlFormat;
772772

773+
function isURLInstance(fileURLOrPath) {
774+
return fileURLOrPath != null && fileURLOrPath.href && fileURLOrPath.origin;
775+
}
776+
777+
function getPathFromURLPosix(url) {
778+
if (url.hostname !== '') {
779+
throw new TypeError('File URL host must be "localhost" or empty on darwin');
780+
}
781+
var pathname = url.pathname;
782+
for (var n = 0; n < pathname.length; n++) {
783+
if (pathname[n] === '%') {
784+
var third = pathname.codePointAt(n + 2) | 0x20;
785+
if (pathname[n + 1] === '2' && third === 102) {
786+
throw new TypeError('File URL path must not include encoded / characters');
787+
}
788+
}
789+
}
790+
return decodeURIComponent(pathname);
791+
}
792+
793+
function fileURLToPath(path) {
794+
if (typeof path === 'string') {
795+
path = new URL(path);
796+
} else if (!isURLInstance(path)) {
797+
throw new TypeError('The "path" argument must be of type string or an instance of URL. Received ' + path);
798+
}
799+
if (path.protocol !== 'file:') {
800+
throw new TypeError('The URL must be of scheme file');
801+
}
802+
return getPathFromURLPosix(path);
803+
}
804+
805+
exports.fileURLToPath = fileURLToPath;
806+
773807
exports.Url = Url;

0 commit comments

Comments
 (0)