From 4d95ca159bf26ea5dc1d4ef299c5e98d94f43f27 Mon Sep 17 00:00:00 2001 From: guybedford Date: Fri, 24 Aug 2018 18:13:32 +0200 Subject: [PATCH 01/18] url: expose pathToFileUrl and fileUrlToPath --- doc/api/url.md | 45 +++++++++ lib/fs.js | 100 ++++++++++---------- lib/internal/fs/promises.js | 46 ++++----- lib/internal/fs/streams.js | 6 +- lib/internal/fs/watchers.js | 6 +- lib/internal/modules/cjs/loader.js | 8 +- lib/internal/modules/esm/default_resolve.js | 8 +- lib/internal/modules/esm/translators.js | 6 +- lib/internal/process/esm_loader.js | 6 +- lib/internal/url.js | 22 +++-- lib/url.js | 8 +- test/common/inspector-helper.js | 6 +- 12 files changed, 162 insertions(+), 105 deletions(-) diff --git a/doc/api/url.md b/doc/api/url.md index 8013b1ffb75462..2865f2fdea8423 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -921,6 +921,51 @@ console.log(url.format(myURL, { fragment: false, unicode: true, auth: false })); // Prints 'https://你好你好/?abc' ``` +## File URL Utility Functions + +When working with `file:///` URLs in Node.js (eg when working with ES modules +which are keyed in the registry by File URL), the utility functions +`urlToFilePath` and `fileUrlToPath` are provided to convert to and from file +paths. + +The edge cases handled by these functions include percent-encoding and decoding +as well as cross-platform support. + +For example, instead of writing: + +```js +// BAD: +// - fails in Windows +// - doesn't handle loading paths using extended non-latin characters +fs.promises.readFile(fileUrl.pathname); +``` + +write: + +```js +// GOOD: +// - works in Windows +// - handles emoji file paths +const { fileUrlToPath } = require('url'); +fs.promises.readFile(fileUrlToPath(fileUrl)); +``` + +### pathToFileUrl(path) + +* `path` {string} The absolute path to convert to a File URL. +* Returns: {URL} The file URL object. + +This function ensures the correct encodings of URL control characters in file paths +when converting into File URLs. + +### fileUrlToPath(url) + +* `url` {URL} | {string} The file URL string or URL object to convert to a path. +* Returns: {URL} The fully-resolved platform-specific Node.js file path. + +This function ensures the correct decodings of percent-encoded characters as well +as ensuring a cross-platform valid absolute path string. + ## Legacy URL API ### Legacy `urlObject` diff --git a/lib/fs.js b/lib/fs.js index a0e5f64476c0fd..2c0eebea4bd6ea 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -54,7 +54,7 @@ const { const { FSReqCallback, statValues } = binding; const internalFS = require('internal/fs/utils'); -const { getPathFromURL } = require('internal/url'); +const { toPathIfFileUrl } = require('internal/url'); const internalUtil = require('internal/util'); const { copyObject, @@ -177,7 +177,7 @@ function access(path, mode, callback) { mode = F_OK; } - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); mode = mode | 0; @@ -187,7 +187,7 @@ function access(path, mode, callback) { } function accessSync(path, mode) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); if (mode === undefined) @@ -297,7 +297,7 @@ function readFile(path, options, callback) { return; } - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); binding.open(pathModule.toNamespacedPath(path), stringToFlags(options.flag || 'r'), @@ -409,7 +409,7 @@ function closeSync(fd) { } function open(path, flags, mode, callback) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const flagsNumber = stringToFlags(flags); if (arguments.length < 4) { @@ -431,7 +431,7 @@ function open(path, flags, mode, callback) { function openSync(path, flags, mode) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const flagsNumber = stringToFlags(flags); mode = validateMode(mode, 'mode', 0o666); @@ -587,9 +587,9 @@ function writeSync(fd, buffer, offset, length, position) { function rename(oldPath, newPath, callback) { callback = makeCallback(callback); - oldPath = getPathFromURL(oldPath); + oldPath = toPathIfFileUrl(oldPath); validatePath(oldPath, 'oldPath'); - newPath = getPathFromURL(newPath); + newPath = toPathIfFileUrl(newPath); validatePath(newPath, 'newPath'); const req = new FSReqCallback(); req.oncomplete = callback; @@ -599,9 +599,9 @@ function rename(oldPath, newPath, callback) { } function renameSync(oldPath, newPath) { - oldPath = getPathFromURL(oldPath); + oldPath = toPathIfFileUrl(oldPath); validatePath(oldPath, 'oldPath'); - newPath = getPathFromURL(newPath); + newPath = toPathIfFileUrl(newPath); validatePath(newPath, 'newPath'); const ctx = { path: oldPath, dest: newPath }; binding.rename(pathModule.toNamespacedPath(oldPath), @@ -680,7 +680,7 @@ function ftruncateSync(fd, len = 0) { function rmdir(path, callback) { callback = makeCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const req = new FSReqCallback(); req.oncomplete = callback; @@ -688,7 +688,7 @@ function rmdir(path, callback) { } function rmdirSync(path) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const ctx = { path }; binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx); @@ -735,7 +735,7 @@ function mkdir(path, options, callback) { mode = 0o777 } = options || {}; callback = makeCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); if (typeof recursive !== 'boolean') @@ -751,7 +751,7 @@ function mkdirSync(path, options) { if (typeof options === 'number' || typeof options === 'string') { options = { mode: options }; } - path = getPathFromURL(path); + path = toPathIfFileUrl(path); const { recursive = false, mode = 0o777 @@ -771,7 +771,7 @@ function mkdirSync(path, options) { function readdir(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const req = new FSReqCallback(); @@ -792,7 +792,7 @@ function readdir(path, options, callback) { function readdirSync(path, options) { options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const ctx = { path }; const result = binding.readdir(pathModule.toNamespacedPath(path), @@ -819,7 +819,7 @@ function lstat(path, options, callback) { options = {}; } callback = makeStatsCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const req = new FSReqCallback(options.bigint); req.oncomplete = callback; @@ -832,7 +832,7 @@ function stat(path, options, callback) { options = {}; } callback = makeStatsCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const req = new FSReqCallback(options.bigint); req.oncomplete = callback; @@ -848,7 +848,7 @@ function fstatSync(fd, options = {}) { } function lstatSync(path, options = {}) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const ctx = { path }; const stats = binding.lstat(pathModule.toNamespacedPath(path), @@ -858,7 +858,7 @@ function lstatSync(path, options = {}) { } function statSync(path, options = {}) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const ctx = { path }; const stats = binding.stat(pathModule.toNamespacedPath(path), @@ -870,7 +870,7 @@ function statSync(path, options = {}) { function readlink(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path, 'oldPath'); const req = new FSReqCallback(); req.oncomplete = callback; @@ -879,7 +879,7 @@ function readlink(path, options, callback) { function readlinkSync(path, options) { options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path, 'oldPath'); const ctx = { path }; const result = binding.readlink(pathModule.toNamespacedPath(path), @@ -892,8 +892,8 @@ function symlink(target, path, type_, callback_) { const type = (typeof type_ === 'string' ? type_ : null); const callback = makeCallback(arguments[arguments.length - 1]); - target = getPathFromURL(target); - path = getPathFromURL(path); + target = toPathIfFileUrl(target); + path = toPathIfFileUrl(path); validatePath(target, 'target'); validatePath(path); @@ -907,8 +907,8 @@ function symlink(target, path, type_, callback_) { function symlinkSync(target, path, type) { type = (typeof type === 'string' ? type : null); - target = getPathFromURL(target); - path = getPathFromURL(path); + target = toPathIfFileUrl(target); + path = toPathIfFileUrl(path); validatePath(target, 'target'); validatePath(path); const flags = stringToSymlinkType(type); @@ -923,8 +923,8 @@ function symlinkSync(target, path, type) { function link(existingPath, newPath, callback) { callback = makeCallback(callback); - existingPath = getPathFromURL(existingPath); - newPath = getPathFromURL(newPath); + existingPath = toPathIfFileUrl(existingPath); + newPath = toPathIfFileUrl(newPath); validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); @@ -937,8 +937,8 @@ function link(existingPath, newPath, callback) { } function linkSync(existingPath, newPath) { - existingPath = getPathFromURL(existingPath); - newPath = getPathFromURL(newPath); + existingPath = toPathIfFileUrl(existingPath); + newPath = toPathIfFileUrl(newPath); validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); @@ -952,7 +952,7 @@ function linkSync(existingPath, newPath) { function unlink(path, callback) { callback = makeCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const req = new FSReqCallback(); req.oncomplete = callback; @@ -960,7 +960,7 @@ function unlink(path, callback) { } function unlinkSync(path) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const ctx = { path }; binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx); @@ -1018,7 +1018,7 @@ function lchmodSync(path, mode) { function chmod(path, mode, callback) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); mode = validateMode(mode, 'mode'); callback = makeCallback(callback); @@ -1029,7 +1029,7 @@ function chmod(path, mode, callback) { } function chmodSync(path, mode) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); mode = validateMode(mode, 'mode'); @@ -1040,7 +1040,7 @@ function chmodSync(path, mode) { function lchown(path, uid, gid, callback) { callback = makeCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1050,7 +1050,7 @@ function lchown(path, uid, gid, callback) { } function lchownSync(path, uid, gid) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1081,7 +1081,7 @@ function fchownSync(fd, uid, gid) { function chown(path, uid, gid, callback) { callback = makeCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1092,7 +1092,7 @@ function chown(path, uid, gid, callback) { } function chownSync(path, uid, gid) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1103,7 +1103,7 @@ function chownSync(path, uid, gid) { function utimes(path, atime, mtime, callback) { callback = makeCallback(callback); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const req = new FSReqCallback(); @@ -1115,7 +1115,7 @@ function utimes(path, atime, mtime, callback) { } function utimesSync(path, atime, mtime) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const ctx = { path }; binding.utimes(pathModule.toNamespacedPath(path), @@ -1282,7 +1282,7 @@ function watch(filename, options, listener) { const statWatchers = new Map(); function watchFile(filename, options, listener) { - filename = getPathFromURL(filename); + filename = toPathIfFileUrl(filename); validatePath(filename); filename = pathModule.resolve(filename); let stat; @@ -1321,7 +1321,7 @@ function watchFile(filename, options, listener) { } function unwatchFile(filename, listener) { - filename = getPathFromURL(filename); + filename = toPathIfFileUrl(filename); validatePath(filename); filename = pathModule.resolve(filename); const stat = statWatchers.get(filename); @@ -1393,7 +1393,7 @@ function realpathSync(p, options) { options = emptyObj; else options = getOptions(options, emptyObj); - p = getPathFromURL(p); + p = toPathIfFileUrl(p); if (typeof p !== 'string') { p += ''; } @@ -1525,7 +1525,7 @@ function realpathSync(p, options) { realpathSync.native = function(path, options) { options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const ctx = { path }; const result = binding.realpath(path, options.encoding, undefined, ctx); @@ -1540,7 +1540,7 @@ function realpath(p, options, callback) { options = emptyObj; else options = getOptions(options, emptyObj); - p = getPathFromURL(p); + p = toPathIfFileUrl(p); if (typeof p !== 'string') { p += ''; } @@ -1667,7 +1667,7 @@ function realpath(p, options, callback) { realpath.native = function(path, options, callback) { callback = makeCallback(callback || options); options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const req = new FSReqCallback(); req.oncomplete = callback; @@ -1710,8 +1710,8 @@ function copyFile(src, dest, flags, callback) { throw new ERR_INVALID_CALLBACK(); } - src = getPathFromURL(src); - dest = getPathFromURL(dest); + src = toPathIfFileUrl(src); + dest = toPathIfFileUrl(dest); validatePath(src, 'src'); validatePath(dest, 'dest'); @@ -1725,8 +1725,8 @@ function copyFile(src, dest, flags, callback) { function copyFileSync(src, dest, flags) { - src = getPathFromURL(src); - dest = getPathFromURL(dest); + src = toPathIfFileUrl(src); + dest = toPathIfFileUrl(dest); validatePath(src, 'src'); validatePath(dest, 'dest'); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index e0ddee4c7f2296..c80bf061de1d3b 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -15,7 +15,7 @@ const { ERR_INVALID_ARG_VALUE, ERR_METHOD_NOT_IMPLEMENTED } = require('internal/errors').codes; -const { getPathFromURL } = require('internal/url'); +const { toPathIfFileUrl } = require('internal/url'); const { isUint8Array } = require('internal/util/types'); const { copyObject, @@ -172,7 +172,7 @@ async function readFileHandle(filehandle, options) { // All of the functions are defined as async in order to ensure that errors // thrown cause promise rejections rather than being thrown synchronously. async function access(path, mode = F_OK) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); mode = mode | 0; @@ -181,8 +181,8 @@ async function access(path, mode = F_OK) { } async function copyFile(src, dest, flags) { - src = getPathFromURL(src); - dest = getPathFromURL(dest); + src = toPathIfFileUrl(src); + dest = toPathIfFileUrl(dest); validatePath(src, 'src'); validatePath(dest, 'dest'); flags = flags | 0; @@ -194,7 +194,7 @@ async function copyFile(src, dest, flags) { // Note that unlike fs.open() which uses numeric file descriptors, // fsPromises.open() uses the fs.FileHandle class. async function open(path, flags, mode) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); mode = validateMode(mode, 'mode', 0o666); return new FileHandle( @@ -257,8 +257,8 @@ async function write(handle, buffer, offset, length, position) { } async function rename(oldPath, newPath) { - oldPath = getPathFromURL(oldPath); - newPath = getPathFromURL(newPath); + oldPath = toPathIfFileUrl(oldPath); + newPath = toPathIfFileUrl(newPath); validatePath(oldPath, 'oldPath'); validatePath(newPath, 'newPath'); return binding.rename(pathModule.toNamespacedPath(oldPath), @@ -278,7 +278,7 @@ async function ftruncate(handle, len = 0) { } async function rmdir(path) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises); } @@ -301,7 +301,7 @@ async function mkdir(path, options) { recursive = false, mode = 0o777 } = options || {}; - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); if (typeof recursive !== 'boolean') @@ -314,7 +314,7 @@ async function mkdir(path, options) { async function readdir(path, options) { options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const result = await binding.readdir(pathModule.toNamespacedPath(path), options.encoding, !!options.withTypes, @@ -326,7 +326,7 @@ async function readdir(path, options) { async function readlink(path, options) { options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path, 'oldPath'); return binding.readlink(pathModule.toNamespacedPath(path), options.encoding, kUsePromises); @@ -334,8 +334,8 @@ async function readlink(path, options) { async function symlink(target, path, type_) { const type = (typeof type_ === 'string' ? type_ : null); - target = getPathFromURL(target); - path = getPathFromURL(path); + target = toPathIfFileUrl(target); + path = toPathIfFileUrl(path); validatePath(target, 'target'); validatePath(path); return binding.symlink(preprocessSymlinkDestination(target, type, path), @@ -351,7 +351,7 @@ async function fstat(handle, options = { bigint: false }) { } async function lstat(path, options = { bigint: false }) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const result = await binding.lstat(pathModule.toNamespacedPath(path), options.bigint, kUsePromises); @@ -359,7 +359,7 @@ async function lstat(path, options = { bigint: false }) { } async function stat(path, options = { bigint: false }) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); const result = await binding.stat(pathModule.toNamespacedPath(path), options.bigint, kUsePromises); @@ -367,8 +367,8 @@ async function stat(path, options = { bigint: false }) { } async function link(existingPath, newPath) { - existingPath = getPathFromURL(existingPath); - newPath = getPathFromURL(newPath); + existingPath = toPathIfFileUrl(existingPath); + newPath = toPathIfFileUrl(newPath); validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); return binding.link(pathModule.toNamespacedPath(existingPath), @@ -377,7 +377,7 @@ async function link(existingPath, newPath) { } async function unlink(path) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises); } @@ -389,7 +389,7 @@ async function fchmod(handle, mode) { } async function chmod(path, mode) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); mode = validateMode(mode, 'mode'); return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); @@ -404,7 +404,7 @@ async function lchmod(path, mode) { } async function lchown(path, uid, gid) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -420,7 +420,7 @@ async function fchown(handle, uid, gid) { } async function chown(path, uid, gid) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -429,7 +429,7 @@ async function chown(path, uid, gid) { } async function utimes(path, atime, mtime) { - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); return binding.utimes(pathModule.toNamespacedPath(path), toUnixTimestamp(atime), @@ -446,7 +446,7 @@ async function futimes(handle, atime, mtime) { async function realpath(path, options) { options = getOptions(options, {}); - path = getPathFromURL(path); + path = toPathIfFileUrl(path); validatePath(path); return binding.realpath(path, options.encoding, kUsePromises); } diff --git a/lib/internal/fs/streams.js b/lib/internal/fs/streams.js index 502b3c143086f7..9a679c21f3ce4e 100644 --- a/lib/internal/fs/streams.js +++ b/lib/internal/fs/streams.js @@ -16,7 +16,7 @@ const { getOptions, } = require('internal/fs/utils'); const { Readable, Writable } = require('stream'); -const { getPathFromURL } = require('internal/url'); +const { toPathIfFileUrl } = require('internal/url'); const util = require('util'); const kMinPoolSpace = 128; @@ -52,7 +52,7 @@ function ReadStream(path, options) { Readable.call(this, options); // path will be ignored when fd is specified, so it can be falsy - this.path = getPathFromURL(path); + this.path = toPathIfFileUrl(path); this.fd = options.fd === undefined ? null : options.fd; this.flags = options.flags === undefined ? 'r' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; @@ -240,7 +240,7 @@ function WriteStream(path, options) { Writable.call(this, options); // path will be ignored when fd is specified, so it can be falsy - this.path = getPathFromURL(path); + this.path = toPathIfFileUrl(path); this.fd = options.fd === undefined ? null : options.fd; this.flags = options.flags === undefined ? 'w' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; diff --git a/lib/internal/fs/watchers.js b/lib/internal/fs/watchers.js index 90ea3971aae6f5..9a2d14937329aa 100644 --- a/lib/internal/fs/watchers.js +++ b/lib/internal/fs/watchers.js @@ -17,7 +17,7 @@ const { } = require('internal/async_hooks'); const { toNamespacedPath } = require('path'); const { validateUint32 } = require('internal/validators'); -const { getPathFromURL } = require('internal/url'); +const { toPathIfFileUrl } = require('internal/url'); const util = require('util'); const assert = require('assert'); @@ -70,7 +70,7 @@ StatWatcher.prototype.start = function(filename, persistent, interval) { // the sake of backwards compatibility this[kOldStatus] = -1; - filename = getPathFromURL(filename); + filename = toPathIfFileUrl(filename); validatePath(filename, 'filename'); validateUint32(interval, 'interval'); const err = this._handle.start(toNamespacedPath(filename), interval); @@ -153,7 +153,7 @@ FSWatcher.prototype.start = function(filename, return; } - filename = getPathFromURL(filename); + filename = toPathIfFileUrl(filename); validatePath(filename, 'filename'); const err = this._handle.start(toNamespacedPath(filename), diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index c218dc2cac2a50..53bdb02d8f8c7e 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -54,7 +54,7 @@ module.exports = Module; let asyncESM; let ModuleJob; let createDynamicModule; -let getURLFromFilePath; +let pathToFileUrl; let decorateErrorStack; function lazyLoadESM() { @@ -63,7 +63,7 @@ function lazyLoadESM() { createDynamicModule = require( 'internal/modules/esm/create_dynamic_module'); decorateErrorStack = require('internal/util').decorateErrorStack; - getURLFromFilePath = require('internal/url').getURLFromFilePath; + pathToFileUrl = require('internal/url').pathToFileUrl; } const { @@ -602,7 +602,7 @@ Module.prototype.load = function(filename) { if (experimentalModules) { if (asyncESM === undefined) lazyLoadESM(); const ESMLoader = asyncESM.ESMLoader; - const url = getURLFromFilePath(filename); + const url = pathToFileUrl(filename); const urlString = `${url}`; const exports = this.exports; if (ESMLoader.moduleMap.has(urlString) !== true) { @@ -731,7 +731,7 @@ Module.runMain = function() { if (experimentalModules) { if (asyncESM === undefined) lazyLoadESM(); asyncESM.loaderPromise.then((loader) => { - return loader.import(getURLFromFilePath(process.argv[1]).pathname); + return loader.import(pathToFileUrl(process.argv[1]).pathname); }) .catch((e) => { decorateErrorStack(e); diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js index b573ce4e8d9e5a..04f125150eeb34 100644 --- a/lib/internal/modules/esm/default_resolve.js +++ b/lib/internal/modules/esm/default_resolve.js @@ -15,7 +15,7 @@ const { } = require('internal/errors').codes; const { resolve: moduleWrapResolve } = internalBinding('module_wrap'); const StringStartsWith = Function.call.bind(String.prototype.startsWith); -const { getURLFromFilePath, getPathFromURL } = require('internal/url'); +const { pathToFileUrl, fileUrlToPath } = require('internal/url'); const realpathCache = new Map(); @@ -62,7 +62,7 @@ function resolve(specifier, parentURL) { let url; try { url = search(specifier, - parentURL || getURLFromFilePath(`${process.cwd()}/`).href); + parentURL || pathToFileUrl(`${process.cwd()}/`).href); } catch (e) { if (typeof e.message === 'string' && StringStartsWith(e.message, 'Cannot find module')) @@ -73,11 +73,11 @@ function resolve(specifier, parentURL) { const isMain = parentURL === undefined; if (isMain ? !preserveSymlinksMain : !preserveSymlinks) { - const real = realpathSync(getPathFromURL(url), { + const real = realpathSync(fileUrlToPath(url), { [internalFS.realpathCacheKey]: realpathCache }); const old = url; - url = getURLFromFilePath(real); + url = pathToFileUrl(real); url.search = old.search; url.hash = old.hash; } diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 618f1adac8deb7..267a5557a52e1a 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -40,7 +40,7 @@ const isWindows = process.platform === 'win32'; const winSepRegEx = /\//g; translators.set('cjs', async (url, isMain) => { debug(`Translating CJSModule ${url}`); - const pathname = internalURLModule.getPathFromURL(new URL(url)); + const pathname = internalURLModule.fileUrlToPath(new URL(url)); const module = CJSModule._cache[ isWindows ? StringReplace(pathname, winSepRegEx, '\\') : pathname]; if (module && module.loaded) { @@ -81,7 +81,7 @@ translators.set('addon', async (url) => { return createDynamicModule(['default'], url, (reflect) => { debug(`Loading NativeModule ${url}`); const module = { exports: {} }; - const pathname = internalURLModule.getPathFromURL(new URL(url)); + const pathname = internalURLModule.fileUrlToPath(new URL(url)); process.dlopen(module, _makeLong(pathname)); reflect.exports.default.set(module.exports); }); @@ -92,7 +92,7 @@ translators.set('json', async (url) => { debug(`Translating JSONModule ${url}`); return createDynamicModule(['default'], url, (reflect) => { debug(`Loading JSONModule ${url}`); - const pathname = internalURLModule.getPathFromURL(new URL(url)); + const pathname = internalURLModule.fileUrlToPath(new URL(url)); const content = readFileSync(pathname, 'utf8'); try { const exports = JsonParse(stripBOM(content)); diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index c622084415f7a9..27d1dffa736128 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -6,7 +6,7 @@ const { setInitializeImportMetaObjectCallback } = internalBinding('module_wrap'); -const { getURLFromFilePath } = require('internal/url'); +const { pathToFileUrl } = require('internal/url'); const Loader = require('internal/modules/esm/loader'); const path = require('path'); const { URL } = require('url'); @@ -17,7 +17,7 @@ const { function normalizeReferrerURL(referrer) { if (typeof referrer === 'string' && path.isAbsolute(referrer)) { - return getURLFromFilePath(referrer).href; + return pathToFileUrl(referrer).href; } return new URL(referrer).href; } @@ -52,7 +52,7 @@ exports.setup = function() { const userLoader = process.binding('config').userLoader; if (userLoader) { const hooks = await ESMLoader.import( - userLoader, getURLFromFilePath(`${process.cwd()}/`).href); + userLoader, pathToFileUrl(`${process.cwd()}/`).href); ESMLoader = new Loader(); ESMLoader.hook(hooks); exports.ESMLoader = ESMLoader; diff --git a/lib/internal/url.js b/lib/internal/url.js index ffd8f10edaeee8..13b1e6db90dec5 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -1392,11 +1392,9 @@ function getPathFromURLPosix(url) { return decodeURIComponent(pathname); } -function getPathFromURL(path) { - if (path == null || !path[searchParams] || - !path[searchParams][searchParams]) { - return path; - } +function fileUrlToPath(path) { + if (typeof path === 'string') + path = new URL(path); if (path.protocol !== 'file:') throw new ERR_INVALID_URL_SCHEME('file'); return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path); @@ -1406,7 +1404,7 @@ function getPathFromURL(path) { // as this is the only character that won't be percent encoded by // default URL percent encoding when pathname is set. const percentRegEx = /%/g; -function getURLFromFilePath(filepath) { +function pathToFileUrl(filepath) { const tmp = new URL('file://'); if (filepath.includes('%')) filepath = filepath.replace(percentRegEx, '%25'); @@ -1414,6 +1412,13 @@ function getURLFromFilePath(filepath) { return tmp; } +function toPathIfFileUrl(fileUrlOrPath) { + if (fileUrlOrPath == null || !fileUrlOrPath[searchParams] || + !fileUrlOrPath[searchParams][searchParams]) + return fileUrlOrPath; + return fileUrlToPath(fileUrlOrPath); +} + function NativeURL(ctx) { this[context] = ctx; } @@ -1441,8 +1446,9 @@ setURLConstructor(constructUrl); module.exports = { toUSVString, - getPathFromURL, - getURLFromFilePath, + fileUrlToPath, + pathToFileUrl, + toPathIfFileUrl, URL, URLSearchParams, domainToASCII, diff --git a/lib/url.js b/lib/url.js index 3a2c3c01ea1f30..9027b8fe8db6af 100644 --- a/lib/url.js +++ b/lib/url.js @@ -42,6 +42,8 @@ const { domainToUnicode, formatSymbol, encodeStr, + pathToFileUrl, + fileUrlToPath } = require('internal/url'); // Original url.parse() API @@ -966,5 +968,9 @@ module.exports = { URL, URLSearchParams, domainToASCII, - domainToUnicode + domainToUnicode, + + // Utilities + pathToFileUrl, + fileUrlToPath }; diff --git a/test/common/inspector-helper.js b/test/common/inspector-helper.js index 84393c4281fa88..dfc358b45dd4b7 100644 --- a/test/common/inspector-helper.js +++ b/test/common/inspector-helper.js @@ -6,7 +6,7 @@ const http = require('http'); const fixtures = require('../common/fixtures'); const { spawn } = require('child_process'); const { parse: parseURL } = require('url'); -const { getURLFromFilePath } = require('internal/url'); +const { pathToFileUrl } = require('internal/url'); const { EventEmitter } = require('events'); const _MAINSCRIPT = fixtures.path('loop.js'); @@ -174,7 +174,7 @@ class InspectorSession { const { scriptId, url } = message.params; this._scriptsIdsByUrl.set(scriptId, url); const fileUrl = url.startsWith('file:') ? - url : getURLFromFilePath(url).toString(); + url : pathToFileUrl(url).toString(); if (fileUrl === this.scriptURL().toString()) { this.mainScriptId = scriptId; } @@ -309,7 +309,7 @@ class InspectorSession { } scriptURL() { - return getURLFromFilePath(this.scriptPath()); + return pathToFileUrl(this.scriptPath()); } } From 0d9c80810f5236eba360c869d9c25bd234514f9f Mon Sep 17 00:00:00 2001 From: guybedford Date: Fri, 24 Aug 2018 19:06:21 +0200 Subject: [PATCH 02/18] Url -> URL, pad out examples --- doc/api/url.md | 51 ++++++---- lib/fs.js | 100 ++++++++++---------- lib/internal/fs/promises.js | 46 ++++----- lib/internal/fs/streams.js | 6 +- lib/internal/fs/watchers.js | 6 +- lib/internal/modules/cjs/loader.js | 8 +- lib/internal/modules/esm/default_resolve.js | 8 +- lib/internal/modules/esm/translators.js | 6 +- lib/internal/process/esm_loader.js | 6 +- lib/internal/url.js | 20 ++-- lib/url.js | 8 +- test/common/inspector-helper.js | 6 +- 12 files changed, 143 insertions(+), 128 deletions(-) diff --git a/doc/api/url.md b/doc/api/url.md index 2865f2fdea8423..643a6cc4d9b7b4 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -925,46 +925,61 @@ console.log(url.format(myURL, { fragment: false, unicode: true, auth: false })); When working with `file:///` URLs in Node.js (eg when working with ES modules which are keyed in the registry by File URL), the utility functions -`urlToFilePath` and `fileUrlToPath` are provided to convert to and from file +`urlToFilePath` and `fileURLToPath` are provided to convert to and from file paths. The edge cases handled by these functions include percent-encoding and decoding as well as cross-platform support. -For example, instead of writing: +For example, the following errors can occur when converting from paths to URLs: ```js -// BAD: -// - fails in Windows -// - doesn't handle loading paths using extended non-latin characters -fs.promises.readFile(fileUrl.pathname); +// throws for missing schema (posix) +// (in Windows the drive letter is detected as the protocol) +new URL(__filename); + +// 'file:///foo' instead of the correct 'file:///foo%231' (posix) +new URL('./foo#1', 'file:///'); + +// 'file:///nas/foo.txt' instead of the correct 'file:///foo.txt' (posix) +new URL('file://' + '//nas/foo.txt'); + +// 'file:///some/path%' instead of the correct 'file:///some/path%25' (posix) +new URL('file:' + '/some/path%.js'); ``` -write: +where using `pathToFileURL` we can get the correct results above. + +When converting from URL to path, the following common errors can occur: ```js -// GOOD: -// - works in Windows -// - handles emoji file paths -const { fileUrlToPath } = require('url'); -fs.promises.readFile(fileUrlToPath(fileUrl)); +// '/foo.txt' instead of '//nas/foo.txt' (Windows) +new URL('file://nas/foo.txt').pathname; + +// '/%E4%BD%A0%E5%A5%BD.txt' instead of '/你好.txt' (posix) +new URL('file:///你好.txt').pathname; + +// '/hello%20world.txt' instead of '/hello world.txt' +new URL('file:///hello world.txt').pathname; ``` -### pathToFileUrl(path) +where using `fileURLToPath` we can get the correct results above. + +### pathToFileURL(path) * `path` {string} The absolute path to convert to a File URL. * Returns: {URL} The file URL object. -This function ensures the correct encodings of URL control characters in file paths -when converting into File URLs. +This function ensures the correct encodings of URL control characters in file +paths when converting into File URLs. -### fileUrlToPath(url) +### fileURLToPath(url) * `url` {URL} | {string} The file URL string or URL object to convert to a path. * Returns: {URL} The fully-resolved platform-specific Node.js file path. -This function ensures the correct decodings of percent-encoded characters as well -as ensuring a cross-platform valid absolute path string. +This function ensures the correct decodings of percent-encoded characters as +well as ensuring a cross-platform valid absolute path string. ## Legacy URL API diff --git a/lib/fs.js b/lib/fs.js index 2c0eebea4bd6ea..9f0d31efa0d46b 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -54,7 +54,7 @@ const { const { FSReqCallback, statValues } = binding; const internalFS = require('internal/fs/utils'); -const { toPathIfFileUrl } = require('internal/url'); +const { toPathIfFileURL } = require('internal/url'); const internalUtil = require('internal/util'); const { copyObject, @@ -177,7 +177,7 @@ function access(path, mode, callback) { mode = F_OK; } - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); mode = mode | 0; @@ -187,7 +187,7 @@ function access(path, mode, callback) { } function accessSync(path, mode) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); if (mode === undefined) @@ -297,7 +297,7 @@ function readFile(path, options, callback) { return; } - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); binding.open(pathModule.toNamespacedPath(path), stringToFlags(options.flag || 'r'), @@ -409,7 +409,7 @@ function closeSync(fd) { } function open(path, flags, mode, callback) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const flagsNumber = stringToFlags(flags); if (arguments.length < 4) { @@ -431,7 +431,7 @@ function open(path, flags, mode, callback) { function openSync(path, flags, mode) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const flagsNumber = stringToFlags(flags); mode = validateMode(mode, 'mode', 0o666); @@ -587,9 +587,9 @@ function writeSync(fd, buffer, offset, length, position) { function rename(oldPath, newPath, callback) { callback = makeCallback(callback); - oldPath = toPathIfFileUrl(oldPath); + oldPath = toPathIfFileURL(oldPath); validatePath(oldPath, 'oldPath'); - newPath = toPathIfFileUrl(newPath); + newPath = toPathIfFileURL(newPath); validatePath(newPath, 'newPath'); const req = new FSReqCallback(); req.oncomplete = callback; @@ -599,9 +599,9 @@ function rename(oldPath, newPath, callback) { } function renameSync(oldPath, newPath) { - oldPath = toPathIfFileUrl(oldPath); + oldPath = toPathIfFileURL(oldPath); validatePath(oldPath, 'oldPath'); - newPath = toPathIfFileUrl(newPath); + newPath = toPathIfFileURL(newPath); validatePath(newPath, 'newPath'); const ctx = { path: oldPath, dest: newPath }; binding.rename(pathModule.toNamespacedPath(oldPath), @@ -680,7 +680,7 @@ function ftruncateSync(fd, len = 0) { function rmdir(path, callback) { callback = makeCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const req = new FSReqCallback(); req.oncomplete = callback; @@ -688,7 +688,7 @@ function rmdir(path, callback) { } function rmdirSync(path) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const ctx = { path }; binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx); @@ -735,7 +735,7 @@ function mkdir(path, options, callback) { mode = 0o777 } = options || {}; callback = makeCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); if (typeof recursive !== 'boolean') @@ -751,7 +751,7 @@ function mkdirSync(path, options) { if (typeof options === 'number' || typeof options === 'string') { options = { mode: options }; } - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); const { recursive = false, mode = 0o777 @@ -771,7 +771,7 @@ function mkdirSync(path, options) { function readdir(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const req = new FSReqCallback(); @@ -792,7 +792,7 @@ function readdir(path, options, callback) { function readdirSync(path, options) { options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const ctx = { path }; const result = binding.readdir(pathModule.toNamespacedPath(path), @@ -819,7 +819,7 @@ function lstat(path, options, callback) { options = {}; } callback = makeStatsCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const req = new FSReqCallback(options.bigint); req.oncomplete = callback; @@ -832,7 +832,7 @@ function stat(path, options, callback) { options = {}; } callback = makeStatsCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const req = new FSReqCallback(options.bigint); req.oncomplete = callback; @@ -848,7 +848,7 @@ function fstatSync(fd, options = {}) { } function lstatSync(path, options = {}) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const ctx = { path }; const stats = binding.lstat(pathModule.toNamespacedPath(path), @@ -858,7 +858,7 @@ function lstatSync(path, options = {}) { } function statSync(path, options = {}) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const ctx = { path }; const stats = binding.stat(pathModule.toNamespacedPath(path), @@ -870,7 +870,7 @@ function statSync(path, options = {}) { function readlink(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path, 'oldPath'); const req = new FSReqCallback(); req.oncomplete = callback; @@ -879,7 +879,7 @@ function readlink(path, options, callback) { function readlinkSync(path, options) { options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path, 'oldPath'); const ctx = { path }; const result = binding.readlink(pathModule.toNamespacedPath(path), @@ -892,8 +892,8 @@ function symlink(target, path, type_, callback_) { const type = (typeof type_ === 'string' ? type_ : null); const callback = makeCallback(arguments[arguments.length - 1]); - target = toPathIfFileUrl(target); - path = toPathIfFileUrl(path); + target = toPathIfFileURL(target); + path = toPathIfFileURL(path); validatePath(target, 'target'); validatePath(path); @@ -907,8 +907,8 @@ function symlink(target, path, type_, callback_) { function symlinkSync(target, path, type) { type = (typeof type === 'string' ? type : null); - target = toPathIfFileUrl(target); - path = toPathIfFileUrl(path); + target = toPathIfFileURL(target); + path = toPathIfFileURL(path); validatePath(target, 'target'); validatePath(path); const flags = stringToSymlinkType(type); @@ -923,8 +923,8 @@ function symlinkSync(target, path, type) { function link(existingPath, newPath, callback) { callback = makeCallback(callback); - existingPath = toPathIfFileUrl(existingPath); - newPath = toPathIfFileUrl(newPath); + existingPath = toPathIfFileURL(existingPath); + newPath = toPathIfFileURL(newPath); validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); @@ -937,8 +937,8 @@ function link(existingPath, newPath, callback) { } function linkSync(existingPath, newPath) { - existingPath = toPathIfFileUrl(existingPath); - newPath = toPathIfFileUrl(newPath); + existingPath = toPathIfFileURL(existingPath); + newPath = toPathIfFileURL(newPath); validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); @@ -952,7 +952,7 @@ function linkSync(existingPath, newPath) { function unlink(path, callback) { callback = makeCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const req = new FSReqCallback(); req.oncomplete = callback; @@ -960,7 +960,7 @@ function unlink(path, callback) { } function unlinkSync(path) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const ctx = { path }; binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx); @@ -1018,7 +1018,7 @@ function lchmodSync(path, mode) { function chmod(path, mode, callback) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); mode = validateMode(mode, 'mode'); callback = makeCallback(callback); @@ -1029,7 +1029,7 @@ function chmod(path, mode, callback) { } function chmodSync(path, mode) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); mode = validateMode(mode, 'mode'); @@ -1040,7 +1040,7 @@ function chmodSync(path, mode) { function lchown(path, uid, gid, callback) { callback = makeCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1050,7 +1050,7 @@ function lchown(path, uid, gid, callback) { } function lchownSync(path, uid, gid) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1081,7 +1081,7 @@ function fchownSync(fd, uid, gid) { function chown(path, uid, gid, callback) { callback = makeCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1092,7 +1092,7 @@ function chown(path, uid, gid, callback) { } function chownSync(path, uid, gid) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1103,7 +1103,7 @@ function chownSync(path, uid, gid) { function utimes(path, atime, mtime, callback) { callback = makeCallback(callback); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const req = new FSReqCallback(); @@ -1115,7 +1115,7 @@ function utimes(path, atime, mtime, callback) { } function utimesSync(path, atime, mtime) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const ctx = { path }; binding.utimes(pathModule.toNamespacedPath(path), @@ -1282,7 +1282,7 @@ function watch(filename, options, listener) { const statWatchers = new Map(); function watchFile(filename, options, listener) { - filename = toPathIfFileUrl(filename); + filename = toPathIfFileURL(filename); validatePath(filename); filename = pathModule.resolve(filename); let stat; @@ -1321,7 +1321,7 @@ function watchFile(filename, options, listener) { } function unwatchFile(filename, listener) { - filename = toPathIfFileUrl(filename); + filename = toPathIfFileURL(filename); validatePath(filename); filename = pathModule.resolve(filename); const stat = statWatchers.get(filename); @@ -1393,7 +1393,7 @@ function realpathSync(p, options) { options = emptyObj; else options = getOptions(options, emptyObj); - p = toPathIfFileUrl(p); + p = toPathIfFileURL(p); if (typeof p !== 'string') { p += ''; } @@ -1525,7 +1525,7 @@ function realpathSync(p, options) { realpathSync.native = function(path, options) { options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const ctx = { path }; const result = binding.realpath(path, options.encoding, undefined, ctx); @@ -1540,7 +1540,7 @@ function realpath(p, options, callback) { options = emptyObj; else options = getOptions(options, emptyObj); - p = toPathIfFileUrl(p); + p = toPathIfFileURL(p); if (typeof p !== 'string') { p += ''; } @@ -1667,7 +1667,7 @@ function realpath(p, options, callback) { realpath.native = function(path, options, callback) { callback = makeCallback(callback || options); options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const req = new FSReqCallback(); req.oncomplete = callback; @@ -1710,8 +1710,8 @@ function copyFile(src, dest, flags, callback) { throw new ERR_INVALID_CALLBACK(); } - src = toPathIfFileUrl(src); - dest = toPathIfFileUrl(dest); + src = toPathIfFileURL(src); + dest = toPathIfFileURL(dest); validatePath(src, 'src'); validatePath(dest, 'dest'); @@ -1725,8 +1725,8 @@ function copyFile(src, dest, flags, callback) { function copyFileSync(src, dest, flags) { - src = toPathIfFileUrl(src); - dest = toPathIfFileUrl(dest); + src = toPathIfFileURL(src); + dest = toPathIfFileURL(dest); validatePath(src, 'src'); validatePath(dest, 'dest'); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index c80bf061de1d3b..0aa26ba2f97daa 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -15,7 +15,7 @@ const { ERR_INVALID_ARG_VALUE, ERR_METHOD_NOT_IMPLEMENTED } = require('internal/errors').codes; -const { toPathIfFileUrl } = require('internal/url'); +const { toPathIfFileURL } = require('internal/url'); const { isUint8Array } = require('internal/util/types'); const { copyObject, @@ -172,7 +172,7 @@ async function readFileHandle(filehandle, options) { // All of the functions are defined as async in order to ensure that errors // thrown cause promise rejections rather than being thrown synchronously. async function access(path, mode = F_OK) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); mode = mode | 0; @@ -181,8 +181,8 @@ async function access(path, mode = F_OK) { } async function copyFile(src, dest, flags) { - src = toPathIfFileUrl(src); - dest = toPathIfFileUrl(dest); + src = toPathIfFileURL(src); + dest = toPathIfFileURL(dest); validatePath(src, 'src'); validatePath(dest, 'dest'); flags = flags | 0; @@ -194,7 +194,7 @@ async function copyFile(src, dest, flags) { // Note that unlike fs.open() which uses numeric file descriptors, // fsPromises.open() uses the fs.FileHandle class. async function open(path, flags, mode) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); mode = validateMode(mode, 'mode', 0o666); return new FileHandle( @@ -257,8 +257,8 @@ async function write(handle, buffer, offset, length, position) { } async function rename(oldPath, newPath) { - oldPath = toPathIfFileUrl(oldPath); - newPath = toPathIfFileUrl(newPath); + oldPath = toPathIfFileURL(oldPath); + newPath = toPathIfFileURL(newPath); validatePath(oldPath, 'oldPath'); validatePath(newPath, 'newPath'); return binding.rename(pathModule.toNamespacedPath(oldPath), @@ -278,7 +278,7 @@ async function ftruncate(handle, len = 0) { } async function rmdir(path) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises); } @@ -301,7 +301,7 @@ async function mkdir(path, options) { recursive = false, mode = 0o777 } = options || {}; - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); if (typeof recursive !== 'boolean') @@ -314,7 +314,7 @@ async function mkdir(path, options) { async function readdir(path, options) { options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const result = await binding.readdir(pathModule.toNamespacedPath(path), options.encoding, !!options.withTypes, @@ -326,7 +326,7 @@ async function readdir(path, options) { async function readlink(path, options) { options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path, 'oldPath'); return binding.readlink(pathModule.toNamespacedPath(path), options.encoding, kUsePromises); @@ -334,8 +334,8 @@ async function readlink(path, options) { async function symlink(target, path, type_) { const type = (typeof type_ === 'string' ? type_ : null); - target = toPathIfFileUrl(target); - path = toPathIfFileUrl(path); + target = toPathIfFileURL(target); + path = toPathIfFileURL(path); validatePath(target, 'target'); validatePath(path); return binding.symlink(preprocessSymlinkDestination(target, type, path), @@ -351,7 +351,7 @@ async function fstat(handle, options = { bigint: false }) { } async function lstat(path, options = { bigint: false }) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const result = await binding.lstat(pathModule.toNamespacedPath(path), options.bigint, kUsePromises); @@ -359,7 +359,7 @@ async function lstat(path, options = { bigint: false }) { } async function stat(path, options = { bigint: false }) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); const result = await binding.stat(pathModule.toNamespacedPath(path), options.bigint, kUsePromises); @@ -367,8 +367,8 @@ async function stat(path, options = { bigint: false }) { } async function link(existingPath, newPath) { - existingPath = toPathIfFileUrl(existingPath); - newPath = toPathIfFileUrl(newPath); + existingPath = toPathIfFileURL(existingPath); + newPath = toPathIfFileURL(newPath); validatePath(existingPath, 'existingPath'); validatePath(newPath, 'newPath'); return binding.link(pathModule.toNamespacedPath(existingPath), @@ -377,7 +377,7 @@ async function link(existingPath, newPath) { } async function unlink(path) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises); } @@ -389,7 +389,7 @@ async function fchmod(handle, mode) { } async function chmod(path, mode) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); mode = validateMode(mode, 'mode'); return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); @@ -404,7 +404,7 @@ async function lchmod(path, mode) { } async function lchown(path, uid, gid) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -420,7 +420,7 @@ async function fchown(handle, uid, gid) { } async function chown(path, uid, gid) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -429,7 +429,7 @@ async function chown(path, uid, gid) { } async function utimes(path, atime, mtime) { - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); return binding.utimes(pathModule.toNamespacedPath(path), toUnixTimestamp(atime), @@ -446,7 +446,7 @@ async function futimes(handle, atime, mtime) { async function realpath(path, options) { options = getOptions(options, {}); - path = toPathIfFileUrl(path); + path = toPathIfFileURL(path); validatePath(path); return binding.realpath(path, options.encoding, kUsePromises); } diff --git a/lib/internal/fs/streams.js b/lib/internal/fs/streams.js index 9a679c21f3ce4e..a2ae1c9787d288 100644 --- a/lib/internal/fs/streams.js +++ b/lib/internal/fs/streams.js @@ -16,7 +16,7 @@ const { getOptions, } = require('internal/fs/utils'); const { Readable, Writable } = require('stream'); -const { toPathIfFileUrl } = require('internal/url'); +const { toPathIfFileURL } = require('internal/url'); const util = require('util'); const kMinPoolSpace = 128; @@ -52,7 +52,7 @@ function ReadStream(path, options) { Readable.call(this, options); // path will be ignored when fd is specified, so it can be falsy - this.path = toPathIfFileUrl(path); + this.path = toPathIfFileURL(path); this.fd = options.fd === undefined ? null : options.fd; this.flags = options.flags === undefined ? 'r' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; @@ -240,7 +240,7 @@ function WriteStream(path, options) { Writable.call(this, options); // path will be ignored when fd is specified, so it can be falsy - this.path = toPathIfFileUrl(path); + this.path = toPathIfFileURL(path); this.fd = options.fd === undefined ? null : options.fd; this.flags = options.flags === undefined ? 'w' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; diff --git a/lib/internal/fs/watchers.js b/lib/internal/fs/watchers.js index 9a2d14937329aa..5c6b2d2fab085e 100644 --- a/lib/internal/fs/watchers.js +++ b/lib/internal/fs/watchers.js @@ -17,7 +17,7 @@ const { } = require('internal/async_hooks'); const { toNamespacedPath } = require('path'); const { validateUint32 } = require('internal/validators'); -const { toPathIfFileUrl } = require('internal/url'); +const { toPathIfFileURL } = require('internal/url'); const util = require('util'); const assert = require('assert'); @@ -70,7 +70,7 @@ StatWatcher.prototype.start = function(filename, persistent, interval) { // the sake of backwards compatibility this[kOldStatus] = -1; - filename = toPathIfFileUrl(filename); + filename = toPathIfFileURL(filename); validatePath(filename, 'filename'); validateUint32(interval, 'interval'); const err = this._handle.start(toNamespacedPath(filename), interval); @@ -153,7 +153,7 @@ FSWatcher.prototype.start = function(filename, return; } - filename = toPathIfFileUrl(filename); + filename = toPathIfFileURL(filename); validatePath(filename, 'filename'); const err = this._handle.start(toNamespacedPath(filename), diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 53bdb02d8f8c7e..1e93791237c92f 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -54,7 +54,7 @@ module.exports = Module; let asyncESM; let ModuleJob; let createDynamicModule; -let pathToFileUrl; +let pathToFileURL; let decorateErrorStack; function lazyLoadESM() { @@ -63,7 +63,7 @@ function lazyLoadESM() { createDynamicModule = require( 'internal/modules/esm/create_dynamic_module'); decorateErrorStack = require('internal/util').decorateErrorStack; - pathToFileUrl = require('internal/url').pathToFileUrl; + pathToFileURL = require('internal/url').pathToFileURL; } const { @@ -602,7 +602,7 @@ Module.prototype.load = function(filename) { if (experimentalModules) { if (asyncESM === undefined) lazyLoadESM(); const ESMLoader = asyncESM.ESMLoader; - const url = pathToFileUrl(filename); + const url = pathToFileURL(filename); const urlString = `${url}`; const exports = this.exports; if (ESMLoader.moduleMap.has(urlString) !== true) { @@ -731,7 +731,7 @@ Module.runMain = function() { if (experimentalModules) { if (asyncESM === undefined) lazyLoadESM(); asyncESM.loaderPromise.then((loader) => { - return loader.import(pathToFileUrl(process.argv[1]).pathname); + return loader.import(pathToFileURL(process.argv[1]).pathname); }) .catch((e) => { decorateErrorStack(e); diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js index 04f125150eeb34..875c560cb15079 100644 --- a/lib/internal/modules/esm/default_resolve.js +++ b/lib/internal/modules/esm/default_resolve.js @@ -15,7 +15,7 @@ const { } = require('internal/errors').codes; const { resolve: moduleWrapResolve } = internalBinding('module_wrap'); const StringStartsWith = Function.call.bind(String.prototype.startsWith); -const { pathToFileUrl, fileUrlToPath } = require('internal/url'); +const { pathToFileURL, fileURLToPath } = require('internal/url'); const realpathCache = new Map(); @@ -62,7 +62,7 @@ function resolve(specifier, parentURL) { let url; try { url = search(specifier, - parentURL || pathToFileUrl(`${process.cwd()}/`).href); + parentURL || pathToFileURL(`${process.cwd()}/`).href); } catch (e) { if (typeof e.message === 'string' && StringStartsWith(e.message, 'Cannot find module')) @@ -73,11 +73,11 @@ function resolve(specifier, parentURL) { const isMain = parentURL === undefined; if (isMain ? !preserveSymlinksMain : !preserveSymlinks) { - const real = realpathSync(fileUrlToPath(url), { + const real = realpathSync(fileURLToPath(url), { [internalFS.realpathCacheKey]: realpathCache }); const old = url; - url = pathToFileUrl(real); + url = pathToFileURL(real); url.search = old.search; url.hash = old.hash; } diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 267a5557a52e1a..aaf35ed461edb9 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -40,7 +40,7 @@ const isWindows = process.platform === 'win32'; const winSepRegEx = /\//g; translators.set('cjs', async (url, isMain) => { debug(`Translating CJSModule ${url}`); - const pathname = internalURLModule.fileUrlToPath(new URL(url)); + const pathname = internalURLModule.fileURLToPath(new URL(url)); const module = CJSModule._cache[ isWindows ? StringReplace(pathname, winSepRegEx, '\\') : pathname]; if (module && module.loaded) { @@ -81,7 +81,7 @@ translators.set('addon', async (url) => { return createDynamicModule(['default'], url, (reflect) => { debug(`Loading NativeModule ${url}`); const module = { exports: {} }; - const pathname = internalURLModule.fileUrlToPath(new URL(url)); + const pathname = internalURLModule.fileURLToPath(new URL(url)); process.dlopen(module, _makeLong(pathname)); reflect.exports.default.set(module.exports); }); @@ -92,7 +92,7 @@ translators.set('json', async (url) => { debug(`Translating JSONModule ${url}`); return createDynamicModule(['default'], url, (reflect) => { debug(`Loading JSONModule ${url}`); - const pathname = internalURLModule.fileUrlToPath(new URL(url)); + const pathname = internalURLModule.fileURLToPath(new URL(url)); const content = readFileSync(pathname, 'utf8'); try { const exports = JsonParse(stripBOM(content)); diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index 27d1dffa736128..6f3ac729f8143a 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -6,7 +6,7 @@ const { setInitializeImportMetaObjectCallback } = internalBinding('module_wrap'); -const { pathToFileUrl } = require('internal/url'); +const { pathToFileURL } = require('internal/url'); const Loader = require('internal/modules/esm/loader'); const path = require('path'); const { URL } = require('url'); @@ -17,7 +17,7 @@ const { function normalizeReferrerURL(referrer) { if (typeof referrer === 'string' && path.isAbsolute(referrer)) { - return pathToFileUrl(referrer).href; + return pathToFileURL(referrer).href; } return new URL(referrer).href; } @@ -52,7 +52,7 @@ exports.setup = function() { const userLoader = process.binding('config').userLoader; if (userLoader) { const hooks = await ESMLoader.import( - userLoader, pathToFileUrl(`${process.cwd()}/`).href); + userLoader, pathToFileURL(`${process.cwd()}/`).href); ESMLoader = new Loader(); ESMLoader.hook(hooks); exports.ESMLoader = ESMLoader; diff --git a/lib/internal/url.js b/lib/internal/url.js index 13b1e6db90dec5..82db7110462bad 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -1392,7 +1392,7 @@ function getPathFromURLPosix(url) { return decodeURIComponent(pathname); } -function fileUrlToPath(path) { +function fileURLToPath(path) { if (typeof path === 'string') path = new URL(path); if (path.protocol !== 'file:') @@ -1404,7 +1404,7 @@ function fileUrlToPath(path) { // as this is the only character that won't be percent encoded by // default URL percent encoding when pathname is set. const percentRegEx = /%/g; -function pathToFileUrl(filepath) { +function pathToFileURL(filepath) { const tmp = new URL('file://'); if (filepath.includes('%')) filepath = filepath.replace(percentRegEx, '%25'); @@ -1412,11 +1412,11 @@ function pathToFileUrl(filepath) { return tmp; } -function toPathIfFileUrl(fileUrlOrPath) { - if (fileUrlOrPath == null || !fileUrlOrPath[searchParams] || - !fileUrlOrPath[searchParams][searchParams]) - return fileUrlOrPath; - return fileUrlToPath(fileUrlOrPath); +function toPathIfFileURL(fileURLOrPath) { + if (fileURLOrPath == null || !fileURLOrPath[searchParams] || + !fileURLOrPath[searchParams][searchParams]) + return fileURLOrPath; + return fileURLToPath(fileURLOrPath); } function NativeURL(ctx) { @@ -1446,9 +1446,9 @@ setURLConstructor(constructUrl); module.exports = { toUSVString, - fileUrlToPath, - pathToFileUrl, - toPathIfFileUrl, + fileURLToPath, + pathToFileURL, + toPathIfFileURL, URL, URLSearchParams, domainToASCII, diff --git a/lib/url.js b/lib/url.js index 9027b8fe8db6af..ba2033b4e533aa 100644 --- a/lib/url.js +++ b/lib/url.js @@ -42,8 +42,8 @@ const { domainToUnicode, formatSymbol, encodeStr, - pathToFileUrl, - fileUrlToPath + pathToFileURL, + fileURLToPath } = require('internal/url'); // Original url.parse() API @@ -971,6 +971,6 @@ module.exports = { domainToUnicode, // Utilities - pathToFileUrl, - fileUrlToPath + pathToFileURL, + fileURLToPath }; diff --git a/test/common/inspector-helper.js b/test/common/inspector-helper.js index dfc358b45dd4b7..ae8fd9732d34f5 100644 --- a/test/common/inspector-helper.js +++ b/test/common/inspector-helper.js @@ -6,7 +6,7 @@ const http = require('http'); const fixtures = require('../common/fixtures'); const { spawn } = require('child_process'); const { parse: parseURL } = require('url'); -const { pathToFileUrl } = require('internal/url'); +const { pathToFileURL } = require('internal/url'); const { EventEmitter } = require('events'); const _MAINSCRIPT = fixtures.path('loop.js'); @@ -174,7 +174,7 @@ class InspectorSession { const { scriptId, url } = message.params; this._scriptsIdsByUrl.set(scriptId, url); const fileUrl = url.startsWith('file:') ? - url : pathToFileUrl(url).toString(); + url : pathToFileURL(url).toString(); if (fileUrl === this.scriptURL().toString()) { this.mainScriptId = scriptId; } @@ -309,7 +309,7 @@ class InspectorSession { } scriptURL() { - return pathToFileUrl(this.scriptPath()); + return pathToFileURL(this.scriptPath()); } } From 0b784b4b0104a8287c4b0d18fa12681f0634fe96 Mon Sep 17 00:00:00 2001 From: guybedford Date: Fri, 24 Aug 2018 19:09:17 +0200 Subject: [PATCH 03/18] update windows examples --- doc/api/url.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/url.md b/doc/api/url.md index 643a6cc4d9b7b4..d18e3fa3648a4e 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -953,7 +953,10 @@ where using `pathToFileURL` we can get the correct results above. When converting from URL to path, the following common errors can occur: ```js -// '/foo.txt' instead of '//nas/foo.txt' (Windows) +// '/C:/path/' instead of 'C:\path\' +new URL('file:///C:/path/').pathname; + +// '/foo.txt' instead of '\\nas\foo.txt' (Windows) new URL('file://nas/foo.txt').pathname; // '/%E4%BD%A0%E5%A5%BD.txt' instead of '/你好.txt' (posix) From 6479897c4ef1adcb20c5186cdd2b7507d4bdef13 Mon Sep 17 00:00:00 2001 From: guybedford Date: Fri, 24 Aug 2018 19:10:39 +0200 Subject: [PATCH 04/18] correction --- doc/api/url.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/url.md b/doc/api/url.md index d18e3fa3648a4e..40cbb0d48a1d4e 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -925,7 +925,7 @@ console.log(url.format(myURL, { fragment: false, unicode: true, auth: false })); When working with `file:///` URLs in Node.js (eg when working with ES modules which are keyed in the registry by File URL), the utility functions -`urlToFilePath` and `fileURLToPath` are provided to convert to and from file +`pathToFileURL` and `fileURLToPath` are provided to convert to and from file paths. The edge cases handled by these functions include percent-encoding and decoding @@ -942,10 +942,10 @@ new URL(__filename); new URL('./foo#1', 'file:///'); // 'file:///nas/foo.txt' instead of the correct 'file:///foo.txt' (posix) -new URL('file://' + '//nas/foo.txt'); +new URL(`file://${'//nas/foo.txt'}`); // 'file:///some/path%' instead of the correct 'file:///some/path%25' (posix) -new URL('file:' + '/some/path%.js'); +new URL(`sfile:${'/some/path%.js'}`); ``` where using `pathToFileURL` we can get the correct results above. From 09bc85ad6013ff794027a3787fe475d44e276b78 Mon Sep 17 00:00:00 2001 From: guybedford Date: Fri, 24 Aug 2018 19:12:29 +0200 Subject: [PATCH 05/18] registry -> module map --- doc/api/url.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/url.md b/doc/api/url.md index 40cbb0d48a1d4e..478ee0ceb796c9 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -924,7 +924,7 @@ console.log(url.format(myURL, { fragment: false, unicode: true, auth: false })); ## File URL Utility Functions When working with `file:///` URLs in Node.js (eg when working with ES modules -which are keyed in the registry by File URL), the utility functions +which are keyed in the module map by File URL), the utility functions `pathToFileURL` and `fileURLToPath` are provided to convert to and from file paths. From c073384ec058247a4ff03cf3c4e039fcd5a861cb Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Fri, 24 Aug 2018 19:26:34 +0200 Subject: [PATCH 06/18] document utils in same section --- doc/api/url.md | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/doc/api/url.md b/doc/api/url.md index 478ee0ceb796c9..b4b448a59270b6 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -921,15 +921,13 @@ console.log(url.format(myURL, { fragment: false, unicode: true, auth: false })); // Prints 'https://你好你好/?abc' ``` -## File URL Utility Functions +### url.pathToFileURL(path) -When working with `file:///` URLs in Node.js (eg when working with ES modules -which are keyed in the module map by File URL), the utility functions -`pathToFileURL` and `fileURLToPath` are provided to convert to and from file -paths. +* `path` {string} The absolute path to convert to a File URL. +* Returns: {URL} The file URL object. -The edge cases handled by these functions include percent-encoding and decoding -as well as cross-platform support. +This function ensures the correct encodings of URL control characters in file +paths when converting into File URLs. For example, the following errors can occur when converting from paths to URLs: @@ -950,10 +948,18 @@ new URL(`sfile:${'/some/path%.js'}`); where using `pathToFileURL` we can get the correct results above. +### url.fileURLToPath(url) + +* `url` {URL} | {string} The file URL string or URL object to convert to a path. +* Returns: {URL} The fully-resolved platform-specific Node.js file path. + +This function ensures the correct decodings of percent-encoded characters as +well as ensuring a cross-platform valid absolute path string. + When converting from URL to path, the following common errors can occur: ```js -// '/C:/path/' instead of 'C:\path\' +// '/C:/path/' instead of 'C:\path\' (Windows) new URL('file:///C:/path/').pathname; // '/foo.txt' instead of '\\nas\foo.txt' (Windows) @@ -962,28 +968,12 @@ new URL('file://nas/foo.txt').pathname; // '/%E4%BD%A0%E5%A5%BD.txt' instead of '/你好.txt' (posix) new URL('file:///你好.txt').pathname; -// '/hello%20world.txt' instead of '/hello world.txt' +// '/hello%20world.txt' instead of '/hello world.txt' (posix) new URL('file:///hello world.txt').pathname; ``` where using `fileURLToPath` we can get the correct results above. -### pathToFileURL(path) - -* `path` {string} The absolute path to convert to a File URL. -* Returns: {URL} The file URL object. - -This function ensures the correct encodings of URL control characters in file -paths when converting into File URLs. - -### fileURLToPath(url) - -* `url` {URL} | {string} The file URL string or URL object to convert to a path. -* Returns: {URL} The fully-resolved platform-specific Node.js file path. - -This function ensures the correct decodings of percent-encoded characters as -well as ensuring a cross-platform valid absolute path string. - ## Legacy URL API ### Legacy `urlObject` From d36d1f9f99d7cece4f14c6f3ad4beb7bf5e4e6db Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 25 Aug 2018 11:15:38 +0200 Subject: [PATCH 07/18] readme corrections --- doc/api/url.md | 64 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/api/url.md b/doc/api/url.md index b4b448a59270b6..c565ed37d7d1c4 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -880,6 +880,32 @@ console.log(url.domainToUnicode('xn--iñvalid.com')); // Prints an empty string ``` +### url.fileURLToPath(url) + +* `url` {URL | string} The file URL string or URL object to convert to a path. +* Returns: {URL} The fully-resolved platform-specific Node.js file path. + +This function ensures the correct decodings of percent-encoded characters as +well as ensuring a cross-platform valid absolute path string. + +When converting from URL to path, the following common errors can occur: + +```js +// '/C:/path/' instead of 'C:\path\' (Windows) +new URL('file:///C:/path/').pathname; + +// '/foo.txt' instead of '\\nas\foo.txt' (Windows) +new URL('file://nas/foo.txt').pathname; + +// '/%E4%BD%A0%E5%A5%BD.txt' instead of '/你好.txt' (POSIX) +new URL('file:///你好.txt').pathname; + +// '/hello%20world.txt' instead of '/hello world.txt' (POSIX) +new URL('file:///hello world.txt').pathname; +``` + +where using `url.fileURLToPath` we can get the correct results above. + ### url.format(URL[, options])