diff options
Diffstat (limited to 'toolkit/components/osfile/modules/ospath_win.jsm')
-rw-r--r-- | toolkit/components/osfile/modules/ospath_win.jsm | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/toolkit/components/osfile/modules/ospath_win.jsm b/toolkit/components/osfile/modules/ospath_win.jsm new file mode 100644 index 000000000..31a87b115 --- /dev/null +++ b/toolkit/components/osfile/modules/ospath_win.jsm @@ -0,0 +1,373 @@ +/** + * Handling native paths. + * + * This module contains a number of functions destined to simplify + * working with native paths through a cross-platform API. Functions + * of this module will only work with the following assumptions: + * + * - paths are valid; + * - paths are defined with one of the grammars that this module can + * parse (see later); + * - all path concatenations go through function |join|. + * + * Limitations of this implementation. + * + * Windows supports 6 distinct grammars for paths. For the moment, this + * implementation supports the following subset: + * + * - drivename:backslash-separated components + * - backslash-separated components + * - \\drivename\ followed by backslash-separated components + * + * Additionally, |normalize| can convert a path containing slash- + * separated components to a path containing backslash-separated + * components. + */ + +"use strict"; + +// Boilerplate used to be able to import this module both from the main +// thread and from worker threads. +if (typeof Components != "undefined") { + Components.utils.importGlobalProperties(["URL"]); + // Global definition of |exports|, to keep everybody happy. + // In non-main thread, |exports| is provided by the module + // loader. + this.exports = {}; +} else if (typeof module == "undefined" || typeof exports == "undefined") { + throw new Error("Please load this module using require()"); +} + +var EXPORTED_SYMBOLS = [ + "basename", + "dirname", + "join", + "normalize", + "split", + "winGetDrive", + "winIsAbsolute", + "toFileURI", + "fromFileURI", +]; + +/** + * Return the final part of the path. + * The final part of the path is everything after the last "\\". + */ +var basename = function(path) { + if (path.startsWith("\\\\")) { + // UNC-style path + let index = path.lastIndexOf("\\"); + if (index != 1) { + return path.slice(index + 1); + } + return ""; // Degenerate case + } + return path.slice(Math.max(path.lastIndexOf("\\"), + path.lastIndexOf(":")) + 1); +}; +exports.basename = basename; + +/** + * Return the directory part of the path. + * + * If the path contains no directory, return the drive letter, + * or "." if the path contains no drive letter or if option + * |winNoDrive| is set. + * + * Otherwise, return everything before the last backslash, + * including the drive/server name. + * + * + * @param {string} path The path. + * @param {*=} options Platform-specific options controlling the behavior + * of this function. This implementation supports the following options: + * - |winNoDrive| If |true|, also remove the letter from the path name. + */ +var dirname = function(path, options) { + let noDrive = (options && options.winNoDrive); + + // Find the last occurrence of "\\" + let index = path.lastIndexOf("\\"); + if (index == -1) { + // If there is no directory component... + if (!noDrive) { + // Return the drive path if possible, falling back to "." + return this.winGetDrive(path) || "."; + } else { + // Or just "." + return "."; + } + } + + if (index == 1 && path.charAt(0) == "\\") { + // The path is reduced to a UNC drive + if (noDrive) { + return "."; + } else { + return path; + } + } + + // Ignore any occurrence of "\\: immediately before that one + while (index >= 0 && path[index] == "\\") { + --index; + } + + // Compute what is left, removing the drive name if necessary + let start; + if (noDrive) { + start = (this.winGetDrive(path) || "").length; + } else { + start = 0; + } + return path.slice(start, index + 1); +}; +exports.dirname = dirname; + +/** + * Join path components. + * This is the recommended manner of getting the path of a file/subdirectory + * in a directory. + * + * Example: Obtaining $TMP/foo/bar in an OS-independent manner + * var tmpDir = OS.Constants.Path.tmpDir; + * var path = OS.Path.join(tmpDir, "foo", "bar"); + * + * Under Windows, this will return "$TMP\foo\bar". + * + * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the + * same as `OS.Path.join("foo", "bar")`. + */ +var join = function(...path) { + let paths = []; + let root; + let absolute = false; + for (let subpath of path) { + if (subpath == null) { + throw new TypeError("invalid path component"); + } + if (subpath == "") { + continue; + } + let drive = this.winGetDrive(subpath); + if (drive) { + root = drive; + let component = trimBackslashes(subpath.slice(drive.length)); + if (component) { + paths = [component]; + } else { + paths = []; + } + absolute = true; + } else if (this.winIsAbsolute(subpath)) { + paths = [trimBackslashes(subpath)]; + absolute = true; + } else { + paths.push(trimBackslashes(subpath)); + } + } + let result = ""; + if (root) { + result += root; + } + if (absolute) { + result += "\\"; + } + result += paths.join("\\"); + return result; +}; +exports.join = join; + +/** + * Return the drive name of a path, or |null| if the path does + * not contain a drive name. + * + * Drive name appear either as "DriveName:..." (the return drive + * name includes the ":") or "\\\\DriveName..." (the returned drive name + * includes "\\\\"). + */ +var winGetDrive = function(path) { + if (path == null) { + throw new TypeError("path is invalid"); + } + + if (path.startsWith("\\\\")) { + // UNC path + if (path.length == 2) { + return null; + } + let index = path.indexOf("\\", 2); + if (index == -1) { + return path; + } + return path.slice(0, index); + } + // Non-UNC path + let index = path.indexOf(":"); + if (index <= 0) return null; + return path.slice(0, index + 1); +}; +exports.winGetDrive = winGetDrive; + +/** + * Return |true| if the path is absolute, |false| otherwise. + * + * We consider that a path is absolute if it starts with "\\" + * or "driveletter:\\". + */ +var winIsAbsolute = function(path) { + let index = path.indexOf(":"); + return path.length > index + 1 && path[index + 1] == "\\"; +}; +exports.winIsAbsolute = winIsAbsolute; + +/** + * Normalize a path by removing any unneeded ".", "..", "\\". + * Also convert any "/" to a "\\". + */ +var normalize = function(path) { + let stack = []; + + if (!path.startsWith("\\\\")) { + // Normalize "/" to "\\" + path = path.replace(/\//g, "\\"); + } + + // Remove the drive (we will put it back at the end) + let root = this.winGetDrive(path); + if (root) { + path = path.slice(root.length); + } + + // Remember whether we need to restore a leading "\\" or drive name. + let absolute = this.winIsAbsolute(path); + + // And now, fill |stack| from the components, + // popping whenever there is a ".." + path.split("\\").forEach(function loop(v) { + switch (v) { + case "": case ".": // Ignore + break; + case "..": + if (stack.length == 0) { + if (absolute) { + throw new Error("Path is ill-formed: attempting to go past root"); + } else { + stack.push(".."); + } + } else { + if (stack[stack.length - 1] == "..") { + stack.push(".."); + } else { + stack.pop(); + } + } + break; + default: + stack.push(v); + } + }); + + // Put everything back together + let result = stack.join("\\"); + if (absolute || root) { + result = "\\" + result; + } + if (root) { + result = root + result; + } + return result; +}; +exports.normalize = normalize; + +/** + * Return the components of a path. + * You should generally apply this function to a normalized path. + * + * @return {{ + * {bool} absolute |true| if the path is absolute, |false| otherwise + * {array} components the string components of the path + * {string?} winDrive the drive or server for this path + * }} + * + * Other implementations may add additional OS-specific informations. + */ +var split = function(path) { + return { + absolute: this.winIsAbsolute(path), + winDrive: this.winGetDrive(path), + components: path.split("\\") + }; +}; +exports.split = split; + +/** + * Return the file:// URI file path of the given local file path. + */ +// The case of %3b is designed to match Services.io, but fundamentally doesn't matter. +var toFileURIExtraEncodings = {';': '%3b', '?': '%3F', '#': '%23'}; +var toFileURI = function toFileURI(path) { + // URI-escape forward slashes and convert backward slashes to forward + path = this.normalize(path).replace(/[\\\/]/g, m => (m=='\\')? '/' : '%2F'); + // Per https://url.spec.whatwg.org we should not encode [] in the path + let dontNeedEscaping = {'%5B': '[', '%5D': ']'}; + let uri = encodeURI(path).replace(/%(5B|5D)/gi, + match => dontNeedEscaping[match]); + + // add a prefix, and encodeURI doesn't escape a few characters that we do + // want to escape, so fix that up + let prefix = "file:///"; + uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]); + + // turn e.g., file:///C: into file:///C:/ + if (uri.charAt(uri.length - 1) === ':') { + uri += "/" + } + + return uri; +}; +exports.toFileURI = toFileURI; + +/** + * Returns the local file path from a given file URI. + */ +var fromFileURI = function fromFileURI(uri) { + let url = new URL(uri); + if (url.protocol != 'file:') { + throw new Error("fromFileURI expects a file URI"); + } + + // strip leading slash, since Windows paths don't start with one + uri = url.pathname.substr(1); + + let path = decodeURI(uri); + // decode a few characters where URL's parsing is overzealous + path = path.replace(/%(3b|3f|23)/gi, + match => decodeURIComponent(match)); + path = this.normalize(path); + + // this.normalize() does not remove the trailing slash if the path + // component is a drive letter. eg. 'C:\'' will not get normalized. + if (path.endsWith(":\\")) { + path = path.substr(0, path.length - 1); + } + return this.normalize(path); +}; +exports.fromFileURI = fromFileURI; + +/** +* Utility function: Remove any leading/trailing backslashes +* from a string. +*/ +var trimBackslashes = function trimBackslashes(string) { + return string.replace(/^\\+|\\+$/g,''); +}; + +//////////// Boilerplate +if (typeof Components != "undefined") { + this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; + for (let symbol of EXPORTED_SYMBOLS) { + this[symbol] = exports[symbol]; + } +} |