tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

util.js (11815B)


      1 /* -*- Mode: js; js-indent-level: 2; -*- */
      2 /*
      3 * Copyright 2011 Mozilla Foundation and contributors
      4 * Licensed under the New BSD license. See LICENSE or:
      5 * http://opensource.org/licenses/BSD-3-Clause
      6 */
      7 
      8 const URL = require("./url");
      9 
     10 /**
     11 * This is a helper function for getting values from parameter/options
     12 * objects.
     13 *
     14 * @param args The object we are extracting values from
     15 * @param name The name of the property we are getting.
     16 * @param defaultValue An optional value to return if the property is missing
     17 * from the object. If this is not specified and the property is missing, an
     18 * error will be thrown.
     19 */
     20 function getArg(aArgs, aName, aDefaultValue) {
     21  if (aName in aArgs) {
     22    return aArgs[aName];
     23  } else if (arguments.length === 3) {
     24    return aDefaultValue;
     25  }
     26  throw new Error('"' + aName + '" is a required argument.');
     27 }
     28 exports.getArg = getArg;
     29 
     30 const supportsNullProto = (function () {
     31  const obj = Object.create(null);
     32  return !("__proto__" in obj);
     33 })();
     34 
     35 function identity(s) {
     36  return s;
     37 }
     38 
     39 /**
     40 * Because behavior goes wacky when you set `__proto__` on objects, we
     41 * have to prefix all the strings in our set with an arbitrary character.
     42 *
     43 * See https://github.com/mozilla/source-map/pull/31 and
     44 * https://github.com/mozilla/source-map/issues/30
     45 *
     46 * @param String aStr
     47 */
     48 function toSetString(aStr) {
     49  if (isProtoString(aStr)) {
     50    return "$" + aStr;
     51  }
     52 
     53  return aStr;
     54 }
     55 exports.toSetString = supportsNullProto ? identity : toSetString;
     56 
     57 function fromSetString(aStr) {
     58  if (isProtoString(aStr)) {
     59    return aStr.slice(1);
     60  }
     61 
     62  return aStr;
     63 }
     64 exports.fromSetString = supportsNullProto ? identity : fromSetString;
     65 
     66 function isProtoString(s) {
     67  if (!s) {
     68    return false;
     69  }
     70 
     71  const length = s.length;
     72 
     73  if (length < 9 /* "__proto__".length */) {
     74    return false;
     75  }
     76 
     77  /* eslint-disable no-multi-spaces */
     78  if (
     79    s.charCodeAt(length - 1) !== 95 /* '_' */ ||
     80    s.charCodeAt(length - 2) !== 95 /* '_' */ ||
     81    s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
     82    s.charCodeAt(length - 4) !== 116 /* 't' */ ||
     83    s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
     84    s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
     85    s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
     86    s.charCodeAt(length - 8) !== 95 /* '_' */ ||
     87    s.charCodeAt(length - 9) !== 95 /* '_' */
     88  ) {
     89    return false;
     90  }
     91  /* eslint-enable no-multi-spaces */
     92 
     93  for (let i = length - 10; i >= 0; i--) {
     94    if (s.charCodeAt(i) !== 36 /* '$' */) {
     95      return false;
     96    }
     97  }
     98 
     99  return true;
    100 }
    101 
    102 function strcmp(aStr1, aStr2) {
    103  if (aStr1 === aStr2) {
    104    return 0;
    105  }
    106 
    107  if (aStr1 === null) {
    108    return 1; // aStr2 !== null
    109  }
    110 
    111  if (aStr2 === null) {
    112    return -1; // aStr1 !== null
    113  }
    114 
    115  if (aStr1 > aStr2) {
    116    return 1;
    117  }
    118 
    119  return -1;
    120 }
    121 
    122 /**
    123 * Comparator between two mappings with inflated source and name strings where
    124 * the generated positions are compared.
    125 */
    126 function compareByGeneratedPositionsInflated(mappingA, mappingB) {
    127  let cmp = mappingA.generatedLine - mappingB.generatedLine;
    128  if (cmp !== 0) {
    129    return cmp;
    130  }
    131 
    132  cmp = mappingA.generatedColumn - mappingB.generatedColumn;
    133  if (cmp !== 0) {
    134    return cmp;
    135  }
    136 
    137  cmp = strcmp(mappingA.source, mappingB.source);
    138  if (cmp !== 0) {
    139    return cmp;
    140  }
    141 
    142  cmp = mappingA.originalLine - mappingB.originalLine;
    143  if (cmp !== 0) {
    144    return cmp;
    145  }
    146 
    147  cmp = mappingA.originalColumn - mappingB.originalColumn;
    148  if (cmp !== 0) {
    149    return cmp;
    150  }
    151 
    152  return strcmp(mappingA.name, mappingB.name);
    153 }
    154 exports.compareByGeneratedPositionsInflated =
    155  compareByGeneratedPositionsInflated;
    156 
    157 /**
    158 * Strip any JSON XSSI avoidance prefix from the string (as documented
    159 * in the source maps specification), and then parse the string as
    160 * JSON.
    161 */
    162 function parseSourceMapInput(str) {
    163  return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ""));
    164 }
    165 exports.parseSourceMapInput = parseSourceMapInput;
    166 
    167 // We use 'http' as the base here because we want URLs processed relative
    168 // to the safe base to be treated as "special" URLs during parsing using
    169 // the WHATWG URL parsing. This ensures that backslash normalization
    170 // applies to the path and such.
    171 const PROTOCOL = "http:";
    172 const PROTOCOL_AND_HOST = `${PROTOCOL}//host`;
    173 
    174 /**
    175 * Make it easy to create small utilities that tweak a URL's path.
    176 */
    177 function createSafeHandler(cb) {
    178  return input => {
    179    const type = getURLType(input);
    180    const base = buildSafeBase(input);
    181    const url = new URL(input, base);
    182 
    183    cb(url);
    184 
    185    const result = url.toString();
    186 
    187    if (type === "absolute") {
    188      return result;
    189    } else if (type === "scheme-relative") {
    190      return result.slice(PROTOCOL.length);
    191    } else if (type === "path-absolute") {
    192      return result.slice(PROTOCOL_AND_HOST.length);
    193    }
    194 
    195    // This assumes that the callback will only change
    196    // the path, search and hash values.
    197    return computeRelativeURL(base, result);
    198  };
    199 }
    200 
    201 function withBase(url, base) {
    202  return new URL(url, base).toString();
    203 }
    204 
    205 function buildUniqueSegment(prefix, str) {
    206  let id = 0;
    207  do {
    208    const ident = prefix + id++;
    209    if (str.indexOf(ident) === -1) return ident;
    210  } while (true);
    211 }
    212 
    213 function buildSafeBase(str) {
    214  const maxDotParts = str.split("..").length - 1;
    215 
    216  // If we used a segment that also existed in `str`, then we would be unable
    217  // to compute relative paths. For example, if `segment` were just "a":
    218  //
    219  //   const url = "../../a/"
    220  //   const base = buildSafeBase(url); // http://host/a/a/
    221  //   const joined = "http://host/a/";
    222  //   const result = relative(base, joined);
    223  //
    224  // Expected: "../../a/";
    225  // Actual: "a/"
    226  //
    227  const segment = buildUniqueSegment("p", str);
    228 
    229  let base = `${PROTOCOL_AND_HOST}/`;
    230  for (let i = 0; i < maxDotParts; i++) {
    231    base += `${segment}/`;
    232  }
    233  return base;
    234 }
    235 
    236 const ABSOLUTE_SCHEME = /^[A-Za-z0-9\+\-\.]+:/;
    237 function getURLType(url) {
    238  if (url[0] === "/") {
    239    if (url[1] === "/") return "scheme-relative";
    240    return "path-absolute";
    241  }
    242 
    243  return ABSOLUTE_SCHEME.test(url) ? "absolute" : "path-relative";
    244 }
    245 
    246 /**
    247 * Given two URLs that are assumed to be on the same
    248 * protocol/host/user/password build a relative URL from the
    249 * path, params, and hash values.
    250 *
    251 * @param rootURL The root URL that the target will be relative to.
    252 * @param targetURL The target that the relative URL points to.
    253 * @return A rootURL-relative, normalized URL value.
    254 */
    255 function computeRelativeURL(rootURL, targetURL) {
    256  if (typeof rootURL === "string") rootURL = new URL(rootURL);
    257  if (typeof targetURL === "string") targetURL = new URL(targetURL);
    258 
    259  const targetParts = targetURL.pathname.split("/");
    260  const rootParts = rootURL.pathname.split("/");
    261 
    262  // If we've got a URL path ending with a "/", we remove it since we'd
    263  // otherwise be relative to the wrong location.
    264  if (rootParts.length > 0 && !rootParts[rootParts.length - 1]) {
    265    rootParts.pop();
    266  }
    267 
    268  while (
    269    targetParts.length > 0 &&
    270    rootParts.length > 0 &&
    271    targetParts[0] === rootParts[0]
    272  ) {
    273    targetParts.shift();
    274    rootParts.shift();
    275  }
    276 
    277  const relativePath = rootParts
    278    .map(() => "..")
    279    .concat(targetParts)
    280    .join("/");
    281 
    282  return relativePath + targetURL.search + targetURL.hash;
    283 }
    284 
    285 /**
    286 * Given a URL, ensure that it is treated as a directory URL.
    287 *
    288 * @param url
    289 * @return A normalized URL value.
    290 */
    291 const ensureDirectory = createSafeHandler(url => {
    292  url.pathname = url.pathname.replace(/\/?$/, "/");
    293 });
    294 
    295 /**
    296 * Given a URL, strip off any filename if one is present.
    297 *
    298 * @param url
    299 * @return A normalized URL value.
    300 */
    301 const trimFilename = createSafeHandler(url => {
    302  url.href = new URL(".", url.toString()).toString();
    303 });
    304 
    305 /**
    306 * Normalize a given URL.
    307 * * Convert backslashes.
    308 * * Remove any ".." and "." segments.
    309 *
    310 * @param url
    311 * @return A normalized URL value.
    312 */
    313 const normalize = createSafeHandler(url => {});
    314 exports.normalize = normalize;
    315 
    316 /**
    317 * Joins two paths/URLs.
    318 *
    319 * All returned URLs will be normalized.
    320 *
    321 * @param aRoot The root path or URL. Assumed to reference a directory.
    322 * @param aPath The path or URL to be joined with the root.
    323 * @return A joined and normalized URL value.
    324 */
    325 function join(aRoot, aPath) {
    326  const pathType = getURLType(aPath);
    327  const rootType = getURLType(aRoot);
    328 
    329  aRoot = ensureDirectory(aRoot);
    330 
    331  if (pathType === "absolute") {
    332    return withBase(aPath, undefined);
    333  }
    334  if (rootType === "absolute") {
    335    return withBase(aPath, aRoot);
    336  }
    337 
    338  if (pathType === "scheme-relative") {
    339    return normalize(aPath);
    340  }
    341  if (rootType === "scheme-relative") {
    342    return withBase(aPath, withBase(aRoot, PROTOCOL_AND_HOST)).slice(
    343      PROTOCOL.length
    344    );
    345  }
    346 
    347  if (pathType === "path-absolute") {
    348    return normalize(aPath);
    349  }
    350  if (rootType === "path-absolute") {
    351    return withBase(aPath, withBase(aRoot, PROTOCOL_AND_HOST)).slice(
    352      PROTOCOL_AND_HOST.length
    353    );
    354  }
    355 
    356  const base = buildSafeBase(aPath + aRoot);
    357  const newPath = withBase(aPath, withBase(aRoot, base));
    358  return computeRelativeURL(base, newPath);
    359 }
    360 exports.join = join;
    361 
    362 /**
    363 * Make a path relative to a URL or another path. If returning a
    364 * relative URL is not possible, the original target will be returned.
    365 * All returned URLs will be normalized.
    366 *
    367 * @param aRoot The root path or URL.
    368 * @param aPath The path or URL to be made relative to aRoot.
    369 * @return A rootURL-relative (if possible), normalized URL value.
    370 */
    371 function relative(rootURL, targetURL) {
    372  const result = relativeIfPossible(rootURL, targetURL);
    373 
    374  return typeof result === "string" ? result : normalize(targetURL);
    375 }
    376 exports.relative = relative;
    377 
    378 function relativeIfPossible(rootURL, targetURL) {
    379  const urlType = getURLType(rootURL);
    380  if (urlType !== getURLType(targetURL)) {
    381    return null;
    382  }
    383 
    384  const base = buildSafeBase(rootURL + targetURL);
    385  const root = new URL(rootURL, base);
    386  const target = new URL(targetURL, base);
    387 
    388  try {
    389    new URL("", target.toString());
    390  } catch (err) {
    391    // Bail if the URL doesn't support things being relative to it,
    392    // For example, data: and blob: URLs.
    393    return null;
    394  }
    395 
    396  if (
    397    target.protocol !== root.protocol ||
    398    target.user !== root.user ||
    399    target.password !== root.password ||
    400    target.hostname !== root.hostname ||
    401    target.port !== root.port
    402  ) {
    403    return null;
    404  }
    405 
    406  return computeRelativeURL(root, target);
    407 }
    408 
    409 /**
    410 * Compute the URL of a source given the the source root, the source's
    411 * URL, and the source map's URL.
    412 */
    413 function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
    414  // The source map spec states that "sourceRoot" and "sources" entries are to be appended. While
    415  // that is a little vague, implementations have generally interpreted that as joining the
    416  // URLs with a `/` between then, assuming the "sourceRoot" doesn't already end with one.
    417  // For example,
    418  //
    419  //   sourceRoot: "some-dir",
    420  //   sources: ["/some-path.js"]
    421  //
    422  // and
    423  //
    424  //   sourceRoot: "some-dir/",
    425  //   sources: ["/some-path.js"]
    426  //
    427  // must behave as "some-dir/some-path.js".
    428  //
    429  // With this library's the transition to a more URL-focused implementation, that behavior is
    430  // preserved here. To acheive that, we trim the "/" from absolute-path when a sourceRoot value
    431  // is present in order to make the sources entries behave as if they are relative to the
    432  // "sourceRoot", as they would have if the two strings were simply concated.
    433  if (sourceRoot && getURLType(sourceURL) === "path-absolute") {
    434    sourceURL = sourceURL.replace(/^\//, "");
    435  }
    436 
    437  let url = normalize(sourceURL || "");
    438 
    439  // Parsing URLs can be expensive, so we only perform these joins when needed.
    440  if (sourceRoot) url = join(sourceRoot, url);
    441  if (sourceMapURL) url = join(trimFilename(sourceMapURL), url);
    442  return url;
    443 }
    444 exports.computeSourceURL = computeSourceURL;