tor-browser

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

intervention_helpers.js (9835B)


      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 
      5 "use strict";
      6 
      7 /* globals browser, UAHelpers */
      8 
      9 const GOOGLE_TLDS = [
     10  "com",
     11  "ac",
     12  "ad",
     13  "ae",
     14  "com.af",
     15  "com.ag",
     16  "com.ai",
     17  "al",
     18  "am",
     19  "co.ao",
     20  "com.ar",
     21  "as",
     22  "at",
     23  "com.au",
     24  "az",
     25  "ba",
     26  "com.bd",
     27  "be",
     28  "bf",
     29  "bg",
     30  "com.bh",
     31  "bi",
     32  "bj",
     33  "com.bn",
     34  "com.bo",
     35  "com.br",
     36  "bs",
     37  "bt",
     38  "co.bw",
     39  "by",
     40  "com.bz",
     41  "ca",
     42  "com.kh",
     43  "cc",
     44  "cd",
     45  "cf",
     46  "cat",
     47  "cg",
     48  "ch",
     49  "ci",
     50  "co.ck",
     51  "cl",
     52  "cm",
     53  "cn",
     54  "com.co",
     55  "co.cr",
     56  "com.cu",
     57  "cv",
     58  "com.cy",
     59  "cz",
     60  "de",
     61  "dj",
     62  "dk",
     63  "dm",
     64  "com.do",
     65  "dz",
     66  "com.ec",
     67  "ee",
     68  "com.eg",
     69  "es",
     70  "com.et",
     71  "fi",
     72  "com.fj",
     73  "fm",
     74  "fr",
     75  "ga",
     76  "ge",
     77  "gf",
     78  "gg",
     79  "com.gh",
     80  "com.gi",
     81  "gl",
     82  "gm",
     83  "gp",
     84  "gr",
     85  "com.gt",
     86  "gy",
     87  "com.hk",
     88  "hn",
     89  "hr",
     90  "ht",
     91  "hu",
     92  "co.id",
     93  "iq",
     94  "ie",
     95  "co.il",
     96  "im",
     97  "co.in",
     98  "io",
     99  "is",
    100  "it",
    101  "je",
    102  "com.jm",
    103  "jo",
    104  "co.jp",
    105  "co.ke",
    106  "ki",
    107  "kg",
    108  "co.kr",
    109  "com.kw",
    110  "kz",
    111  "la",
    112  "com.lb",
    113  "com.lc",
    114  "li",
    115  "lk",
    116  "co.ls",
    117  "lt",
    118  "lu",
    119  "lv",
    120  "com.ly",
    121  "co.ma",
    122  "md",
    123  "me",
    124  "mg",
    125  "mk",
    126  "ml",
    127  "com.mm",
    128  "mn",
    129  "ms",
    130  "com.mt",
    131  "mu",
    132  "mv",
    133  "mw",
    134  "com.mx",
    135  "com.my",
    136  "co.mz",
    137  "com.na",
    138  "ne",
    139  "com.nf",
    140  "com.ng",
    141  "com.ni",
    142  "nl",
    143  "no",
    144  "com.np",
    145  "nr",
    146  "nu",
    147  "co.nz",
    148  "com.om",
    149  "com.pk",
    150  "com.pa",
    151  "com.pe",
    152  "com.ph",
    153  "pl",
    154  "com.pg",
    155  "pn",
    156  "com.pr",
    157  "ps",
    158  "pt",
    159  "com.py",
    160  "com.qa",
    161  "ro",
    162  "rs",
    163  "ru",
    164  "rw",
    165  "com.sa",
    166  "com.sb",
    167  "sc",
    168  "se",
    169  "com.sg",
    170  "sh",
    171  "si",
    172  "sk",
    173  "com.sl",
    174  "sn",
    175  "sm",
    176  "so",
    177  "st",
    178  "sr",
    179  "com.sv",
    180  "td",
    181  "tg",
    182  "co.th",
    183  "com.tj",
    184  "tk",
    185  "tl",
    186  "tm",
    187  "to",
    188  "tn",
    189  "com.tr",
    190  "tt",
    191  "com.tw",
    192  "co.tz",
    193  "com.ua",
    194  "co.ug",
    195  "co.uk",
    196  "com",
    197  "com.uy",
    198  "co.uz",
    199  "com.vc",
    200  "co.ve",
    201  "vg",
    202  "co.vi",
    203  "com.vn",
    204  "vu",
    205  "ws",
    206  "co.za",
    207  "co.zm",
    208  "co.zw",
    209 ];
    210 
    211 var InterventionHelpers = {
    212  skip_if_functions: {
    213    getWeekInfo_defined: () => {
    214      return !!Intl?.Locale?.prototype?.getWeekInfo;
    215    },
    216    InstallTrigger_defined: () => {
    217      return "InstallTrigger" in window;
    218    },
    219    InstallTrigger_undefined: () => {
    220      return !("InstallTrigger" in window);
    221    },
    222    text_event_supported: () => {
    223      return !!window.TextEvent;
    224    },
    225  },
    226 
    227  ua_change_functions: {
    228    add_Chrome: (ua, config) => {
    229      return UAHelpers.addChrome(ua, config.version);
    230    },
    231    add_Firefox_as_Gecko: (ua, config) => {
    232      return UAHelpers.addGecko(ua, config.version);
    233    },
    234    add_Samsung_for_Samsung_devices: ua => {
    235      return UAHelpers.addSamsungForSamsungDevices(ua);
    236    },
    237    add_Version_segment: ua => {
    238      return `${ua} Version/0`;
    239    },
    240    cap_Version_to_99: ua => {
    241      return UAHelpers.capVersionTo99(ua);
    242    },
    243    change_Firefox_to_FireFox: ua => {
    244      return UAHelpers.changeFirefoxToFireFox(ua);
    245    },
    246    change_Gecko_to_like_Gecko: ua => {
    247      return ua.replace("Gecko", "like Gecko");
    248    },
    249    change_OS_to_MacOSX: (ua, config) => {
    250      return UAHelpers.getMacOSXUA(ua, config.arch, config.version);
    251    },
    252    change_OS_to_Windows: ua => {
    253      return UAHelpers.windows(ua);
    254    },
    255    Chrome: (ua, config) => {
    256      config.ua = ua;
    257      config.noFxQuantum = true;
    258      return UAHelpers.getDeviceAppropriateChromeUA(config);
    259    },
    260    Chrome_with_FxQuantum: (ua, config) => {
    261      config.ua = ua;
    262      return UAHelpers.getDeviceAppropriateChromeUA(config);
    263    },
    264    desktop_not_mobile: () => {
    265      return UAHelpers.desktopUA();
    266    },
    267    mimic_Android_Hotspot2_device: ua => {
    268      return UAHelpers.androidHotspot2Device(ua);
    269    },
    270    replace_colon_in_rv_with_space: ua => {
    271      return ua.replace("rv:", "rv ");
    272    },
    273    reduce_firefox_version_by_one: ua => {
    274      const [head, fx, tail] = ua.split(/(firefox\/)/i);
    275      if (!fx || !tail) {
    276        return ua;
    277      }
    278      const major = parseInt(tail);
    279      if (!major) {
    280        return ua;
    281      }
    282      return `${head}${fx}${major - 1}${tail.slice(major.toString().length)}`;
    283    },
    284    add_Safari: (ua, config) => {
    285      config.withFirefox = true;
    286      return UAHelpers.safari(config);
    287    },
    288    Safari: (ua, config) => {
    289      return UAHelpers.safari(config);
    290    },
    291    Safari_with_FxQuantum: (ua, config) => {
    292      config.withFxQuantum = true;
    293      return UAHelpers.safari(config);
    294    },
    295  },
    296 
    297  valid_platforms: [
    298    "all",
    299    "android",
    300    "desktop",
    301    "fenix",
    302    "linux",
    303    "mac",
    304    "windows",
    305  ],
    306  valid_channels: ["beta", "esr", "nightly", "stable"],
    307 
    308  shouldSkip(intervention, firefoxVersion, firefoxChannel) {
    309    const {
    310      bug,
    311      max_version,
    312      min_version,
    313      not_channels,
    314      only_channels,
    315      skip_if,
    316      ua_string,
    317    } = intervention;
    318    if (firefoxChannel) {
    319      if (only_channels && !only_channels.includes(firefoxChannel)) {
    320        return true;
    321      }
    322      if (not_channels?.includes(firefoxChannel)) {
    323        return true;
    324      }
    325    }
    326    if (min_version && firefoxVersion < min_version) {
    327      return true;
    328    }
    329    if (max_version) {
    330      // Make sure to handle the case where only the major version matters,
    331      // for instance if we want 138 and the version number is 138.1.
    332      if (String(max_version).includes(".")) {
    333        if (firefoxVersion > max_version) {
    334          return true;
    335        }
    336      } else if (Math.floor(firefoxVersion) > max_version) {
    337        return true;
    338      }
    339    }
    340    if (ua_string) {
    341      for (let ua of Array.isArray(ua_string) ? ua_string : [ua_string]) {
    342        if (!InterventionHelpers.ua_change_functions[ua.change ?? ua]) {
    343          return true;
    344        }
    345      }
    346    }
    347    if (skip_if) {
    348      try {
    349        if (
    350          !this.skip_if_functions[skip_if] ||
    351          this.skip_if_functions[skip_if]?.()
    352        ) {
    353          return true;
    354        }
    355      } catch (e) {
    356        console.trace(
    357          `Error while checking skip-if condition ${skip_if} for bug ${bug}:`,
    358          e
    359        );
    360        return true;
    361      }
    362    }
    363    return false;
    364  },
    365 
    366  nonCustomInterventionKeys: Object.freeze(
    367    new Set([
    368      "content_scripts",
    369      "enabled",
    370      "max_version",
    371      "min_version",
    372      "not_platforms",
    373      "platforms",
    374      "not_channels",
    375      "only_channels",
    376      "pref_check",
    377      "skip_if",
    378      "ua_string",
    379    ])
    380  ),
    381 
    382  isMissingCustomFunctions(intervention, customFunctionNames) {
    383    for (let key of Object.keys(intervention)) {
    384      if (
    385        !InterventionHelpers.nonCustomInterventionKeys.has(key) &&
    386        !customFunctionNames.has(key)
    387      ) {
    388        return true;
    389      }
    390    }
    391    return false;
    392  },
    393 
    394  async getOS() {
    395    const os =
    396      browser.aboutConfigPrefs.getPref("platform_override") ??
    397      (await browser.runtime.getPlatformInfo()).os;
    398    if (os === "win") {
    399      return "windows";
    400    }
    401    return os;
    402  },
    403 
    404  async getPlatformMatches() {
    405    if (!InterventionHelpers._platformMatches) {
    406      const os = await this.getOS();
    407      InterventionHelpers._platformMatches = [
    408        "all",
    409        os,
    410        os == "android" ? "android" : "desktop",
    411      ];
    412      if (os == "android") {
    413        const packageName = await browser.appConstants.getAndroidPackageName();
    414        if (packageName.includes("fenix") || packageName.includes("firefox")) {
    415          InterventionHelpers._platformMatches.push("fenix");
    416        }
    417      }
    418    }
    419    return InterventionHelpers._platformMatches;
    420  },
    421 
    422  async checkPlatformMatches(intervention) {
    423    let desired = intervention.platforms;
    424    let undesired = intervention.not_platforms;
    425    if (!desired && !undesired) {
    426      return true;
    427    }
    428 
    429    const actual = await InterventionHelpers.getPlatformMatches();
    430    if (undesired) {
    431      if (!Array.isArray(undesired)) {
    432        undesired = [undesired];
    433      }
    434      if (
    435        undesired.includes("all") ||
    436        actual.filter(x => undesired.includes(x)).length
    437      ) {
    438        return false;
    439      }
    440    }
    441 
    442    if (!desired) {
    443      return true;
    444    }
    445    if (!Array.isArray(desired)) {
    446      desired = [desired];
    447    }
    448    return (
    449      desired.includes("all") ||
    450      !!actual.filter(x => desired.includes(x)).length
    451    );
    452  },
    453 
    454  applyUAChanges(ua, changes) {
    455    if (!Array.isArray(changes)) {
    456      changes = [changes];
    457    }
    458    for (let config of changes) {
    459      if (typeof config === "string") {
    460        config = { change: config };
    461      }
    462      let finalChanges = config.change;
    463      if (!Array.isArray(finalChanges)) {
    464        finalChanges = [finalChanges];
    465      }
    466      for (const change of finalChanges) {
    467        try {
    468          ua = InterventionHelpers.ua_change_functions[change](ua, config);
    469        } catch (e) {
    470          console.trace(
    471            `Error while calling UA change function ${change} for bug ${config.bug}:`,
    472            e
    473          );
    474          return ua;
    475        }
    476      }
    477    }
    478    return ua;
    479  },
    480 
    481  /**
    482   * Useful helper to generate a list of domains with a fixed base domain and
    483   * multiple country-TLDs or other cases with various TLDs.
    484   *
    485   * Example:
    486   *   matchPatternsForTLDs("*://mozilla.", "/*", ["com", "org"])
    487   *     => ["*://mozilla.com/*", "*://mozilla.org/*"]
    488   */
    489  matchPatternsForTLDs(base, suffix, tlds) {
    490    return tlds.map(tld => base + tld + suffix);
    491  },
    492 
    493  /**
    494   * A modified version of matchPatternsForTLDs that always returns the match
    495   * list for all known Google country TLDs.
    496   */
    497  matchPatternsForGoogle(base, suffix = "/*") {
    498    return InterventionHelpers.matchPatternsForTLDs(base, suffix, GOOGLE_TLDS);
    499  },
    500 };