tor-browser

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

background.js (4908B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /* global browser */
      8 
      9 // Telemetry values
     10 const TELEMETRY_VALUE_EXTENSION = "extension";
     11 const TELEMETRY_VALUE_SERVER = "server";
     12 
     13 class AddonsSearchDetection {
     14  constructor() {
     15    /** @type {{baseUrl: string, addonId?: string, paramName?: string}[]} */
     16    this.engines = [];
     17 
     18    this.onRedirectedListener = this.onRedirectedListener.bind(this);
     19  }
     20 
     21  async getEngines() {
     22    try {
     23      this.engines = await browser.addonsSearchDetection.getEngines();
     24    } catch (err) {
     25      console.error(`failed to retrieve the list of URL patterns: ${err}`);
     26      this.engines = [];
     27    }
     28 
     29    return this.engines;
     30  }
     31 
     32  // When the search service changes the set of engines that are enabled, we
     33  // update our pattern matching in the webrequest listeners (go to the bottom
     34  // of this file for the search service events we listen to).
     35  async monitor() {
     36    // If there is already a listener, remove it so that we can re-add one
     37    // after. This is because we're using the same listener with different URL
     38    // patterns (when the list of search engines changes).
     39    if (
     40      browser.addonsSearchDetection.onRedirected.hasListener(
     41        this.onRedirectedListener
     42      )
     43    ) {
     44      browser.addonsSearchDetection.onRedirected.removeListener(
     45        this.onRedirectedListener
     46      );
     47    }
     48 
     49    // Retrieve the list of URL patterns to monitor with our listener.
     50    //
     51    // Note: search suggestions are system principal requests, so webRequest
     52    // cannot intercept them.
     53    const engines = await this.getEngines();
     54    const patterns = new Set(engines.map(e => e.baseUrl + "*"));
     55 
     56    if (patterns.size === 0) {
     57      return;
     58    }
     59 
     60    browser.addonsSearchDetection.onRedirected.addListener(
     61      this.onRedirectedListener,
     62      { urls: [...patterns] }
     63    );
     64  }
     65 
     66  async onRedirectedListener({ addonId, firstUrl, lastUrl }) {
     67    // When we do not have an add-on ID (in the request property bag), we
     68    // likely detected a search server-side redirect.
     69    const maybeServerSideRedirect = !addonId;
     70 
     71    // All engines that match the initial url.
     72    let engines = this.getEnginesForUrl(firstUrl);
     73 
     74    let addonIds = [];
     75    // Search server-side redirects are possible because an extension has
     76    // registered a search engine, which is why we can (hopefully) retrieve the
     77    // add-on ID.
     78    if (maybeServerSideRedirect) {
     79      addonIds = engines.filter(e => e.addonId).map(e => e.addonId);
     80    } else if (addonId) {
     81      addonIds = [addonId];
     82    }
     83 
     84    if (addonIds.length === 0) {
     85      // No add-on ID means there is nothing we can report.
     86      return;
     87    }
     88 
     89    // This is the monitored URL that was first redirected.
     90    const from = await browser.addonsSearchDetection.getPublicSuffix(firstUrl);
     91    // This is the final URL after redirect(s).
     92    const to = await browser.addonsSearchDetection.getPublicSuffix(lastUrl);
     93 
     94    let sameSite = from === to;
     95    let paramChanged = false;
     96    if (sameSite) {
     97      // We report redirects within the same site separately.
     98 
     99      // Known limitation: if a redirect chain starts and ends with the same
    100      // public suffix, it will still get reported as a same_site_redirect,
    101      // even if the chain contains different public suffixes in between.
    102 
    103      // Need special logic to detect changes to the query param named in `paramName`.
    104      let firstParams = new URLSearchParams(new URL(firstUrl).search);
    105      let lastParams = new URLSearchParams(new URL(lastUrl).search);
    106      for (let { paramName } of engines.filter(e => e.paramName)) {
    107        if (firstParams.get(paramName) !== lastParams.get(paramName)) {
    108          paramChanged = true;
    109          break;
    110        }
    111      }
    112    }
    113 
    114    for (const id of addonIds) {
    115      const addonVersion =
    116        await browser.addonsSearchDetection.getAddonVersion(id);
    117      const extra = {
    118        addonId: id,
    119        addonVersion,
    120        from,
    121        to,
    122        value: maybeServerSideRedirect
    123          ? TELEMETRY_VALUE_SERVER
    124          : TELEMETRY_VALUE_EXTENSION,
    125      };
    126      if (sameSite) {
    127        browser.addonsSearchDetection.reportSameSiteRedirect({
    128          addonId: id,
    129          addonVersion,
    130          origin: from,
    131          paramChanged,
    132        });
    133      } else if (maybeServerSideRedirect) {
    134        browser.addonsSearchDetection.reportETLDChangeOther(extra);
    135      } else {
    136        browser.addonsSearchDetection.reportETLDChangeWebrequest(extra);
    137      }
    138    }
    139  }
    140 
    141  getEnginesForUrl(url) {
    142    return this.engines.filter(e => url.startsWith(e.baseUrl));
    143  }
    144 }
    145 
    146 const exp = new AddonsSearchDetection();
    147 exp.monitor();
    148 
    149 browser.addonsSearchDetection.onSearchEngineModified.addListener(async () => {
    150  await exp.monitor();
    151 });