mirror of https://github.com/roytam1/boc-uxp.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
595 lines
19 KiB
595 lines
19 KiB
3 years ago
|
/*
|
||
|
* This Source Code is subject to the terms of the Mozilla Public License
|
||
|
* version 2.0 (the "License"). You can obtain a copy of the License at
|
||
|
* http://mozilla.org/MPL/2.0/.
|
||
|
*/
|
||
|
|
||
|
#filter substitution
|
||
|
|
||
|
/**
|
||
|
* @fileOverview Manages synchronization of filter subscriptions.
|
||
|
*/
|
||
|
|
||
|
var EXPORTED_SYMBOLS = ["Synchronizer"];
|
||
|
|
||
|
const Cc = Components.classes;
|
||
|
const Ci = Components.interfaces;
|
||
|
const Cr = Components.results;
|
||
|
const Cu = Components.utils;
|
||
|
|
||
|
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
|
||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||
|
Cu.import(baseURL + "TimeLine.jsm");
|
||
|
Cu.import(baseURL + "Utils.jsm");
|
||
|
Cu.import(baseURL + "FilterStorage.jsm");
|
||
|
Cu.import(baseURL + "FilterNotifier.jsm");
|
||
|
Cu.import(baseURL + "FilterClasses.jsm");
|
||
|
Cu.import(baseURL + "SubscriptionClasses.jsm");
|
||
|
Cu.import(baseURL + "Prefs.jsm");
|
||
|
|
||
|
const MILLISECONDS_IN_SECOND = 1000;
|
||
|
const SECONDS_IN_MINUTE = 60;
|
||
|
const SECONDS_IN_HOUR = 60 * SECONDS_IN_MINUTE;
|
||
|
const SECONDS_IN_DAY = 24 * SECONDS_IN_HOUR;
|
||
|
const INITIAL_DELAY = 6 * SECONDS_IN_MINUTE;
|
||
|
const CHECK_INTERVAL = SECONDS_IN_HOUR;
|
||
|
const MIN_EXPIRATION_INTERVAL = 1 * SECONDS_IN_DAY;
|
||
|
const MAX_EXPIRATION_INTERVAL = 14 * SECONDS_IN_DAY;
|
||
|
const MAX_ABSENSE_INTERVAL = 1 * SECONDS_IN_DAY;
|
||
|
|
||
|
let timer = null;
|
||
|
|
||
|
/**
|
||
|
* Map of subscriptions currently being downloaded, all currently downloaded
|
||
|
* URLs are keys of that map.
|
||
|
*/
|
||
|
let executing = {__proto__: null};
|
||
|
|
||
|
/**
|
||
|
* This object is responsible for downloading filter subscriptions whenever
|
||
|
* necessary.
|
||
|
* @class
|
||
|
*/
|
||
|
var Synchronizer =
|
||
|
{
|
||
|
/**
|
||
|
* Called on module startup.
|
||
|
*/
|
||
|
startup: function()
|
||
|
{
|
||
|
TimeLine.enter("Entered Synchronizer.startup()");
|
||
|
|
||
|
let callback = function()
|
||
|
{
|
||
|
timer.delay = CHECK_INTERVAL * MILLISECONDS_IN_SECOND;
|
||
|
checkSubscriptions();
|
||
|
};
|
||
|
|
||
|
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||
|
timer.initWithCallback(callback, INITIAL_DELAY * MILLISECONDS_IN_SECOND, Ci.nsITimer.TYPE_REPEATING_SLACK);
|
||
|
|
||
|
TimeLine.leave("Synchronizer.startup() done");
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Checks whether a subscription is currently being downloaded.
|
||
|
* @param {String} url URL of the subscription
|
||
|
* @return {Boolean}
|
||
|
*/
|
||
|
isExecuting: function(url)
|
||
|
{
|
||
|
return url in executing;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Starts the download of a subscription.
|
||
|
* @param {DownloadableSubscription} subscription Subscription to be downloaded
|
||
|
* @param {Boolean} manual true for a manually started download (should not trigger fallback requests)
|
||
|
* @param {Boolean} forceDownload if true, the subscription will even be redownloaded if it didn't change on the server
|
||
|
*/
|
||
|
execute: function(subscription, manual, forceDownload)
|
||
|
{
|
||
|
// Delay execution, SeaMonkey 2.1 won't fire request's event handlers
|
||
|
// otherwise if the window that called us is closed.
|
||
|
Utils.runAsync(this.executeInternal, this, subscription, manual, forceDownload);
|
||
|
},
|
||
|
|
||
|
executeInternal: function(subscription, manual, forceDownload)
|
||
|
{
|
||
|
let url = subscription.url;
|
||
|
if (url in executing)
|
||
|
return;
|
||
|
|
||
|
let newURL = subscription.nextURL;
|
||
|
let hadTemporaryRedirect = false;
|
||
|
subscription.nextURL = null;
|
||
|
|
||
|
let curVersion = "3.5.0";
|
||
|
let loadFrom = newURL;
|
||
|
let isBaseLocation = true;
|
||
|
if (!loadFrom)
|
||
|
loadFrom = url;
|
||
|
if (loadFrom == url)
|
||
|
{
|
||
|
if (subscription.alternativeLocations)
|
||
|
{
|
||
|
// We have alternative download locations, choose one. "Regular"
|
||
|
// subscription URL always goes in with weight 1.
|
||
|
let options = [[1, url]];
|
||
|
let totalWeight = 1;
|
||
|
for each (let alternative in subscription.alternativeLocations.split(','))
|
||
|
{
|
||
|
if (!/^https?:\/\//.test(alternative))
|
||
|
continue;
|
||
|
|
||
|
let weight = 1;
|
||
|
let match = /;q=([\d\.]+)$/.exec(alternative);
|
||
|
if (match)
|
||
|
{
|
||
|
weight = parseFloat(match[1]);
|
||
|
if (isNaN(weight) || !isFinite(weight) || weight < 0)
|
||
|
weight = 1;
|
||
|
if (weight > 10)
|
||
|
weight = 10;
|
||
|
|
||
|
alternative = alternative.substr(0, match.index);
|
||
|
}
|
||
|
options.push([weight, alternative]);
|
||
|
totalWeight += weight;
|
||
|
}
|
||
|
|
||
|
let choice = Math.random() * totalWeight;
|
||
|
for each (let [weight, alternative] in options)
|
||
|
{
|
||
|
choice -= weight;
|
||
|
if (choice < 0)
|
||
|
{
|
||
|
loadFrom = alternative;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
isBaseLocation = (loadFrom == url);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Ignore modification date if we are downloading from a different location
|
||
|
forceDownload = true;
|
||
|
}
|
||
|
loadFrom = loadFrom.replace(/%VERSION%/, "ABP" + "3.5.0");
|
||
|
|
||
|
let request = null;
|
||
|
function errorCallback(error)
|
||
|
{
|
||
|
let channelStatus = -1;
|
||
|
try
|
||
|
{
|
||
|
channelStatus = request.channel.status;
|
||
|
} catch (e) {}
|
||
|
let responseStatus = "";
|
||
|
try
|
||
|
{
|
||
|
responseStatus = request.channel.QueryInterface(Ci.nsIHttpChannel).responseStatus;
|
||
|
} catch (e) {}
|
||
|
setError(subscription, error, channelStatus, responseStatus, loadFrom, isBaseLocation, manual);
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
|
||
|
request.mozBackgroundRequest = true;
|
||
|
request.open("GET", loadFrom);
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
errorCallback("synchronize_invalid_url");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
request.overrideMimeType("text/plain");
|
||
|
request.channel.loadFlags = request.channel.loadFlags |
|
||
|
request.channel.INHIBIT_CACHING |
|
||
|
request.channel.VALIDATE_ALWAYS;
|
||
|
|
||
|
// Override redirect limit from preferences, user might have set it to 1
|
||
|
if (request.channel instanceof Ci.nsIHttpChannel)
|
||
|
request.channel.redirectionLimit = 5;
|
||
|
|
||
|
var oldNotifications = request.channel.notificationCallbacks;
|
||
|
var oldEventSink = null;
|
||
|
request.channel.notificationCallbacks =
|
||
|
{
|
||
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor, Ci.nsIChannelEventSink]),
|
||
|
|
||
|
getInterface: function(iid)
|
||
|
{
|
||
|
if (iid.equals(Ci.nsIChannelEventSink))
|
||
|
{
|
||
|
try {
|
||
|
oldEventSink = oldNotifications.QueryInterface(iid);
|
||
|
} catch(e) {}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
if (oldNotifications)
|
||
|
return oldNotifications.QueryInterface(iid);
|
||
|
else
|
||
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
||
|
},
|
||
|
|
||
|
asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
|
||
|
{
|
||
|
if (isBaseLocation && !hadTemporaryRedirect && oldChannel instanceof Ci.nsIHttpChannel)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
subscription.alternativeLocations = oldChannel.getResponseHeader("X-Alternative-Locations");
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
subscription.alternativeLocations = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (flags & Ci.nsIChannelEventSink.REDIRECT_TEMPORARY)
|
||
|
hadTemporaryRedirect = true;
|
||
|
else if (!hadTemporaryRedirect)
|
||
|
newURL = newChannel.URI.spec;
|
||
|
|
||
|
if (oldEventSink)
|
||
|
oldEventSink.asyncOnChannelRedirect(oldChannel, newChannel, flags, callback);
|
||
|
else
|
||
|
callback.onRedirectVerifyCallback(Cr.NS_OK);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
Cu.reportError(e)
|
||
|
}
|
||
|
|
||
|
if (subscription.lastModified && !forceDownload)
|
||
|
request.setRequestHeader("If-Modified-Since", subscription.lastModified);
|
||
|
|
||
|
request.addEventListener("error", function(ev)
|
||
|
{
|
||
|
delete executing[url];
|
||
|
try {
|
||
|
request.channel.notificationCallbacks = null;
|
||
|
} catch (e) {}
|
||
|
|
||
|
errorCallback("synchronize_connection_error");
|
||
|
}, false);
|
||
|
|
||
|
request.addEventListener("load", function(ev)
|
||
|
{
|
||
|
delete executing[url];
|
||
|
try {
|
||
|
request.channel.notificationCallbacks = null;
|
||
|
} catch (e) {}
|
||
|
|
||
|
// Status will be 0 for non-HTTP requests
|
||
|
if (request.status && request.status != 200 && request.status != 304)
|
||
|
{
|
||
|
errorCallback("synchronize_connection_error");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let newFilters = null;
|
||
|
if (request.status != 304)
|
||
|
{
|
||
|
newFilters = readFilters(subscription, request.responseText, errorCallback);
|
||
|
if (!newFilters)
|
||
|
return;
|
||
|
|
||
|
subscription.lastModified = request.getResponseHeader("Last-Modified");
|
||
|
}
|
||
|
|
||
|
if (isBaseLocation && !hadTemporaryRedirect)
|
||
|
subscription.alternativeLocations = request.getResponseHeader("X-Alternative-Locations");
|
||
|
subscription.lastSuccess = subscription.lastDownload = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
|
||
|
subscription.downloadStatus = "synchronize_ok";
|
||
|
subscription.errors = 0;
|
||
|
|
||
|
// Expiration header is relative to server time - use Date header if it exists, otherwise local time
|
||
|
let now = Math.round((new Date(request.getResponseHeader("Date")).getTime() || Date.now()) / MILLISECONDS_IN_SECOND);
|
||
|
let expires = Math.round(new Date(request.getResponseHeader("Expires")).getTime() / MILLISECONDS_IN_SECOND) || 0;
|
||
|
let expirationInterval = (expires ? expires - now : 0);
|
||
|
for each (let filter in newFilters || subscription.filters)
|
||
|
{
|
||
|
if (!(filter instanceof CommentFilter))
|
||
|
continue;
|
||
|
|
||
|
let match = /\bExpires\s*(?::|after)\s*(\d+)\s*(h)?/i.exec(filter.text);
|
||
|
if (match)
|
||
|
{
|
||
|
let interval = parseInt(match[1], 10);
|
||
|
if (match[2])
|
||
|
interval *= SECONDS_IN_HOUR;
|
||
|
else
|
||
|
interval *= SECONDS_IN_DAY;
|
||
|
|
||
|
if (interval > expirationInterval)
|
||
|
expirationInterval = interval;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Expiration interval should be within allowed range
|
||
|
expirationInterval = Math.min(Math.max(expirationInterval, MIN_EXPIRATION_INTERVAL), MAX_EXPIRATION_INTERVAL);
|
||
|
|
||
|
// Hard expiration: download immediately after twice the expiration interval
|
||
|
subscription.expires = (subscription.lastDownload + expirationInterval * 2);
|
||
|
|
||
|
// Soft expiration: use random interval factor between 0.8 and 1.2
|
||
|
subscription.softExpiration = (subscription.lastDownload + Math.round(expirationInterval * (Math.random() * 0.4 + 0.8)));
|
||
|
|
||
|
// Process some special filters and remove them
|
||
|
if (newFilters)
|
||
|
{
|
||
|
let fixedTitle = false;
|
||
|
for (let i = 0; i < newFilters.length; i++)
|
||
|
{
|
||
|
let filter = newFilters[i];
|
||
|
if (!(filter instanceof CommentFilter))
|
||
|
continue;
|
||
|
|
||
|
let match = /^!\s*(\w+)\s*:\s*(.*)/.exec(filter.text);
|
||
|
if (match)
|
||
|
{
|
||
|
let keyword = match[1].toLowerCase();
|
||
|
let value = match[2];
|
||
|
let known = true;
|
||
|
if (keyword == "redirect")
|
||
|
{
|
||
|
if (isBaseLocation && value != url)
|
||
|
subscription.nextURL = value;
|
||
|
}
|
||
|
else if (keyword == "homepage")
|
||
|
{
|
||
|
let uri = Utils.makeURI(value);
|
||
|
if (uri && (uri.scheme == "http" || uri.scheme == "https"))
|
||
|
subscription.homepage = uri.spec;
|
||
|
}
|
||
|
else if (keyword == "title")
|
||
|
{
|
||
|
if (value)
|
||
|
{
|
||
|
subscription.title = value;
|
||
|
fixedTitle = true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
known = false;
|
||
|
|
||
|
if (known)
|
||
|
newFilters.splice(i--, 1);
|
||
|
}
|
||
|
}
|
||
|
subscription.fixedTitle = fixedTitle;
|
||
|
}
|
||
|
|
||
|
if (isBaseLocation && newURL && newURL != url)
|
||
|
{
|
||
|
let listed = (subscription.url in FilterStorage.knownSubscriptions);
|
||
|
if (listed)
|
||
|
FilterStorage.removeSubscription(subscription);
|
||
|
|
||
|
url = newURL;
|
||
|
|
||
|
let newSubscription = Subscription.fromURL(url);
|
||
|
for (let key in newSubscription)
|
||
|
delete newSubscription[key];
|
||
|
for (let key in subscription)
|
||
|
newSubscription[key] = subscription[key];
|
||
|
|
||
|
delete Subscription.knownSubscriptions[subscription.url];
|
||
|
newSubscription.oldSubscription = subscription;
|
||
|
subscription = newSubscription;
|
||
|
subscription.url = url;
|
||
|
|
||
|
if (!(subscription.url in FilterStorage.knownSubscriptions) && listed)
|
||
|
FilterStorage.addSubscription(subscription);
|
||
|
}
|
||
|
|
||
|
if (newFilters)
|
||
|
FilterStorage.updateSubscriptionFilters(subscription, newFilters);
|
||
|
delete subscription.oldSubscription;
|
||
|
}, false);
|
||
|
|
||
|
executing[url] = true;
|
||
|
FilterNotifier.triggerListeners("subscription.downloadStatus", subscription);
|
||
|
|
||
|
try
|
||
|
{
|
||
|
request.send(null);
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
delete executing[url];
|
||
|
errorCallback("synchronize_connection_error");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Checks whether any subscriptions need to be downloaded and starts the download
|
||
|
* if necessary.
|
||
|
*/
|
||
|
function checkSubscriptions()
|
||
|
{
|
||
|
if (!Prefs.subscriptions_autoupdate)
|
||
|
return;
|
||
|
|
||
|
let time = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
|
||
|
for each (let subscription in FilterStorage.subscriptions)
|
||
|
{
|
||
|
if (!(subscription instanceof DownloadableSubscription))
|
||
|
continue;
|
||
|
|
||
|
if (subscription.lastCheck && time - subscription.lastCheck > MAX_ABSENSE_INTERVAL)
|
||
|
{
|
||
|
// No checks for a long time interval - user must have been offline, e.g.
|
||
|
// during a weekend. Increase soft expiration to prevent load peaks on the
|
||
|
// server.
|
||
|
subscription.softExpiration += time - subscription.lastCheck;
|
||
|
}
|
||
|
subscription.lastCheck = time;
|
||
|
|
||
|
// Sanity check: do expiration times make sense? Make sure people changing
|
||
|
// system clock don't get stuck with outdated subscriptions.
|
||
|
if (subscription.expires - time > MAX_EXPIRATION_INTERVAL)
|
||
|
subscription.expires = time + MAX_EXPIRATION_INTERVAL;
|
||
|
if (subscription.softExpiration - time > MAX_EXPIRATION_INTERVAL)
|
||
|
subscription.softExpiration = time + MAX_EXPIRATION_INTERVAL;
|
||
|
|
||
|
if (subscription.softExpiration > time && subscription.expires > time)
|
||
|
continue;
|
||
|
|
||
|
// Do not retry downloads more often than MIN_EXPIRATION_INTERVAL
|
||
|
if (time - subscription.lastDownload >= MIN_EXPIRATION_INTERVAL)
|
||
|
Synchronizer.execute(subscription, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extracts a list of filters from text returned by a server.
|
||
|
* @param {DownloadableSubscription} subscription subscription the info should be placed into
|
||
|
* @param {String} text server response
|
||
|
* @param {Function} errorCallback function to be called on error
|
||
|
* @return {Array of Filter}
|
||
|
*/
|
||
|
function readFilters(subscription, text, errorCallback)
|
||
|
{
|
||
|
let lines = text.split(/[\r\n]+/);
|
||
|
let match = /\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\]/i.exec(lines[0]);
|
||
|
if (!match)
|
||
|
{
|
||
|
errorCallback("synchronize_invalid_data");
|
||
|
return null;
|
||
|
}
|
||
|
let minVersion = match[1];
|
||
|
|
||
|
for (let i = 0; i < lines.length; i++)
|
||
|
{
|
||
|
let match = /!\s*checksum[\s\-:]+([\w\+\/]+)/i.exec(lines[i]);
|
||
|
if (match)
|
||
|
{
|
||
|
lines.splice(i, 1);
|
||
|
let checksum = Utils.generateChecksum(lines);
|
||
|
|
||
|
if (checksum && checksum != match[1])
|
||
|
{
|
||
|
errorCallback("synchronize_checksum_mismatch");
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
delete subscription.requiredVersion;
|
||
|
delete subscription.upgradeRequired;
|
||
|
if (minVersion)
|
||
|
{
|
||
|
subscription.requiredVersion = minVersion;
|
||
|
if (Utils.versionComparator.compare(minVersion, "3.5.0") > 0)
|
||
|
subscription.upgradeRequired = true;
|
||
|
}
|
||
|
|
||
|
lines.shift();
|
||
|
let result = [];
|
||
|
for each (let line in lines)
|
||
|
{
|
||
|
let filter = Filter.fromText(Filter.normalize(line));
|
||
|
if (filter)
|
||
|
result.push(filter);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handles an error during a subscription download.
|
||
|
* @param {DownloadableSubscription} subscription subscription that failed to download
|
||
|
* @param {Integer} channelStatus result code of the download channel
|
||
|
* @param {String} responseStatus result code as received from server
|
||
|
* @param {String} downloadURL the URL used for download
|
||
|
* @param {String} error error ID in global.properties
|
||
|
* @param {Boolean} isBaseLocation false if the subscription was downloaded from a location specified in X-Alternative-Locations header
|
||
|
* @param {Boolean} manual true for a manually started download (should not trigger fallback requests)
|
||
|
*/
|
||
|
function setError(subscription, error, channelStatus, responseStatus, downloadURL, isBaseLocation, manual)
|
||
|
{
|
||
|
// If download from an alternative location failed, reset the list of
|
||
|
// alternative locations - have to get an updated list from base location.
|
||
|
if (!isBaseLocation)
|
||
|
subscription.alternativeLocations = null;
|
||
|
|
||
|
try {
|
||
|
Cu.reportError("Adblock Plus: Downloading filter subscription " + subscription.title + " failed (" + Utils.getString(error) + ")\n" +
|
||
|
"Download address: " + downloadURL + "\n" +
|
||
|
"Channel status: " + channelStatus + "\n" +
|
||
|
"Server response: " + responseStatus);
|
||
|
} catch(e) {}
|
||
|
|
||
|
subscription.lastDownload = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
|
||
|
subscription.downloadStatus = error;
|
||
|
|
||
|
// Request fallback URL if necessary - for automatic updates only
|
||
|
if (!manual)
|
||
|
{
|
||
|
if (error == "synchronize_checksum_mismatch")
|
||
|
{
|
||
|
// No fallback for successful download with checksum mismatch, reset error counter
|
||
|
subscription.errors = 0;
|
||
|
}
|
||
|
else
|
||
|
subscription.errors++;
|
||
|
|
||
|
if (subscription.errors >= Prefs.subscriptions_fallbackerrors && /^https?:\/\//i.test(subscription.url))
|
||
|
{
|
||
|
subscription.errors = 0;
|
||
|
|
||
|
let fallbackURL = Prefs.subscriptions_fallbackurl;
|
||
|
fallbackURL = fallbackURL.replace(/%VERSION%/g, encodeURIComponent("3.5.0"));
|
||
|
fallbackURL = fallbackURL.replace(/%SUBSCRIPTION%/g, encodeURIComponent(subscription.url));
|
||
|
fallbackURL = fallbackURL.replace(/%URL%/g, encodeURIComponent(downloadURL));
|
||
|
fallbackURL = fallbackURL.replace(/%ERROR%/g, encodeURIComponent(error));
|
||
|
fallbackURL = fallbackURL.replace(/%CHANNELSTATUS%/g, encodeURIComponent(channelStatus));
|
||
|
fallbackURL = fallbackURL.replace(/%RESPONSESTATUS%/g, encodeURIComponent(responseStatus));
|
||
|
|
||
|
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
|
||
|
request.mozBackgroundRequest = true;
|
||
|
request.open("GET", fallbackURL);
|
||
|
request.overrideMimeType("text/plain");
|
||
|
request.channel.loadFlags = request.channel.loadFlags |
|
||
|
request.channel.INHIBIT_CACHING |
|
||
|
request.channel.VALIDATE_ALWAYS;
|
||
|
request.addEventListener("load", function(ev)
|
||
|
{
|
||
|
if (!(subscription.url in FilterStorage.knownSubscriptions))
|
||
|
return;
|
||
|
|
||
|
let match = /^(\d+)(?:\s+(\S+))?$/.exec(request.responseText);
|
||
|
if (match && match[1] == "301" && match[2]) // Moved permanently
|
||
|
subscription.nextURL = match[2];
|
||
|
else if (match && match[1] == "410") // Gone
|
||
|
{
|
||
|
let data = "[Adblock]\n" + subscription.filters.map(function(f) f.text).join("\n");
|
||
|
let url = "data:text/plain," + encodeURIComponent(data);
|
||
|
let newSubscription = Subscription.fromURL(url);
|
||
|
newSubscription.title = subscription.title;
|
||
|
newSubscription.disabled = subscription.disabled;
|
||
|
FilterStorage.removeSubscription(subscription);
|
||
|
FilterStorage.addSubscription(newSubscription);
|
||
|
Synchronizer.execute(newSubscription);
|
||
|
}
|
||
|
}, false);
|
||
|
request.send(null);
|
||
|
}
|
||
|
}
|
||
|
}
|