tor-browser

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

test_BackupService_renderTemplate.js (10044B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 /**
      7 * @typedef {object} TestRenderTemplateResult
      8 * @property {string} markup
      9 *   The rendered template markup as a string.
     10 * @property {Document} backupDOM
     11 *   The rendered template as parsed by a DOMParser.
     12 */
     13 
     14 /**
     15 * Renders a template and returns an object that contains both the raw markup
     16 * and the DOM of the markup as parsed by DOMParser.
     17 *
     18 * @param {boolean} isEncrypted
     19 *   True if the template should report that the backup is encrypted.
     20 * @param {object} metadata
     21 *   The metadata for the backup. See the BackupManifest schema for details.
     22 * @returns {TestRenderTemplateResult}
     23 */
     24 async function testRenderTemplate(isEncrypted, metadata = FAKE_METADATA) {
     25  let bs = new BackupService();
     26  let markup = await bs.renderTemplate(
     27    BackupService.ARCHIVE_TEMPLATE,
     28    isEncrypted,
     29    metadata
     30  );
     31  let backupDOM = new DOMParser().parseFromString(markup, "text/html");
     32  return { backupDOM, markup };
     33 }
     34 
     35 add_setup(() => {
     36  // Setting this pref lets us use Cu.evalInSandbox to run the archive.js
     37  // script.
     38  Services.prefs.setBoolPref(
     39    "security.allow_parent_unrestricted_js_loads",
     40    true
     41  );
     42  registerCleanupFunction(() => {
     43    Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
     44  });
     45 });
     46 
     47 /**
     48 * Tests that header matches our expectations. The header of this file should
     49 * be:
     50 *
     51 * <!DOCTYPE html>
     52 * <!-- Version: 1 -->
     53 */
     54 add_task(async function test_header() {
     55  let { markup } = await testRenderTemplate(false /* isEncrypted */);
     56  const EXPECTED_HEADER =
     57    /^<!DOCTYPE html>[\r\n]+<!-- Version: (\d+) -->[\r\n]+/;
     58  Assert.ok(
     59    markup.match(EXPECTED_HEADER),
     60    "Should have found the expected header."
     61  );
     62 });
     63 
     64 /**
     65 * Tests that the `encryption-state` DOM node says "Not encrypted" if the backup
     66 * is not encrypted, and "Encrypted" otherwise.
     67 */
     68 add_task(async function test_encryption_state() {
     69  let { backupDOM } = await testRenderTemplate(false /* isEncrypted */);
     70  Assert.equal(
     71    backupDOM.querySelector("#encryption-state-value").textContent,
     72    "No"
     73  );
     74 
     75  ({ backupDOM } = await testRenderTemplate(true /* isEncrypted */));
     76  Assert.equal(
     77    backupDOM.querySelector("#encryption-state-value").textContent,
     78    "Yes"
     79  );
     80 });
     81 
     82 /**
     83 * Tests that metadata is properly inserted. The expected metadata inserted on
     84 * the page is the time and date of the backup, as well as the name of the
     85 * machine that the backup was created on.
     86 */
     87 add_task(async function test_metadata() {
     88  let { backupDOM } = await testRenderTemplate(true /* isEncrypted */);
     89  let backupDate = new Date(FAKE_METADATA.date);
     90  let expectedDate = new Intl.DateTimeFormat("en-US", {
     91    dateStyle: "short",
     92  }).format(backupDate);
     93  let expectedTime = new Intl.DateTimeFormat("en-US", {
     94    timeStyle: "short",
     95  }).format(backupDate);
     96  Assert.equal(
     97    backupDOM.querySelector("#creation-date-value").textContent,
     98    `${expectedTime}, ${expectedDate}`
     99  );
    100  Assert.equal(
    101    backupDOM.querySelector("#creation-device-value").textContent,
    102    "A super cool machine"
    103  );
    104 });
    105 
    106 /**
    107 * Tests that metadata is properly escaped. This isn't exhaustive, since we're
    108 * using Fluent under the hood, which is tested pretty widely already.
    109 */
    110 add_task(async function test_hostile_metadata() {
    111  let { backupDOM } = await testRenderTemplate(true /* isEncrypted */, {
    112    date: "<script>alert('test');</script>",
    113    appName: "<script>alert('test');</script>",
    114    appVersion: "<script>alert('test');</script>",
    115    buildID: "<script>alert('test');</script>",
    116    profileName: "<script>alert('test');</script>",
    117    machineName: "<script>alert('test');</script>",
    118    osName: "<script>alert('test');</script>",
    119    osVersion: "<script>alert('test');</script>",
    120    legacyClientID: "<script>alert('test');</script>",
    121    profileGroupID: "<script>alert('test');</script>",
    122    accountID: "<script>alert('test');</script>",
    123    accountEmail: "<script>alert('test');</script>",
    124  });
    125 
    126  let scriptTags = backupDOM.querySelectorAll("script");
    127  Assert.equal(
    128    scriptTags.length,
    129    1,
    130    "There should only be 1 script tag on the page."
    131  );
    132  let scriptContent = scriptTags[0].innerHTML;
    133  let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({}));
    134 
    135  evalSandbox.navigator = {
    136    userAgent: "",
    137  };
    138  evalSandbox.document = {
    139    getElementById: sinon.stub().callsFake((...args) => {
    140      return backupDOM.getElementById(...args);
    141    }),
    142    body: {
    143      toggleAttribute: sinon.stub(),
    144    },
    145    location: {
    146      pathname: "test_archive.html",
    147    },
    148  };
    149  evalSandbox.alert = sinon.stub();
    150 
    151  Cu.evalInSandbox(scriptContent, evalSandbox);
    152 
    153  Assert.ok(evalSandbox.alert.notCalled, "alert() was never called");
    154 });
    155 
    156 add_task(async function test_backup_file_path_from_javascript() {
    157  let { backupDOM } = await testRenderTemplate(false /* isEncrypted */);
    158  let scriptTags = backupDOM.querySelectorAll("script");
    159  Assert.equal(
    160    scriptTags.length,
    161    1,
    162    "There should only be 1 script tag on the page."
    163  );
    164  let scriptContent = scriptTags[0].innerHTML;
    165  let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({}));
    166 
    167  evalSandbox.navigator = {
    168    userAgent:
    169      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:129.0) Gecko/20100101 Firefox/129.0",
    170  };
    171  evalSandbox.document = {
    172    getElementById: sinon.stub().callsFake((...args) => {
    173      return backupDOM.getElementById(...args);
    174    }),
    175    body: {
    176      toggleAttribute: sinon.stub(),
    177    },
    178    location: {
    179      pathname: "test_archive.html",
    180    },
    181  };
    182 
    183  Cu.evalInSandbox(scriptContent, evalSandbox);
    184 
    185  Assert.equal(
    186    backupDOM.querySelector("#backup-file-path-value").textContent,
    187    "test_archive.html",
    188    "backup file path should have been sourced from document.location"
    189  );
    190 });
    191 
    192 /**
    193 * Tests that if the User Agent is a browser that includes "Firefox", that
    194 * toggleAttribute("is-moz-browser", true) is called on document.body.
    195 */
    196 add_task(async function test_moz_browser_handling() {
    197  let { backupDOM } = await testRenderTemplate(false /* isEncrypted */);
    198  let scriptTags = backupDOM.querySelectorAll("script");
    199  Assert.equal(
    200    scriptTags.length,
    201    1,
    202    "There should only be 1 script tag on the page."
    203  );
    204  let scriptContent = scriptTags[0].innerHTML;
    205  let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({}));
    206 
    207  evalSandbox.navigator = {
    208    userAgent:
    209      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:129.0) Gecko/20100101 Firefox/129.0",
    210  };
    211  evalSandbox.document = {
    212    getElementById: sinon.stub().callsFake((...args) => {
    213      return backupDOM.getElementById(...args);
    214    }),
    215    body: {
    216      toggleAttribute: sinon.stub(),
    217    },
    218    location: {
    219      pathname: "test_archive.html",
    220    },
    221  };
    222 
    223  Cu.evalInSandbox(scriptContent, evalSandbox);
    224 
    225  Assert.ok(
    226    evalSandbox.document.body.toggleAttribute.calledOnce,
    227    "document.body.toggleAttribute called"
    228  );
    229  Assert.ok(
    230    evalSandbox.document.body.toggleAttribute.calledWith(
    231      "is-moz-browser",
    232      true
    233    ),
    234    "document.body.toggleAttribute called setting is-moz-browser to true"
    235  );
    236 });
    237 
    238 /**
    239 * Tests that if the User Agent is a browser that does not include "Firefox",
    240 * that toggleAttribute("is-moz-browser", false) is called on document.body.
    241 */
    242 add_task(async function test_non_moz_browser_handling() {
    243  let { backupDOM } = await testRenderTemplate(true /* isEncrypted */);
    244  let scriptTags = backupDOM.querySelectorAll("script");
    245  Assert.equal(
    246    scriptTags.length,
    247    1,
    248    "There should only be 1 script tag on the page."
    249  );
    250  let scriptContent = scriptTags[0].innerHTML;
    251  let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({}));
    252 
    253  evalSandbox.navigator = {
    254    userAgent:
    255      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15",
    256  };
    257  evalSandbox.document = {
    258    getElementById: sinon.stub().callsFake((...args) => {
    259      return backupDOM.getElementById(...args);
    260    }),
    261    body: {
    262      toggleAttribute: sinon.stub(),
    263    },
    264    location: {
    265      pathname: "test_archive.html",
    266    },
    267  };
    268 
    269  Cu.evalInSandbox(scriptContent, evalSandbox);
    270 
    271  Assert.ok(
    272    evalSandbox.document.body.toggleAttribute.calledOnce,
    273    "document.body.toggleAttribute called"
    274  );
    275  Assert.ok(
    276    evalSandbox.document.body.toggleAttribute.calledWith(
    277      "is-moz-browser",
    278      false
    279    ),
    280    "document.body.toggleAttribute called setting is-moz-browser to false"
    281  );
    282 });
    283 
    284 /**
    285 * Tests that the license header does not exist in the generated rendering.
    286 */
    287 add_task(async function test_no_license() {
    288  let { markup } = await testRenderTemplate(true /* isEncrypted */);
    289 
    290  // Instead of looking for the exact license header (which might be indented)
    291  // in such a way as to make string-searching brittle) we'll just look for
    292  // a key part of the license header, which is the reference to
    293  // https://mozilla.org/MPL.
    294 
    295  Assert.ok(
    296    !markup.includes("https://mozilla.org/MPL"),
    297    "The license headers were stripped."
    298  );
    299 });
    300 
    301 /**
    302 * Tests that the "Learn More" support link/SUMO link has the expected UTM
    303 * parameters.
    304 */
    305 add_task(async function test_support_link_utm_parameters() {
    306  let { backupDOM } = await testRenderTemplate(false /* isEncrypted */);
    307 
    308  let supportLinkElement = backupDOM.getElementById("support-link");
    309  Assert.ok(supportLinkElement, "support link should be found");
    310  Assert.ok(
    311    supportLinkElement.href,
    312    "support link should have a non-empty href"
    313  );
    314 
    315  let supportLinkUrl = new URL(supportLinkElement.href);
    316  let { searchParams } = supportLinkUrl;
    317  Assert.equal(
    318    searchParams.get("utm_medium"),
    319    "firefox-desktop",
    320    "utm_medium should be firefox-desktop"
    321  );
    322  Assert.equal(
    323    searchParams.get("utm_source"),
    324    "html-backup",
    325    "utm_source should be html-backup"
    326  );
    327  Assert.equal(
    328    searchParams.get("utm_campaign"),
    329    "fx-backup-restore",
    330    "utm_campaign should be fx-backup-restore"
    331  );
    332 });