Backed out 3 changesets (bug 1884623) for causing multiple failures CLOSED TREE
[gecko.git] / toolkit / components / url-classifier / UrlClassifierRemoteSettingsService.sys.mjs
blob42f8cf272a098275056a24d1ffaf9abe76495c15
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/. */
5 const lazy = {};
6 ChromeUtils.defineESModuleGetters(lazy, {
7   RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
8 });
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",
20   ]),
22   _initialized: false,
24   // Entries that are retrieved from RemoteSettings.get(). keyed by the table name.
25   _entries: {},
27   async lazyInit() {
28     if (this._initialized) {
29       return;
30     }
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 => {
36       let {
37         data: { current },
38       } = event;
39       this._onUpdateEntries(current);
40     });
42     this._initialized = true;
44     let entries;
45     try {
46       entries = await rs.get();
47     } catch (e) {}
49     this._onUpdateEntries(entries || []);
50   },
52   _onUpdateEntries(aEntries) {
53     aEntries.map(entry => {
54       this._entries[entry.Name] = entry;
55     });
56   },
58   // Parse the update request. See UrlClassifierListManager.jsm makeUpdateRequest
59   // for more details about how we build the update request.
60   //
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");
66     let requests = [];
67     for (let line of lines) {
68       let fields = line.split(";");
69       let chunkNum = fields[1]?.match(/(?<=a:).*/);
70       requests.push([fields[0], chunkNum]);
71     }
72     return requests;
73   },
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) {
86         continue;
87       }
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) {
92         continue;
93       }
95       let downloadError = false;
96       try {
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);
101         let strData = "";
102         for (let i = 0; i < bytes.length; i++) {
103           strData += String.fromCharCode(bytes[i]);
104         }
106         // Construct the payload
107         payload += "i:" + reqTableName + "\n";
108         payload += strData;
109       } catch (e) {
110         downloadError = true;
111       }
113       if (downloadError) {
114         try {
115           aListener.onStartRequest(null);
116           aListener.onStopRequest(null, Cr.NS_ERROR_FAILURE);
117         } catch (e) {}
118         return;
119       }
120     }
122     // Send the update response over stream listener interface
123     let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
124       Ci.nsIStringInputStream
125     );
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);
131   },
133   fetchList(aPayload, aListener) {
134     this._getLists(aPayload, aListener);
135   },
137   clear() {
138     this._initialized = false;
139     this._entries = {};
140   },