summaryrefslogtreecommitdiffstats
path: root/netwerk/dns/PublicSuffixList.jsm
blob: c6988c0b8bdb089087d76652646bcd62bb4ce436 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { RemoteSettings } = ChromeUtils.import(
  "resource://services-settings/remote-settings.js"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const FileUtils = ChromeUtils.import("resource://gre/modules/FileUtils.jsm")
  .FileUtils;

const EXPORTED_SYMBOLS = ["PublicSuffixList"];

const RECORD_ID = "tld-dafsa";
const SIGNAL = "public-suffix-list-updated";

const PublicSuffixList = {
  CLIENT: RemoteSettings("public-suffix-list"),

  init() {
    // Only initialize once.
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    this.CLIENT.on("sync", this.onUpdate.bind(this));
    /* We have a single record for this collection. Let's see if we already have it locally.
     * Note that on startup, we don't need to synchronize immediately on new profiles.
     */
    this.CLIENT.get({ syncIfEmpty: false, filters: { id: RECORD_ID } })
      .then(async records => {
        if (records.length == 1) {
          // Get the downloaded file URI (most likely to be a no-op here, since file will exist).
          const fileURI = await this.CLIENT.attachments.download(records[0]);
          // Send a signal so that the C++ code loads the updated list on startup.
          this.notifyUpdate(fileURI);
        }
      })
      .catch(err => console.error(err));
  },

  /**
   * This method returns the path to the file based on the file uri received
   * Example:
   * On windows "file://C:/Users/AppData/main/public-suffix-list/dafsa.bin"
   * will be converted to "C:\\Users\\main\\public-suffix-list\\dafsa.bin
   *
   * On macOS/linux "file:///home/main/public-suffix-list/dafsa.bin"
   * will be converted to "/home/main/public-suffix-list/dafsa.bin"
   */
  getFilePath(fileURI) {
    const uri = Services.io.newURI(fileURI);
    const file = uri.QueryInterface(Ci.nsIFileURL).file;
    return file.path;
  },

  notifyUpdate(fileURI) {
    if (!Services.prefs.getBoolPref("network.psl.onUpdate_notify", false)) {
      // Updating the PSL while Firefox is running could cause principals to
      // have a different base domain before/after the update.
      // See bug 1582647 comment 30
      return;
    }

    const filePath = this.getFilePath(fileURI);
    const nsifile = new FileUtils.File(filePath);
    /* Send a signal to be read by the C++, the method
     * ::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
     * at netwerk/dns/nsEffectiveTLDService.cpp
     */
    Services.obs.notifyObservers(
      nsifile, // aSubject
      SIGNAL, // aTopic
      filePath // aData
    );
  },

  async onUpdate({ data: { created, updated, deleted } }) {
    // In theory, this will never happen, we will never delete the record.
    if (deleted.length == 1) {
      await this.CLIENT.attachments.delete(deleted[0]);
    }
    // Handle creation and update the same way
    const changed = created.concat(updated.map(u => u.new));
    /* In theory, we should never have more than one record. And if we receive
     * this event, it's because the single record was updated.
     */
    if (changed.length != 1) {
      console.warn("Unsupported sync event for Public Suffix List");
      return;
    }
    // Download the updated file.
    let fileURI;
    try {
      fileURI = await this.CLIENT.attachments.download(changed[0]);
    } catch (err) {
      Cu.reportError(err);
      return;
    }

    // Notify the C++ part to reload it from disk.
    this.notifyUpdate(fileURI);
  },
};