tor-browser

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

forms.sys.mjs (7381B)


      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 import {
      6  Store,
      7  SyncEngine,
      8  LegacyTracker,
      9 } from "resource://services-sync/engines.sys.mjs";
     10 
     11 import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
     12 import { Svc, Utils } from "resource://services-sync/util.sys.mjs";
     13 
     14 import { SCORE_INCREMENT_MEDIUM } from "resource://services-sync/constants.sys.mjs";
     15 import {
     16  CollectionProblemData,
     17  CollectionValidator,
     18 } from "resource://services-sync/collection_validator.sys.mjs";
     19 
     20 import { Async } from "resource://services-common/async.sys.mjs";
     21 import { Log } from "resource://gre/modules/Log.sys.mjs";
     22 
     23 const lazy = {};
     24 ChromeUtils.defineESModuleGetters(lazy, {
     25  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
     26 });
     27 
     28 const FORMS_TTL = 3 * 365 * 24 * 60 * 60; // Three years in seconds.
     29 
     30 export function FormRec(collection, id) {
     31  CryptoWrapper.call(this, collection, id);
     32 }
     33 
     34 FormRec.prototype = {
     35  _logName: "Sync.Record.Form",
     36  ttl: FORMS_TTL,
     37 };
     38 Object.setPrototypeOf(FormRec.prototype, CryptoWrapper.prototype);
     39 
     40 Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);
     41 
     42 var FormWrapper = {
     43  _log: Log.repository.getLogger("Sync.Engine.Forms"),
     44 
     45  _getEntryCols: ["fieldname", "value"],
     46  _guidCols: ["guid"],
     47 
     48  _search(terms, searchData) {
     49    return lazy.FormHistory.search(terms, searchData);
     50  },
     51 
     52  async _update(changes) {
     53    if (!lazy.FormHistory.enabled) {
     54      return; // update isn't going to do anything.
     55    }
     56    await lazy.FormHistory.update(changes).catch(console.error);
     57  },
     58 
     59  async getEntry(guid) {
     60    let results = await this._search(this._getEntryCols, { guid });
     61    if (!results.length) {
     62      return null;
     63    }
     64    return { name: results[0].fieldname, value: results[0].value };
     65  },
     66 
     67  async getGUID(name, value) {
     68    // Query for the provided entry.
     69    let query = { fieldname: name, value };
     70    let results = await this._search(this._guidCols, query);
     71    return results.length ? results[0].guid : null;
     72  },
     73 
     74  async hasGUID(guid) {
     75    // We could probably use a count function here, but search exists...
     76    let results = await this._search(this._guidCols, { guid });
     77    return !!results.length;
     78  },
     79 
     80  async replaceGUID(oldGUID, newGUID) {
     81    let changes = {
     82      op: "update",
     83      guid: oldGUID,
     84      newGuid: newGUID,
     85    };
     86    await this._update(changes);
     87  },
     88 };
     89 
     90 export function FormEngine(service) {
     91  SyncEngine.call(this, "Forms", service);
     92 }
     93 
     94 FormEngine.prototype = {
     95  _storeObj: FormStore,
     96  _trackerObj: FormTracker,
     97  _recordObj: FormRec,
     98 
     99  syncPriority: 6,
    100 
    101  get prefName() {
    102    return "history";
    103  },
    104 
    105  async _findDupe(item) {
    106    return FormWrapper.getGUID(item.name, item.value);
    107  },
    108 };
    109 Object.setPrototypeOf(FormEngine.prototype, SyncEngine.prototype);
    110 
    111 function FormStore(name, engine) {
    112  Store.call(this, name, engine);
    113 }
    114 FormStore.prototype = {
    115  async _processChange(change) {
    116    // If this._changes is defined, then we are applying a batch, so we
    117    // can defer it.
    118    if (this._changes) {
    119      this._changes.push(change);
    120      return;
    121    }
    122 
    123    // Otherwise we must handle the change right now.
    124    await FormWrapper._update(change);
    125  },
    126 
    127  async applyIncomingBatch(records, countTelemetry) {
    128    Async.checkAppReady();
    129    // We collect all the changes to be made then apply them all at once.
    130    this._changes = [];
    131    let failures = await Store.prototype.applyIncomingBatch.call(
    132      this,
    133      records,
    134      countTelemetry
    135    );
    136    if (this._changes.length) {
    137      await FormWrapper._update(this._changes);
    138    }
    139    delete this._changes;
    140    return failures;
    141  },
    142 
    143  async getAllIDs() {
    144    let results = await FormWrapper._search(["guid"], []);
    145    let guids = {};
    146    for (let result of results) {
    147      guids[result.guid] = true;
    148    }
    149    return guids;
    150  },
    151 
    152  async changeItemID(oldID, newID) {
    153    await FormWrapper.replaceGUID(oldID, newID);
    154  },
    155 
    156  async itemExists(id) {
    157    return FormWrapper.hasGUID(id);
    158  },
    159 
    160  async createRecord(id, collection) {
    161    let record = new FormRec(collection, id);
    162    let entry = await FormWrapper.getEntry(id);
    163    if (entry != null) {
    164      record.name = entry.name;
    165      record.value = entry.value;
    166    } else {
    167      record.deleted = true;
    168    }
    169    return record;
    170  },
    171 
    172  async create(record) {
    173    this._log.trace("Adding form record for " + record.name);
    174    let change = {
    175      op: "add",
    176      guid: record.id,
    177      fieldname: record.name,
    178      value: record.value,
    179    };
    180    await this._processChange(change);
    181  },
    182 
    183  async remove(record) {
    184    this._log.trace("Removing form record: " + record.id);
    185    let change = {
    186      op: "remove",
    187      guid: record.id,
    188    };
    189    await this._processChange(change);
    190  },
    191 
    192  async update() {
    193    this._log.trace("Ignoring form record update request!");
    194  },
    195 
    196  async wipe() {
    197    let change = {
    198      op: "remove",
    199    };
    200    await FormWrapper._update(change);
    201  },
    202 };
    203 Object.setPrototypeOf(FormStore.prototype, Store.prototype);
    204 
    205 function FormTracker(name, engine) {
    206  LegacyTracker.call(this, name, engine);
    207 }
    208 FormTracker.prototype = {
    209  QueryInterface: ChromeUtils.generateQI([
    210    "nsIObserver",
    211    "nsISupportsWeakReference",
    212  ]),
    213 
    214  onStart() {
    215    Svc.Obs.add("satchel-storage-changed", this.asyncObserver);
    216  },
    217 
    218  onStop() {
    219    Svc.Obs.remove("satchel-storage-changed", this.asyncObserver);
    220  },
    221 
    222  async observe(subject, topic, data) {
    223    if (this.ignoreAll) {
    224      return;
    225    }
    226    switch (topic) {
    227      case "satchel-storage-changed":
    228        if (data == "formhistory-add" || data == "formhistory-remove") {
    229          let guid = subject.QueryInterface(Ci.nsISupportsString).toString();
    230          await this.trackEntry(guid);
    231        }
    232        break;
    233    }
    234  },
    235 
    236  async trackEntry(guid) {
    237    const added = await this.addChangedID(guid);
    238    if (added) {
    239      this.score += SCORE_INCREMENT_MEDIUM;
    240    }
    241  },
    242 };
    243 Object.setPrototypeOf(FormTracker.prototype, LegacyTracker.prototype);
    244 
    245 class FormsProblemData extends CollectionProblemData {
    246  getSummary() {
    247    // We don't support syncing deleted form data, so "clientMissing" isn't a problem
    248    return super.getSummary().filter(entry => entry.name !== "clientMissing");
    249  }
    250 }
    251 
    252 export class FormValidator extends CollectionValidator {
    253  constructor() {
    254    super("forms", "id", ["name", "value"]);
    255    this.ignoresMissingClients = true;
    256  }
    257 
    258  emptyProblemData() {
    259    return new FormsProblemData();
    260  }
    261 
    262  async getClientItems() {
    263    return FormWrapper._search(["guid", "fieldname", "value"], {});
    264  }
    265 
    266  normalizeClientItem(item) {
    267    return {
    268      id: item.guid,
    269      guid: item.guid,
    270      name: item.fieldname,
    271      fieldname: item.fieldname,
    272      value: item.value,
    273      original: item,
    274    };
    275  }
    276 
    277  async normalizeServerItem(item) {
    278    let res = Object.assign(
    279      {
    280        guid: item.id,
    281        fieldname: item.name,
    282        original: item,
    283      },
    284      item
    285    );
    286    // Missing `name` or `value` causes the getGUID call to throw
    287    if (item.name !== undefined && item.value !== undefined) {
    288      let guid = await FormWrapper.getGUID(item.name, item.value);
    289      if (guid) {
    290        res.guid = guid;
    291        res.id = guid;
    292        res.duped = true;
    293      }
    294    }
    295 
    296    return res;
    297  }
    298 }