tor-browser

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

browser_storage_listings.js (20268B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 Services.scriptloader.loadSubScript(
      7  "chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js",
      8  this
      9 );
     10 
     11 const l10n = new Localization(["devtools/client/storage.ftl"], true);
     12 const sessionString = l10n.formatValueSync("storage-expires-session");
     13 
     14 const storeMap = {
     15  cookies: {
     16    "http://test1.example.org": [
     17      {
     18        name: "c1",
     19        value: "foobar",
     20        expires: 2000000000000,
     21        path: "/browser",
     22        host: "test1.example.org",
     23        hostOnly: true,
     24        isSecure: false,
     25      },
     26      {
     27        name: "cs2",
     28        value: "sessionCookie",
     29        path: "/",
     30        host: ".example.org",
     31        expires: 0,
     32        hostOnly: false,
     33        isSecure: false,
     34      },
     35      {
     36        name: "c3",
     37        value: "foobar-2",
     38        expires: 2000000001000,
     39        path: "/",
     40        host: "test1.example.org",
     41        hostOnly: true,
     42        isSecure: true,
     43      },
     44    ],
     45 
     46    "http://sectest1.example.org": [
     47      {
     48        name: "cs2",
     49        value: "sessionCookie",
     50        path: "/",
     51        host: ".example.org",
     52        expires: 0,
     53        hostOnly: false,
     54        isSecure: false,
     55      },
     56      {
     57        name: "sc1",
     58        value: "foobar",
     59        path: "/browser/devtools/server/tests/browser",
     60        host: "sectest1.example.org",
     61        expires: 0,
     62        hostOnly: true,
     63        isSecure: false,
     64      },
     65    ],
     66 
     67    "https://sectest1.example.org": [
     68      {
     69        name: "uc1",
     70        value: "foobar",
     71        host: ".example.org",
     72        path: "/",
     73        expires: 0,
     74        hostOnly: false,
     75        isSecure: true,
     76      },
     77      {
     78        name: "cs2",
     79        value: "sessionCookie",
     80        path: "/",
     81        host: ".example.org",
     82        expires: 0,
     83        hostOnly: false,
     84        isSecure: false,
     85      },
     86      {
     87        name: "sc1",
     88        value: "foobar",
     89        path: "/browser/devtools/server/tests/browser",
     90        host: "sectest1.example.org",
     91        expires: 0,
     92        hostOnly: true,
     93        isSecure: false,
     94      },
     95    ],
     96  },
     97  "local-storage": {
     98    "http://test1.example.org": [
     99      {
    100        name: "ls1",
    101        value: "foobar",
    102      },
    103      {
    104        name: "ls2",
    105        value: "foobar-2",
    106      },
    107    ],
    108    "http://sectest1.example.org": [
    109      {
    110        name: "iframe-u-ls1",
    111        value: "foobar",
    112      },
    113    ],
    114    "https://sectest1.example.org": [
    115      {
    116        name: "iframe-s-ls1",
    117        value: "foobar",
    118      },
    119    ],
    120  },
    121  "session-storage": {
    122    "http://test1.example.org": [
    123      {
    124        name: "ss1",
    125        value: "foobar-3",
    126      },
    127    ],
    128    "http://sectest1.example.org": [
    129      {
    130        name: "iframe-u-ss1",
    131        value: "foobar1",
    132      },
    133      {
    134        name: "iframe-u-ss2",
    135        value: "foobar2",
    136      },
    137    ],
    138    "https://sectest1.example.org": [
    139      {
    140        name: "iframe-s-ss1",
    141        value: "foobar-2",
    142      },
    143    ],
    144  },
    145 };
    146 
    147 const IDBValues = {
    148  listStoresResponse: {
    149    "http://test1.example.org": [
    150      ["idb1 (default)", "obj1"],
    151      ["idb1 (default)", "obj2"],
    152      ["idb2 (default)", "obj3"],
    153    ],
    154    "http://sectest1.example.org": [],
    155    "https://sectest1.example.org": [
    156      ["idb-s1 (default)", "obj-s1"],
    157      ["idb-s2 (default)", "obj-s2"],
    158    ],
    159  },
    160  dbDetails: {
    161    "http://test1.example.org": [
    162      {
    163        db: "idb1 (default)",
    164        origin: "http://test1.example.org",
    165        version: 1,
    166        objectStores: 2,
    167      },
    168      {
    169        db: "idb2 (default)",
    170        origin: "http://test1.example.org",
    171        version: 1,
    172        objectStores: 1,
    173      },
    174    ],
    175    "http://sectest1.example.org": [],
    176    "https://sectest1.example.org": [
    177      {
    178        db: "idb-s1 (default)",
    179        origin: "https://sectest1.example.org",
    180        version: 1,
    181        objectStores: 1,
    182      },
    183      {
    184        db: "idb-s2 (default)",
    185        origin: "https://sectest1.example.org",
    186        version: 1,
    187        objectStores: 1,
    188      },
    189    ],
    190  },
    191  objectStoreDetails: {
    192    "http://test1.example.org": {
    193      "idb1 (default)": [
    194        {
    195          objectStore: "obj1",
    196          keyPath: "id",
    197          autoIncrement: false,
    198          indexes: [
    199            {
    200              name: "name",
    201              keyPath: "name",
    202              unique: false,
    203              multiEntry: false,
    204            },
    205            {
    206              name: "email",
    207              keyPath: "email",
    208              unique: true,
    209              multiEntry: false,
    210            },
    211          ],
    212        },
    213        {
    214          objectStore: "obj2",
    215          keyPath: "id2",
    216          autoIncrement: false,
    217          indexes: [],
    218        },
    219      ],
    220      "idb2 (default)": [
    221        {
    222          objectStore: "obj3",
    223          keyPath: "id3",
    224          autoIncrement: false,
    225          indexes: [
    226            {
    227              name: "name2",
    228              keyPath: "name2",
    229              unique: true,
    230              multiEntry: false,
    231            },
    232          ],
    233        },
    234      ],
    235    },
    236    "http://sectest1.example.org": {},
    237    "https://sectest1.example.org": {
    238      "idb-s1 (default)": [
    239        {
    240          objectStore: "obj-s1",
    241          keyPath: "id",
    242          autoIncrement: false,
    243          indexes: [],
    244        },
    245      ],
    246      "idb-s2 (default)": [
    247        {
    248          objectStore: "obj-s2",
    249          keyPath: "id3",
    250          autoIncrement: true,
    251          indexes: [
    252            {
    253              name: "name2",
    254              keyPath: "name2",
    255              unique: true,
    256              multiEntry: false,
    257            },
    258          ],
    259        },
    260      ],
    261    },
    262  },
    263  entries: {
    264    "http://test1.example.org": {
    265      "idb1 (default)#obj1": [
    266        {
    267          name: 1,
    268          value: {
    269            id: 1,
    270            name: "foo",
    271            email: "foo@bar.com",
    272          },
    273        },
    274        {
    275          name: 2,
    276          value: {
    277            id: 2,
    278            name: "foo2",
    279            email: "foo2@bar.com",
    280          },
    281        },
    282        {
    283          name: 3,
    284          value: {
    285            id: 3,
    286            name: "foo2",
    287            email: "foo3@bar.com",
    288          },
    289        },
    290      ],
    291      "idb1 (default)#obj2": [
    292        {
    293          name: 1,
    294          value: {
    295            id2: 1,
    296            name: "foo",
    297            email: "foo@bar.com",
    298            extra: "baz",
    299          },
    300        },
    301      ],
    302      "idb2 (default)#obj3": [],
    303    },
    304    "http://sectest1.example.org": {},
    305    "https://sectest1.example.org": {
    306      "idb-s1 (default)#obj-s1": [
    307        {
    308          name: 6,
    309          value: {
    310            id: 6,
    311            name: "foo",
    312            email: "foo@bar.com",
    313          },
    314        },
    315        {
    316          name: 7,
    317          value: {
    318            id: 7,
    319            name: "foo2",
    320            email: "foo2@bar.com",
    321          },
    322        },
    323      ],
    324      "idb-s2 (default)#obj-s2": [
    325        {
    326          name: 13,
    327          value: {
    328            id2: 13,
    329            name2: "foo",
    330            email: "foo@bar.com",
    331          },
    332        },
    333      ],
    334    },
    335  },
    336 };
    337 
    338 async function testStores(commands) {
    339  const { resourceCommand } = commands;
    340  const { TYPES } = resourceCommand;
    341  /**
    342   * Data is a dictionary whose keys are storage types (their resourceType)
    343   * while values are objects with following attributes:
    344   * - hosts: dictionary of storage values (values are specific to each storage type)
    345   *          keyed by host names.
    346   * - dataByHost: dictionary of storage objects keyed by host names.
    347   *               storages objects are returned by StorageActor.getStoreObjects.
    348   *               For IndexedDB it is different, instead it is still a dictionary
    349   *               keyed by host names, but each value is yet another sub dictionary with
    350   *               a special "main" attribute, with global store objects.
    351   *               Then, there will be one key per idb database, with their store objects
    352   *               as value.
    353   */
    354  const data = {};
    355  await resourceCommand.watchResources(
    356    [
    357      TYPES.COOKIE,
    358      TYPES.LOCAL_STORAGE,
    359      TYPES.SESSION_STORAGE,
    360      TYPES.INDEXED_DB,
    361    ],
    362    {
    363      async onAvailable(resources) {
    364        for (const resource of resources) {
    365          const { resourceType } = resource;
    366          if (!data[resourceType]) {
    367            data[resourceType] = { hosts: {}, dataByHost: {} };
    368          }
    369 
    370          for (const host in resource.hosts) {
    371            if (!data[resourceType].hosts[host]) {
    372              data[resourceType].hosts[host] = [];
    373            }
    374            // For indexed DB, we have some values, the database names. Other are empty arrays.
    375            const hostValues = resource.hosts[host];
    376            data[resourceType].hosts[host].push(...hostValues);
    377 
    378            // For INDEXED_DB, it is slightly more complex, as we may have 3 store per host,
    379            if (resourceType == TYPES.INDEXED_DB) {
    380              if (!data[resourceType].dataByHost[host]) {
    381                data[resourceType].dataByHost[host] = {};
    382              }
    383              data[resourceType].dataByHost[host].main =
    384                await resource.getStoreObjects(host, null, {
    385                  sessionString,
    386                });
    387              for (const name of resource.hosts[host]) {
    388                const objName = JSON.parse(name).slice(0, 1);
    389                data[resourceType].dataByHost[host][objName] =
    390                  await resource.getStoreObjects(
    391                    host,
    392                    [JSON.stringify(objName)],
    393                    { sessionString }
    394                  );
    395                data[resourceType].dataByHost[host][name] =
    396                  await resource.getStoreObjects(host, [name], {
    397                    sessionString,
    398                  });
    399              }
    400            } else {
    401              data[resourceType].dataByHost[host] =
    402                await resource.getStoreObjects(host, null, { sessionString });
    403            }
    404          }
    405        }
    406      },
    407    }
    408  );
    409 
    410  await testCookies(data.cookies);
    411  await testLocalStorage(data["local-storage"]);
    412  await testSessionStorage(data["session-storage"]);
    413  await testIndexedDB(data["indexed-db"]);
    414 }
    415 
    416 function testCookies({ hosts, dataByHost }) {
    417  is(
    418    Object.keys(hosts).length,
    419    3,
    420    "Correct number of host entries for cookies"
    421  );
    422  return testCookiesObjects(0, hosts, dataByHost);
    423 }
    424 
    425 async function testCookiesObjects(index, hosts, dataByHost) {
    426  const host = Object.keys(hosts)[index];
    427  ok(!!storeMap.cookies[host], "Host is present in the list : " + host);
    428  const data = dataByHost[host];
    429  let cookiesLength = 0;
    430  for (const secureCookie of storeMap.cookies[host]) {
    431    if (secureCookie.isSecure) {
    432      ++cookiesLength;
    433    }
    434  }
    435  // Any secure cookies did not get stored in the database.
    436  is(
    437    data.total,
    438    storeMap.cookies[host].length - cookiesLength,
    439    "Number of cookies in host " + host + " matches"
    440  );
    441  for (const item of data.data) {
    442    let found = false;
    443    for (const toMatch of storeMap.cookies[host]) {
    444      if (item.name == toMatch.name) {
    445        found = true;
    446        ok(true, "Found cookie " + item.name + " in response");
    447        is(item.value.str, toMatch.value, "The value matches.");
    448        is(item.expires, toMatch.expires, "The expiry time matches.");
    449        is(item.path, toMatch.path, "The path matches.");
    450        is(item.host, toMatch.host, "The host matches.");
    451        is(item.isSecure, toMatch.isSecure, "The isSecure value matches.");
    452        is(item.hostOnly, toMatch.hostOnly, "The hostOnly value matches.");
    453        break;
    454      }
    455    }
    456    ok(found, "cookie " + item.name + " should exist in response");
    457  }
    458 
    459  if (index == Object.keys(hosts).length - 1) {
    460    return;
    461  }
    462  await testCookiesObjects(++index, hosts, dataByHost);
    463 }
    464 
    465 function testLocalStorage({ hosts, dataByHost }) {
    466  is(
    467    Object.keys(hosts).length,
    468    3,
    469    "Correct number of host entries for local storage"
    470  );
    471  return testLocalStorageObjects(0, hosts, dataByHost);
    472 }
    473 
    474 var testLocalStorageObjects = async function (index, hosts, dataByHost) {
    475  const host = Object.keys(hosts)[index];
    476  ok(
    477    !!storeMap["local-storage"][host],
    478    "Host is present in the list : " + host
    479  );
    480  const data = dataByHost[host];
    481  is(
    482    data.total,
    483    storeMap["local-storage"][host].length,
    484    "Number of local storage items in host " + host + " matches"
    485  );
    486  for (const item of data.data) {
    487    let found = false;
    488    for (const toMatch of storeMap["local-storage"][host]) {
    489      if (item.name == toMatch.name) {
    490        found = true;
    491        ok(true, "Found local storage item " + item.name + " in response");
    492        is(item.value.str, toMatch.value, "The value matches.");
    493        break;
    494      }
    495    }
    496    ok(found, "local storage item " + item.name + " should exist in response");
    497  }
    498 
    499  if (index == Object.keys(hosts).length - 1) {
    500    return;
    501  }
    502  await testLocalStorageObjects(++index, hosts, dataByHost);
    503 };
    504 
    505 function testSessionStorage({ hosts, dataByHost }) {
    506  is(
    507    Object.keys(hosts).length,
    508    3,
    509    "Correct number of host entries for session storage"
    510  );
    511  return testSessionStorageObjects(0, hosts, dataByHost);
    512 }
    513 
    514 async function testSessionStorageObjects(index, hosts, dataByHost) {
    515  const host = Object.keys(hosts)[index];
    516  ok(
    517    !!storeMap["session-storage"][host],
    518    "Host is present in the list : " + host
    519  );
    520  const data = dataByHost[host];
    521  is(
    522    data.total,
    523    storeMap["session-storage"][host].length,
    524    "Number of session storage items in host " + host + " matches"
    525  );
    526  for (const item of data.data) {
    527    let found = false;
    528    for (const toMatch of storeMap["session-storage"][host]) {
    529      if (item.name == toMatch.name) {
    530        found = true;
    531        ok(true, "Found session storage item " + item.name + " in response");
    532        is(item.value.str, toMatch.value, "The value matches.");
    533        break;
    534      }
    535    }
    536    ok(
    537      found,
    538      "session storage item " + item.name + " should exist in response"
    539    );
    540  }
    541 
    542  if (index == Object.keys(hosts).length - 1) {
    543    return;
    544  }
    545  await testSessionStorageObjects(++index, hosts, dataByHost);
    546 }
    547 
    548 async function testIndexedDB({ hosts, dataByHost }) {
    549  is(
    550    Object.keys(hosts).length,
    551    3,
    552    "Correct number of host entries for indexed db"
    553  );
    554 
    555  for (const host in hosts) {
    556    for (const item of hosts[host]) {
    557      const parsedItem = JSON.parse(item);
    558      let found = false;
    559      for (const toMatch of IDBValues.listStoresResponse[host]) {
    560        if (toMatch[0] == parsedItem[0] && toMatch[1] == parsedItem[1]) {
    561          found = true;
    562          break;
    563        }
    564      }
    565      ok(found, item + " should exist in list stores response");
    566    }
    567  }
    568 
    569  await testIndexedDBs(0, hosts, dataByHost);
    570  await testObjectStores(0, hosts, dataByHost);
    571  await testIDBEntries(0, hosts, dataByHost);
    572 }
    573 
    574 async function testIndexedDBs(index, hosts, dataByHost) {
    575  const host = Object.keys(hosts)[index];
    576  const data = dataByHost[host].main;
    577  is(
    578    data.total,
    579    IDBValues.dbDetails[host].length,
    580    "Number of indexed db in host " + host + " matches"
    581  );
    582  for (const item of data.data) {
    583    let found = false;
    584    for (const toMatch of IDBValues.dbDetails[host]) {
    585      if (item.uniqueKey == toMatch.db) {
    586        found = true;
    587        ok(true, "Found indexed db " + item.uniqueKey + " in response");
    588        is(item.origin, toMatch.origin, "The origin matches.");
    589        is(item.version, toMatch.version, "The version matches.");
    590        is(
    591          item.objectStores,
    592          toMatch.objectStores,
    593          "The number of object stores matches."
    594        );
    595        break;
    596      }
    597    }
    598    ok(found, "indexed db " + item.uniqueKey + " should exist in response");
    599  }
    600 
    601  ok(!!IDBValues.dbDetails[host], "Host is present in the list : " + host);
    602  if (index == Object.keys(hosts).length - 1) {
    603    return;
    604  }
    605  await testIndexedDBs(++index, hosts, dataByHost);
    606 }
    607 
    608 async function testObjectStores(ix, hosts, dataByHost) {
    609  const host = Object.keys(hosts)[ix];
    610  const matchItems = (data, db) => {
    611    is(
    612      data.total,
    613      IDBValues.objectStoreDetails[host][db].length,
    614      "Number of object stores in host " + host + " matches"
    615    );
    616    for (const item of data.data) {
    617      let found = false;
    618      for (const toMatch of IDBValues.objectStoreDetails[host][db]) {
    619        if (item.objectStore == toMatch.objectStore) {
    620          found = true;
    621          ok(true, "Found object store " + item.objectStore + " in response");
    622          is(item.keyPath, toMatch.keyPath, "The keyPath matches.");
    623          is(
    624            item.autoIncrement,
    625            toMatch.autoIncrement,
    626            "The autoIncrement matches."
    627          );
    628          // We might already have parsed the JSON value, in which case this will no longer be a string
    629          item.indexes =
    630            typeof item.indexes == "string"
    631              ? JSON.parse(item.indexes)
    632              : item.indexes;
    633          is(
    634            item.indexes.length,
    635            toMatch.indexes.length,
    636            "Number of indexes match"
    637          );
    638          for (const index of item.indexes) {
    639            let indexFound = false;
    640            for (const toMatchIndex of toMatch.indexes) {
    641              if (toMatchIndex.name == index.name) {
    642                indexFound = true;
    643                ok(true, "Found index " + index.name);
    644                is(
    645                  index.keyPath,
    646                  toMatchIndex.keyPath,
    647                  "The keyPath of index matches."
    648                );
    649                is(index.unique, toMatchIndex.unique, "The unique matches");
    650                is(
    651                  index.multiEntry,
    652                  toMatchIndex.multiEntry,
    653                  "The multiEntry matches"
    654                );
    655                break;
    656              }
    657            }
    658            ok(indexFound, "Index " + index + " should exist in response");
    659          }
    660          break;
    661        }
    662      }
    663      ok(found, "indexed db " + item.name + " should exist in response");
    664    }
    665  };
    666 
    667  ok(
    668    !!IDBValues.objectStoreDetails[host],
    669    "Host is present in the list : " + host
    670  );
    671  for (const name of hosts[host]) {
    672    const objName = JSON.parse(name).slice(0, 1);
    673    matchItems(dataByHost[host][objName], objName[0]);
    674  }
    675  if (ix == Object.keys(hosts).length - 1) {
    676    return;
    677  }
    678  await testObjectStores(++ix, hosts, dataByHost);
    679 }
    680 
    681 async function testIDBEntries(index, hosts, dataByHost) {
    682  const host = Object.keys(hosts)[index];
    683  const matchItems = (data, obj) => {
    684    is(
    685      data.total,
    686      IDBValues.entries[host][obj].length,
    687      "Number of items in object store " + obj + " matches"
    688    );
    689    for (const item of data.data) {
    690      let found = false;
    691      for (const toMatch of IDBValues.entries[host][obj]) {
    692        if (item.name == toMatch.name) {
    693          found = true;
    694          ok(true, "Found indexed db item " + item.name + " in response");
    695          const value = JSON.parse(item.value.str);
    696          is(
    697            Object.keys(value).length,
    698            Object.keys(toMatch.value).length,
    699            "Number of entries in the value matches"
    700          );
    701          for (const key in value) {
    702            is(
    703              value[key],
    704              toMatch.value[key],
    705              "value of " + key + " value key matches"
    706            );
    707          }
    708          break;
    709        }
    710      }
    711      ok(found, "indexed db item " + item.name + " should exist in response");
    712    }
    713  };
    714 
    715  ok(!!IDBValues.entries[host], "Host is present in the list : " + host);
    716  for (const name of hosts[host]) {
    717    const parsed = JSON.parse(name);
    718    matchItems(dataByHost[host][name], parsed[0] + "#" + parsed[1]);
    719  }
    720  if (index == Object.keys(hosts).length - 1) {
    721    return;
    722  }
    723  await testObjectStores(++index, hosts, dataByHost);
    724 }
    725 
    726 add_task(async function () {
    727  await SpecialPowers.pushPrefEnv({
    728    set: [["network.cookie.maxageCap", 0]],
    729  });
    730 
    731  const { commands } = await openTabAndSetupStorage(
    732    MAIN_DOMAIN + "storage-listings.html"
    733  );
    734 
    735  await testStores(commands);
    736 
    737  await clearStorage();
    738 
    739  // Forcing GC/CC to get rid of docshells and windows created by this test.
    740  forceCollections();
    741  await commands.destroy();
    742  forceCollections();
    743 });