tor-browser

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

source-utils.js (10914B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 "use strict";
      5 
      6 const { LocalizationHelper } = require("resource://devtools/shared/l10n.js");
      7 
      8 const l10n = new LocalizationHelper(
      9  "devtools/client/locales/components.properties"
     10 );
     11 const UNKNOWN_SOURCE_STRING = l10n.getStr("frame.unknownSource");
     12 
     13 // Character codes used in various parsing helper functions.
     14 const CHAR_CODE_A = "a".charCodeAt(0);
     15 const CHAR_CODE_B = "b".charCodeAt(0);
     16 const CHAR_CODE_C = "c".charCodeAt(0);
     17 const CHAR_CODE_D = "d".charCodeAt(0);
     18 const CHAR_CODE_E = "e".charCodeAt(0);
     19 const CHAR_CODE_F = "f".charCodeAt(0);
     20 const CHAR_CODE_H = "h".charCodeAt(0);
     21 const CHAR_CODE_I = "i".charCodeAt(0);
     22 const CHAR_CODE_J = "j".charCodeAt(0);
     23 const CHAR_CODE_L = "l".charCodeAt(0);
     24 const CHAR_CODE_M = "m".charCodeAt(0);
     25 const CHAR_CODE_N = "n".charCodeAt(0);
     26 const CHAR_CODE_O = "o".charCodeAt(0);
     27 const CHAR_CODE_P = "p".charCodeAt(0);
     28 const CHAR_CODE_R = "r".charCodeAt(0);
     29 const CHAR_CODE_S = "s".charCodeAt(0);
     30 const CHAR_CODE_T = "t".charCodeAt(0);
     31 const CHAR_CODE_U = "u".charCodeAt(0);
     32 const CHAR_CODE_W = "w".charCodeAt(0);
     33 const CHAR_CODE_COLON = ":".charCodeAt(0);
     34 const CHAR_CODE_DASH = "-".charCodeAt(0);
     35 const CHAR_CODE_L_SQUARE_BRACKET = "[".charCodeAt(0);
     36 const CHAR_CODE_SLASH = "/".charCodeAt(0);
     37 
     38 // The cache used in the `parseURL` function.
     39 const gURLStore = new Map();
     40 // The cache used in the `getSourceNames` function.
     41 const gSourceNamesStore = new Map();
     42 
     43 /**
     44 * Takes a string and returns an object containing all the properties
     45 * available on an URL instance, with additional properties (fileName),
     46 * Leverages caching.
     47 *
     48 * @param {string} location
     49 * @return {object?} An object containing most properties available
     50 *                   in https://developer.mozilla.org/en-US/docs/Web/API/URL
     51 */
     52 
     53 function parseURL(location) {
     54  let url = gURLStore.get(location);
     55 
     56  if (url !== void 0) {
     57    return url;
     58  }
     59 
     60  try {
     61    url = new URL(location);
     62    // The callers were generally written to expect a URL from
     63    // sdk/url, which is subtly different.  So, work around some
     64    // important differences here.
     65    url = {
     66      href: url.href,
     67      protocol: url.protocol,
     68      host: url.host,
     69      hostname: url.hostname,
     70      port: url.port || null,
     71      pathname: url.pathname,
     72      search: url.search,
     73      hash: url.hash,
     74      username: url.username,
     75      password: url.password,
     76      origin: url.origin,
     77    };
     78 
     79    // Definitions:
     80    // Example: https://foo.com:8888/file.js
     81    // `hostname`: "foo.com"
     82    // `host`: "foo.com:8888"
     83    const isChrome = isChromeScheme(location);
     84 
     85    url.fileName = url.pathname
     86      ? url.pathname.slice(url.pathname.lastIndexOf("/") + 1) || "/"
     87      : "/";
     88 
     89    if (isChrome) {
     90      url.hostname = null;
     91      url.host = null;
     92    }
     93 
     94    gURLStore.set(location, url);
     95    return url;
     96  } catch (e) {
     97    gURLStore.set(location, null);
     98    return null;
     99  }
    100 }
    101 
    102 /**
    103 * Parse a source into a short and long name as well as a host name.
    104 *
    105 * @param {string} source
    106 *        The source to parse. Can be a URI or names like "(eval)" or
    107 *        "self-hosted".
    108 * @return {object}
    109 *         An object with the following properties:
    110 *           - {String} short: A short name for the source.
    111 *             - "http://page.com/test.js#go?q=query" -> "test.js"
    112 *           - {String} long: The full, long name for the source, with
    113               hash/query stripped.
    114 *             - "http://page.com/test.js#go?q=query" -> "http://page.com/test.js"
    115 *           - {String?} host: If available, the host name for the source.
    116 *             - "http://page.com/test.js#go?q=query" -> "page.com"
    117 */
    118 function getSourceNames(source) {
    119  const data = gSourceNamesStore.get(source);
    120 
    121  if (data) {
    122    return data;
    123  }
    124 
    125  let short, long, host;
    126  const sourceStr = source ? String(source) : "";
    127 
    128  // If `data:...` uri
    129  if (isDataScheme(sourceStr)) {
    130    const commaIndex = sourceStr.indexOf(",");
    131    if (commaIndex > -1) {
    132      // The `short` name for a data URI becomes `data:` followed by the actual
    133      // encoded content, omitting the MIME type, and charset.
    134      short = `data:${sourceStr.substring(commaIndex + 1)}`.slice(0, 100);
    135      const result = { short, long: sourceStr };
    136      gSourceNamesStore.set(source, result);
    137      return result;
    138    }
    139  }
    140 
    141  const parsedUrl = parseURL(sourceStr);
    142 
    143  if (!parsedUrl) {
    144    // Malformed URI.
    145    long = sourceStr;
    146    short = sourceStr.slice(0, 100);
    147  } else {
    148    host = parsedUrl.host;
    149 
    150    long = parsedUrl.href;
    151    if (parsedUrl.hash) {
    152      long = long.replace(parsedUrl.hash, "");
    153    }
    154    if (parsedUrl.search) {
    155      long = long.replace(parsedUrl.search, "");
    156    }
    157 
    158    short = parsedUrl.fileName;
    159    // If `short` is just a slash, and we actually have a path,
    160    // strip the slash and parse again to get a more useful short name.
    161    // e.g. "http://foo.com/bar/" -> "bar", rather than "/"
    162    if (short === "/" && parsedUrl.pathname !== "/") {
    163      short = parseURL(long.replace(/\/$/, "")).fileName;
    164    }
    165  }
    166 
    167  if (!short) {
    168    if (!long) {
    169      long = UNKNOWN_SOURCE_STRING;
    170    }
    171    short = long.slice(0, 100);
    172  }
    173 
    174  const result = { short, long, host };
    175  gSourceNamesStore.set(source, result);
    176  return result;
    177 }
    178 
    179 // For the functions below, we assume that we will never access the location
    180 // argument out of bounds, which is indeed the vast majority of cases.
    181 //
    182 // They are written this way because they are hot. Each frame is checked for
    183 // being content or chrome when processing the profile.
    184 
    185 function isColonSlashSlash(location, i = 0) {
    186  return (
    187    location.charCodeAt(++i) === CHAR_CODE_COLON &&
    188    location.charCodeAt(++i) === CHAR_CODE_SLASH &&
    189    location.charCodeAt(++i) === CHAR_CODE_SLASH
    190  );
    191 }
    192 
    193 function isDataScheme(location, i = 0) {
    194  return (
    195    location.charCodeAt(i) === CHAR_CODE_D &&
    196    location.charCodeAt(++i) === CHAR_CODE_A &&
    197    location.charCodeAt(++i) === CHAR_CODE_T &&
    198    location.charCodeAt(++i) === CHAR_CODE_A &&
    199    location.charCodeAt(++i) === CHAR_CODE_COLON
    200  );
    201 }
    202 
    203 function isContentScheme(location, i = 0) {
    204  const firstChar = location.charCodeAt(i);
    205 
    206  switch (firstChar) {
    207    // "http://" or "https://"
    208    case CHAR_CODE_H:
    209      if (
    210        location.charCodeAt(++i) === CHAR_CODE_T &&
    211        location.charCodeAt(++i) === CHAR_CODE_T &&
    212        location.charCodeAt(++i) === CHAR_CODE_P
    213      ) {
    214        if (location.charCodeAt(i + 1) === CHAR_CODE_S) {
    215          ++i;
    216        }
    217        return isColonSlashSlash(location, i);
    218      }
    219      return false;
    220 
    221    // "file://"
    222    case CHAR_CODE_F:
    223      if (
    224        location.charCodeAt(++i) === CHAR_CODE_I &&
    225        location.charCodeAt(++i) === CHAR_CODE_L &&
    226        location.charCodeAt(++i) === CHAR_CODE_E
    227      ) {
    228        return isColonSlashSlash(location, i);
    229      }
    230      return false;
    231 
    232    // "app://"
    233    case CHAR_CODE_A:
    234      if (
    235        location.charCodeAt(++i) == CHAR_CODE_P &&
    236        location.charCodeAt(++i) == CHAR_CODE_P
    237      ) {
    238        return isColonSlashSlash(location, i);
    239      }
    240      return false;
    241 
    242    // "blob:"
    243    case CHAR_CODE_B:
    244      if (
    245        location.charCodeAt(++i) == CHAR_CODE_L &&
    246        location.charCodeAt(++i) == CHAR_CODE_O &&
    247        location.charCodeAt(++i) == CHAR_CODE_B &&
    248        location.charCodeAt(++i) == CHAR_CODE_COLON
    249      ) {
    250        return isContentScheme(location, i + 1);
    251      }
    252      return false;
    253 
    254    default:
    255      return false;
    256  }
    257 }
    258 
    259 function isChromeString(location, i = 0) {
    260  if (
    261    location.charCodeAt(i) === CHAR_CODE_C &&
    262    location.charCodeAt(++i) === CHAR_CODE_H &&
    263    location.charCodeAt(++i) === CHAR_CODE_R &&
    264    location.charCodeAt(++i) === CHAR_CODE_O &&
    265    location.charCodeAt(++i) === CHAR_CODE_M &&
    266    location.charCodeAt(++i) === CHAR_CODE_E
    267  ) {
    268    return isColonSlashSlash(location, i);
    269  }
    270  return false;
    271 }
    272 
    273 function isResourceString(location, i = 0) {
    274  if (
    275    location.charCodeAt(i) === CHAR_CODE_R &&
    276    location.charCodeAt(++i) === CHAR_CODE_E &&
    277    location.charCodeAt(++i) === CHAR_CODE_S &&
    278    location.charCodeAt(++i) === CHAR_CODE_O &&
    279    location.charCodeAt(++i) === CHAR_CODE_U &&
    280    location.charCodeAt(++i) === CHAR_CODE_R &&
    281    location.charCodeAt(++i) === CHAR_CODE_C &&
    282    location.charCodeAt(++i) === CHAR_CODE_E
    283  ) {
    284    return isColonSlashSlash(location, i);
    285  }
    286  return false;
    287 }
    288 
    289 function isJarFileString(location, i = 0) {
    290  if (
    291    location.charCodeAt(i) === CHAR_CODE_J &&
    292    location.charCodeAt(++i) === CHAR_CODE_A &&
    293    location.charCodeAt(++i) === CHAR_CODE_R &&
    294    location.charCodeAt(++i) === CHAR_CODE_COLON &&
    295    location.charCodeAt(++i) === CHAR_CODE_F &&
    296    location.charCodeAt(++i) === CHAR_CODE_I &&
    297    location.charCodeAt(++i) === CHAR_CODE_L &&
    298    location.charCodeAt(++i) === CHAR_CODE_E
    299  ) {
    300    return isColonSlashSlash(location, i);
    301  }
    302  return false;
    303 }
    304 
    305 function isChromeScheme(location, i = 0) {
    306  return (
    307    isChromeString(location, i) ||
    308    isResourceString(location, i) ||
    309    isJarFileString(location, i)
    310  );
    311 }
    312 
    313 function isWASM(location, i = 0) {
    314  return (
    315    // "wasm-function["
    316    location.charCodeAt(i) === CHAR_CODE_W &&
    317    location.charCodeAt(++i) === CHAR_CODE_A &&
    318    location.charCodeAt(++i) === CHAR_CODE_S &&
    319    location.charCodeAt(++i) === CHAR_CODE_M &&
    320    location.charCodeAt(++i) === CHAR_CODE_DASH &&
    321    location.charCodeAt(++i) === CHAR_CODE_F &&
    322    location.charCodeAt(++i) === CHAR_CODE_U &&
    323    location.charCodeAt(++i) === CHAR_CODE_N &&
    324    location.charCodeAt(++i) === CHAR_CODE_C &&
    325    location.charCodeAt(++i) === CHAR_CODE_T &&
    326    location.charCodeAt(++i) === CHAR_CODE_I &&
    327    location.charCodeAt(++i) === CHAR_CODE_O &&
    328    location.charCodeAt(++i) === CHAR_CODE_N &&
    329    location.charCodeAt(++i) === CHAR_CODE_L_SQUARE_BRACKET
    330  );
    331 }
    332 
    333 /**
    334 * A utility method to get the file name from a sourcemapped location
    335 * The sourcemap location can be in any form. This method returns a
    336 * formatted file name for different cases like Windows or OSX.
    337 *
    338 * @param source
    339 * @returns String
    340 */
    341 function getSourceMappedFile(source) {
    342  // If sourcemapped source is a OSX path, return
    343  // the characters after last "/".
    344  // If sourcemapped source is a Windowss path, return
    345  // the characters after last "\\".
    346  if (source.lastIndexOf("/") >= 0) {
    347    source = source.slice(source.lastIndexOf("/") + 1);
    348  } else if (source.lastIndexOf("\\") >= 0) {
    349    source = source.slice(source.lastIndexOf("\\") + 1);
    350  }
    351  return source;
    352 }
    353 
    354 exports.parseURL = parseURL;
    355 exports.getSourceNames = getSourceNames;
    356 exports.isChromeScheme = isChromeScheme;
    357 exports.isContentScheme = isContentScheme;
    358 exports.isWASM = isWASM;
    359 exports.isDataScheme = isDataScheme;
    360 exports.getSourceMappedFile = getSourceMappedFile;