summaryrefslogtreecommitdiffstats
path: root/toolkit/components/osfile/modules/ospath_unix.jsm
blob: 1d574baed266282dddf294f87a2ba69b146c6d73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * 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|.
 */

"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",
  "toFileURI",
  "fromFileURI",
];

/**
 * Return the final part of the path.
 * The final part of the path is everything after the last "/".
 */
var basename = function(path) {
  return path.slice(path.lastIndexOf("/") + 1);
};
exports.basename = basename;

/**
 * Return the directory part of the path.
 * The directory part of the path is everything before the last
 * "/". If the last few characters of this part are also "/",
 * they are ignored.
 *
 * If the path contains no directory, return ".".
 */
var dirname = function(path) {
  let index = path.lastIndexOf("/");
  if (index == -1) {
    return ".";
  }
  while (index >= 0 && path[index] == "/") {
    --index;
  }
  return path.slice(0, 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 Unix, 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) {
  // If there is a path that starts with a "/", eliminate everything before
  let paths = [];
  for (let subpath of path) {
    if (subpath == null) {
      throw new TypeError("invalid path component");
    }
    if (subpath.length == 0) {
      continue;
    } else if (subpath[0] == "/") {
      paths = [subpath];
    } else {
      paths.push(subpath);
    }
  }
  return paths.join("/");
};
exports.join = join;

/**
 * Normalize a path by removing any unneeded ".", "..", "//".
 */
var normalize = function(path) {
  let stack = [];
  let absolute;
  if (path.length >= 0 && path[0] == "/") {
    absolute = true;
  } else {
    absolute = false;
  }
  path.split("/").forEach(function(v) {
    switch (v) {
    case "":  case ".":// fallthrough
      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);
    }
  });
  let string = stack.join("/");
  return absolute ? "/" + string : string;
};
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
 * }}
 *
 * Other implementations may add additional OS-specific informations.
 */
var split = function(path) {
  return {
    absolute: path.length && path[0] == "/",
    components: path.split("/")
  };
};
exports.split = split;

/**
 * Returns 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) {
  // Per https://url.spec.whatwg.org we should not encode [] in the path
  let dontNeedEscaping = {'%5B': '[', '%5D': ']'};
  let uri = encodeURI(this.normalize(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]);

  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");
  }
  let path = this.normalize(decodeURIComponent(url.pathname));
  return path;
};
exports.fromFileURI = fromFileURI;


//////////// Boilerplate
if (typeof Components != "undefined") {
  this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
  for (let symbol of EXPORTED_SYMBOLS) {
    this[symbol] = exports[symbol];
  }
}