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/. */
6 ChromeUtils.defineESModuleGetters(lazy, {
7 RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
10 const COLLECTION_NAME = "tracking-protection-lists";
12 // SafeBrowsing protocol parameters.
13 export const SBRS_UPDATE_MINIMUM_DELAY = 21600; // Minimum delay before polling again in seconds
15 export function UrlClassifierRemoteSettingsService() {}
16 UrlClassifierRemoteSettingsService.prototype = {
17 classID: Components.ID("{1980624c-c50b-4b46-a91c-dfaba7792706}"),
18 QueryInterface: ChromeUtils.generateQI([
19 "nsIUrlClassifierRemoteSettingsService",
24 // Entries that are retrieved from RemoteSettings.get(). keyed by the table name.
28 if (this._initialized) {
32 let rs = lazy.RemoteSettings(COLLECTION_NAME);
33 // Bug 1750191: Notify listmanager to trigger an update instead
34 // of polling data periodically.
35 rs.on("sync", async event => {
39 this._onUpdateEntries(current);
42 this._initialized = true;
46 entries = await rs.get();
49 this._onUpdateEntries(entries || []);
52 _onUpdateEntries(aEntries) {
53 aEntries.map(entry => {
54 this._entries[entry.Name] = entry;
58 // Parse the update request. See UrlClassifierListManager.jsm makeUpdateRequest
59 // for more details about how we build the update request.
61 // @param aRequest the request payload of the update request
62 // @return array The array of requested tables. Each item in the array is composed
63 // with [table name, chunk numner]
64 _parseRequest(aRequest) {
65 let lines = aRequest.split("\n");
67 for (let line of lines) {
68 let fields = line.split(";");
69 let chunkNum = fields[1]?.match(/(?<=a:).*/);
70 requests.push([fields[0], chunkNum]);
75 async _getLists(aRequest, aListener) {
76 await this.lazyInit();
78 let rs = lazy.RemoteSettings(COLLECTION_NAME);
79 let payload = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";
81 let requests = this._parseRequest(aRequest);
82 for (let request of requests) {
83 let [reqTableName, reqChunkNum] = request;
84 let entry = this._entries[reqTableName];
85 if (!entry?.attachment) {
89 // If the request version is the same as what we have in Remote Settings,
90 // we are up-to-date now.
91 if (entry.Version == reqChunkNum) {
95 let downloadError = false;
97 // SafeBrowsing maintains its own files, so we can remove the downloaded
98 // files after SafeBrowsing processes the data.
99 let buffer = await rs.attachments.downloadAsBytes(entry);
100 let bytes = new Uint8Array(buffer);
102 for (let i = 0; i < bytes.length; i++) {
103 strData += String.fromCharCode(bytes[i]);
106 // Construct the payload
107 payload += "i:" + reqTableName + "\n";
110 downloadError = true;
115 aListener.onStartRequest(null);
116 aListener.onStopRequest(null, Cr.NS_ERROR_FAILURE);
122 // Send the update response over stream listener interface
123 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
124 Ci.nsIStringInputStream
126 stream.setData(payload, payload.length);
128 aListener.onStartRequest(null);
129 aListener.onDataAvailable(null, stream, 0, payload.length);
130 aListener.onStopRequest(null, Cr.NS_OK);
133 fetchList(aPayload, aListener) {
134 this._getLists(aPayload, aListener);
138 this._initialized = false;