1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/osfile/modules/ospath_win.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,364 @@ 1.4 +/** 1.5 + * Handling native paths. 1.6 + * 1.7 + * This module contains a number of functions destined to simplify 1.8 + * working with native paths through a cross-platform API. Functions 1.9 + * of this module will only work with the following assumptions: 1.10 + * 1.11 + * - paths are valid; 1.12 + * - paths are defined with one of the grammars that this module can 1.13 + * parse (see later); 1.14 + * - all path concatenations go through function |join|. 1.15 + * 1.16 + * Limitations of this implementation. 1.17 + * 1.18 + * Windows supports 6 distinct grammars for paths. For the moment, this 1.19 + * implementation supports the following subset: 1.20 + * 1.21 + * - drivename:backslash-separated components 1.22 + * - backslash-separated components 1.23 + * - \\drivename\ followed by backslash-separated components 1.24 + * 1.25 + * Additionally, |normalize| can convert a path containing slash- 1.26 + * separated components to a path containing backslash-separated 1.27 + * components. 1.28 + */ 1.29 + 1.30 +"use strict"; 1.31 + 1.32 +// Boilerplate used to be able to import this module both from the main 1.33 +// thread and from worker threads. 1.34 +if (typeof Components != "undefined") { 1.35 + Components.utils.importGlobalProperties(["URL"]); 1.36 + // Global definition of |exports|, to keep everybody happy. 1.37 + // In non-main thread, |exports| is provided by the module 1.38 + // loader. 1.39 + this.exports = {}; 1.40 +} else if (typeof "module" == "undefined" || typeof "exports" == "undefined") { 1.41 + throw new Error("Please load this module using require()"); 1.42 +} 1.43 + 1.44 +let EXPORTED_SYMBOLS = [ 1.45 + "basename", 1.46 + "dirname", 1.47 + "join", 1.48 + "normalize", 1.49 + "split", 1.50 + "winGetDrive", 1.51 + "winIsAbsolute", 1.52 + "toFileURI", 1.53 + "fromFileURI", 1.54 +]; 1.55 + 1.56 +/** 1.57 + * Return the final part of the path. 1.58 + * The final part of the path is everything after the last "\\". 1.59 + */ 1.60 +let basename = function(path) { 1.61 + if (path.startsWith("\\\\")) { 1.62 + // UNC-style path 1.63 + let index = path.lastIndexOf("\\"); 1.64 + if (index != 1) { 1.65 + return path.slice(index + 1); 1.66 + } 1.67 + return ""; // Degenerate case 1.68 + } 1.69 + return path.slice(Math.max(path.lastIndexOf("\\"), 1.70 + path.lastIndexOf(":")) + 1); 1.71 +}; 1.72 +exports.basename = basename; 1.73 + 1.74 +/** 1.75 + * Return the directory part of the path. 1.76 + * 1.77 + * If the path contains no directory, return the drive letter, 1.78 + * or "." if the path contains no drive letter or if option 1.79 + * |winNoDrive| is set. 1.80 + * 1.81 + * Otherwise, return everything before the last backslash, 1.82 + * including the drive/server name. 1.83 + * 1.84 + * 1.85 + * @param {string} path The path. 1.86 + * @param {*=} options Platform-specific options controlling the behavior 1.87 + * of this function. This implementation supports the following options: 1.88 + * - |winNoDrive| If |true|, also remove the letter from the path name. 1.89 + */ 1.90 +let dirname = function(path, options) { 1.91 + let noDrive = (options && options.winNoDrive); 1.92 + 1.93 + // Find the last occurrence of "\\" 1.94 + let index = path.lastIndexOf("\\"); 1.95 + if (index == -1) { 1.96 + // If there is no directory component... 1.97 + if (!noDrive) { 1.98 + // Return the drive path if possible, falling back to "." 1.99 + return this.winGetDrive(path) || "."; 1.100 + } else { 1.101 + // Or just "." 1.102 + return "."; 1.103 + } 1.104 + } 1.105 + 1.106 + if (index == 1 && path.charAt(0) == "\\") { 1.107 + // The path is reduced to a UNC drive 1.108 + if (noDrive) { 1.109 + return "."; 1.110 + } else { 1.111 + return path; 1.112 + } 1.113 + } 1.114 + 1.115 + // Ignore any occurrence of "\\: immediately before that one 1.116 + while (index >= 0 && path[index] == "\\") { 1.117 + --index; 1.118 + } 1.119 + 1.120 + // Compute what is left, removing the drive name if necessary 1.121 + let start; 1.122 + if (noDrive) { 1.123 + start = (this.winGetDrive(path) || "").length; 1.124 + } else { 1.125 + start = 0; 1.126 + } 1.127 + return path.slice(start, index + 1); 1.128 +}; 1.129 +exports.dirname = dirname; 1.130 + 1.131 +/** 1.132 + * Join path components. 1.133 + * This is the recommended manner of getting the path of a file/subdirectory 1.134 + * in a directory. 1.135 + * 1.136 + * Example: Obtaining $TMP/foo/bar in an OS-independent manner 1.137 + * var tmpDir = OS.Constants.Path.tmpDir; 1.138 + * var path = OS.Path.join(tmpDir, "foo", "bar"); 1.139 + * 1.140 + * Under Windows, this will return "$TMP\foo\bar". 1.141 + */ 1.142 +let join = function(...path) { 1.143 + let paths = []; 1.144 + let root; 1.145 + let absolute = false; 1.146 + for (let subpath of path) { 1.147 + if (subpath == null) { 1.148 + throw new TypeError("invalid path component"); 1.149 + } 1.150 + let drive = this.winGetDrive(subpath); 1.151 + if (drive) { 1.152 + root = drive; 1.153 + let component = trimBackslashes(subpath.slice(drive.length)); 1.154 + if (component) { 1.155 + paths = [component]; 1.156 + } else { 1.157 + paths = []; 1.158 + } 1.159 + absolute = true; 1.160 + } else if (this.winIsAbsolute(subpath)) { 1.161 + paths = [trimBackslashes(subpath)]; 1.162 + absolute = true; 1.163 + } else { 1.164 + paths.push(trimBackslashes(subpath)); 1.165 + } 1.166 + } 1.167 + let result = ""; 1.168 + if (root) { 1.169 + result += root; 1.170 + } 1.171 + if (absolute) { 1.172 + result += "\\"; 1.173 + } 1.174 + result += paths.join("\\"); 1.175 + return result; 1.176 +}; 1.177 +exports.join = join; 1.178 + 1.179 +/** 1.180 + * Return the drive name of a path, or |null| if the path does 1.181 + * not contain a drive name. 1.182 + * 1.183 + * Drive name appear either as "DriveName:..." (the return drive 1.184 + * name includes the ":") or "\\\\DriveName..." (the returned drive name 1.185 + * includes "\\\\"). 1.186 + */ 1.187 +let winGetDrive = function(path) { 1.188 + if (path == null) { 1.189 + throw new TypeError("path is invalid"); 1.190 + } 1.191 + 1.192 + if (path.startsWith("\\\\")) { 1.193 + // UNC path 1.194 + if (path.length == 2) { 1.195 + return null; 1.196 + } 1.197 + let index = path.indexOf("\\", 2); 1.198 + if (index == -1) { 1.199 + return path; 1.200 + } 1.201 + return path.slice(0, index); 1.202 + } 1.203 + // Non-UNC path 1.204 + let index = path.indexOf(":"); 1.205 + if (index <= 0) return null; 1.206 + return path.slice(0, index + 1); 1.207 +}; 1.208 +exports.winGetDrive = winGetDrive; 1.209 + 1.210 +/** 1.211 + * Return |true| if the path is absolute, |false| otherwise. 1.212 + * 1.213 + * We consider that a path is absolute if it starts with "\\" 1.214 + * or "driveletter:\\". 1.215 + */ 1.216 +let winIsAbsolute = function(path) { 1.217 + let index = path.indexOf(":"); 1.218 + return path.length > index + 1 && path[index + 1] == "\\"; 1.219 +}; 1.220 +exports.winIsAbsolute = winIsAbsolute; 1.221 + 1.222 +/** 1.223 + * Normalize a path by removing any unneeded ".", "..", "\\". 1.224 + * Also convert any "/" to a "\\". 1.225 + */ 1.226 +let normalize = function(path) { 1.227 + let stack = []; 1.228 + 1.229 + if (!path.startsWith("\\\\")) { 1.230 + // Normalize "/" to "\\" 1.231 + path = path.replace(/\//g, "\\"); 1.232 + } 1.233 + 1.234 + // Remove the drive (we will put it back at the end) 1.235 + let root = this.winGetDrive(path); 1.236 + if (root) { 1.237 + path = path.slice(root.length); 1.238 + } 1.239 + 1.240 + // Remember whether we need to restore a leading "\\" or drive name. 1.241 + let absolute = this.winIsAbsolute(path); 1.242 + 1.243 + // And now, fill |stack| from the components, 1.244 + // popping whenever there is a ".." 1.245 + path.split("\\").forEach(function loop(v) { 1.246 + switch (v) { 1.247 + case "": case ".": // Ignore 1.248 + break; 1.249 + case "..": 1.250 + if (stack.length == 0) { 1.251 + if (absolute) { 1.252 + throw new Error("Path is ill-formed: attempting to go past root"); 1.253 + } else { 1.254 + stack.push(".."); 1.255 + } 1.256 + } else { 1.257 + if (stack[stack.length - 1] == "..") { 1.258 + stack.push(".."); 1.259 + } else { 1.260 + stack.pop(); 1.261 + } 1.262 + } 1.263 + break; 1.264 + default: 1.265 + stack.push(v); 1.266 + } 1.267 + }); 1.268 + 1.269 + // Put everything back together 1.270 + let result = stack.join("\\"); 1.271 + if (absolute || root) { 1.272 + result = "\\" + result; 1.273 + } 1.274 + if (root) { 1.275 + result = root + result; 1.276 + } 1.277 + return result; 1.278 +}; 1.279 +exports.normalize = normalize; 1.280 + 1.281 +/** 1.282 + * Return the components of a path. 1.283 + * You should generally apply this function to a normalized path. 1.284 + * 1.285 + * @return {{ 1.286 + * {bool} absolute |true| if the path is absolute, |false| otherwise 1.287 + * {array} components the string components of the path 1.288 + * {string?} winDrive the drive or server for this path 1.289 + * }} 1.290 + * 1.291 + * Other implementations may add additional OS-specific informations. 1.292 + */ 1.293 +let split = function(path) { 1.294 + return { 1.295 + absolute: this.winIsAbsolute(path), 1.296 + winDrive: this.winGetDrive(path), 1.297 + components: path.split("\\") 1.298 + }; 1.299 +}; 1.300 +exports.split = split; 1.301 + 1.302 +/** 1.303 + * Return the file:// URI file path of the given local file path. 1.304 + */ 1.305 +// The case of %3b is designed to match Services.io, but fundamentally doesn't matter. 1.306 +let toFileURIExtraEncodings = {';': '%3b', '?': '%3F', "'": '%27', '#': '%23'}; 1.307 +let toFileURI = function toFileURI(path) { 1.308 + // URI-escape forward slashes and convert backward slashes to forward 1.309 + path = this.normalize(path).replace(/[\\\/]/g, m => (m=='\\')? '/' : '%2F'); 1.310 + let uri = encodeURI(path); 1.311 + 1.312 + // add a prefix, and encodeURI doesn't escape a few characters that we do 1.313 + // want to escape, so fix that up 1.314 + let prefix = "file:///"; 1.315 + uri = prefix + uri.replace(/[;?'#]/g, match => toFileURIExtraEncodings[match]); 1.316 + 1.317 + // turn e.g., file:///C: into file:///C:/ 1.318 + if (uri.charAt(uri.length - 1) === ':') { 1.319 + uri += "/" 1.320 + } 1.321 + 1.322 + return uri; 1.323 +}; 1.324 +exports.toFileURI = toFileURI; 1.325 + 1.326 +/** 1.327 + * Returns the local file path from a given file URI. 1.328 + */ 1.329 +let fromFileURI = function fromFileURI(uri) { 1.330 + let url = new URL(uri); 1.331 + if (url.protocol != 'file:') { 1.332 + throw new Error("fromFileURI expects a file URI"); 1.333 + } 1.334 + 1.335 + // strip leading slash, since Windows paths don't start with one 1.336 + uri = url.pathname.substr(1); 1.337 + 1.338 + let path = decodeURI(uri); 1.339 + // decode a few characters where URL's parsing is overzealous 1.340 + path = path.replace(/%(3b|3f|23)/gi, 1.341 + match => decodeURIComponent(match)); 1.342 + path = this.normalize(path); 1.343 + 1.344 + // this.normalize() does not remove the trailing slash if the path 1.345 + // component is a drive letter. eg. 'C:\'' will not get normalized. 1.346 + if (path.endsWith(":\\")) { 1.347 + path = path.substr(0, path.length - 1); 1.348 + } 1.349 + return this.normalize(path); 1.350 +}; 1.351 +exports.fromFileURI = fromFileURI; 1.352 + 1.353 +/** 1.354 +* Utility function: Remove any leading/trailing backslashes 1.355 +* from a string. 1.356 +*/ 1.357 +let trimBackslashes = function trimBackslashes(string) { 1.358 + return string.replace(/^\\+|\\+$/g,''); 1.359 +}; 1.360 + 1.361 +//////////// Boilerplate 1.362 +if (typeof Components != "undefined") { 1.363 + this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; 1.364 + for (let symbol of EXPORTED_SYMBOLS) { 1.365 + this[symbol] = exports[symbol]; 1.366 + } 1.367 +}