toolkit/components/osfile/modules/ospath_win.jsm

changeset 0
6474c204b198
     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 +}

mercurial