147 lines
4.2 KiB
JavaScript
147 lines
4.2 KiB
JavaScript
/* 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/. */
|
|
|
|
const lazy = {};
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
|
|
});
|
|
|
|
const COLLECTION_NAME = "tracking-protection-lists";
|
|
|
|
// SafeBrowsing protocol parameters.
|
|
export const SBRS_UPDATE_MINIMUM_DELAY = 21600; // Minimum delay before polling again in seconds
|
|
|
|
export function UrlClassifierRemoteSettingsService() {}
|
|
UrlClassifierRemoteSettingsService.prototype = {
|
|
classID: Components.ID("{1980624c-c50b-4b46-a91c-dfaba7792706}"),
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIUrlClassifierRemoteSettingsService",
|
|
]),
|
|
|
|
_initialized: false,
|
|
|
|
// Entries that are retrieved from RemoteSettings.get(). keyed by the table name.
|
|
_entries: {},
|
|
|
|
async lazyInit() {
|
|
if (this._initialized) {
|
|
return;
|
|
}
|
|
|
|
let rs = lazy.RemoteSettings(COLLECTION_NAME);
|
|
// Bug 1750191: Notify listmanager to trigger an update instead
|
|
// of polling data periodically.
|
|
rs.on("sync", async event => {
|
|
let {
|
|
data: { current },
|
|
} = event;
|
|
this._onUpdateEntries(current);
|
|
});
|
|
|
|
this._initialized = true;
|
|
|
|
let entries;
|
|
try {
|
|
entries = await rs.get();
|
|
} catch (e) {}
|
|
|
|
this._onUpdateEntries(entries || []);
|
|
},
|
|
|
|
_onUpdateEntries(aEntries) {
|
|
aEntries.map(entry => {
|
|
this._entries[entry.Name] = entry;
|
|
});
|
|
},
|
|
|
|
// Parse the update request. See UrlClassifierListManager.sys.mjs makeUpdateRequest
|
|
// for more details about how we build the update request.
|
|
//
|
|
// @param aRequest the request payload of the update request
|
|
// @return array The array of requested tables. Each item in the array is composed
|
|
// with [table name, chunk numner]
|
|
_parseRequest(aRequest) {
|
|
let lines = aRequest.split("\n");
|
|
let requests = [];
|
|
for (let line of lines) {
|
|
let fields = line.split(";");
|
|
let chunkNum = fields[1]?.match(/(?<=a:).*/);
|
|
requests.push([fields[0], chunkNum]);
|
|
}
|
|
return requests;
|
|
},
|
|
|
|
async _getLists(aRequest, aListener) {
|
|
await this.lazyInit();
|
|
|
|
let rs = lazy.RemoteSettings(COLLECTION_NAME);
|
|
let payload = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";
|
|
|
|
let requests = this._parseRequest(aRequest);
|
|
for (let request of requests) {
|
|
let [reqTableName, reqChunkNum] = request;
|
|
let entry = this._entries[reqTableName];
|
|
if (!entry?.attachment) {
|
|
continue;
|
|
}
|
|
|
|
// If the request version is the same as what we have in Remote Settings,
|
|
// we are up-to-date now.
|
|
if (entry.Version == reqChunkNum) {
|
|
continue;
|
|
}
|
|
|
|
let downloadError = false;
|
|
try {
|
|
// SafeBrowsing maintains its own files, so we can remove the downloaded
|
|
// files after SafeBrowsing processes the data.
|
|
let buffer = await rs.attachments.downloadAsBytes(entry);
|
|
let bytes = new Uint8Array(buffer);
|
|
let strData = "";
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
strData += String.fromCharCode(bytes[i]);
|
|
}
|
|
|
|
// Construct the payload
|
|
payload += "i:" + reqTableName + "\n";
|
|
// Add adddel directive to clear the old chunks.
|
|
if (reqChunkNum) {
|
|
payload += "ad:" + reqChunkNum.toString() + "\n";
|
|
}
|
|
payload += strData;
|
|
} catch (e) {
|
|
downloadError = true;
|
|
}
|
|
|
|
if (downloadError) {
|
|
try {
|
|
aListener.onStartRequest(null);
|
|
aListener.onStopRequest(null, Cr.NS_ERROR_FAILURE);
|
|
} catch (e) {}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Send the update response over stream listener interface
|
|
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
|
|
Ci.nsIStringInputStream
|
|
);
|
|
stream.setByteStringData(payload);
|
|
|
|
try {
|
|
aListener.onStartRequest(null);
|
|
aListener.onDataAvailable(null, stream, 0, payload.length);
|
|
aListener.onStopRequest(null, Cr.NS_OK);
|
|
} catch (e) {}
|
|
},
|
|
|
|
fetchList(aPayload, aListener) {
|
|
this._getLists(aPayload, aListener);
|
|
},
|
|
|
|
clear() {
|
|
this._initialized = false;
|
|
this._entries = {};
|
|
},
|
|
};
|