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.
 
 
 
 
 
 

560 lines
15 KiB

/*
* 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 Definition of Subscription class and its subclasses.
*/
var EXPORTED_SYMBOLS = ["Subscription", "SpecialSubscription", "RegularSubscription", "ExternalSubscription", "DownloadableSubscription"];
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(baseURL + "Utils.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
/**
* Abstract base class for filter subscriptions
*
* @param {String} url download location of the subscription
* @param {String} [title] title of the filter subscription
* @constructor
*/
function Subscription(url, title)
{
this.url = url;
this.filters = [];
this._title = title || Utils.getString("newGroup_title");
Subscription.knownSubscriptions[url] = this;
}
Subscription.prototype =
{
/**
* Download location of the subscription
* @type String
*/
url: null,
/**
* Filters contained in the filter subscription
* @type Array of Filter
*/
filters: null,
_title: null,
_fixedTitle: false,
_disabled: false,
/**
* Title of the filter subscription
* @type String
*/
get title() this._title,
set title(value)
{
if (value != this._title)
{
let oldValue = this._title;
this._title = value;
FilterNotifier.triggerListeners("subscription.title", this, value, oldValue);
}
return this._title;
},
/**
* Determines whether the title should be editable
* @type Boolean
*/
get fixedTitle() this._fixedTitle,
set fixedTitle(value)
{
if (value != this._fixedTitle)
{
let oldValue = this._fixedTitle;
this._fixedTitle = value;
FilterNotifier.triggerListeners("subscription.fixedTitle", this, value, oldValue);
}
return this._fixedTitle;
},
/**
* Defines whether the filters in the subscription should be disabled
* @type Boolean
*/
get disabled() this._disabled,
set disabled(value)
{
if (value != this._disabled)
{
let oldValue = this._disabled;
this._disabled = value;
FilterNotifier.triggerListeners("subscription.disabled", this, value, oldValue);
}
return this._disabled;
},
/**
* Serializes the filter to an array of strings for writing out on the disk.
* @param {Array of String} buffer buffer to push the serialization results into
*/
serialize: function(buffer)
{
buffer.push("[Subscription]");
buffer.push("url=" + this.url);
buffer.push("title=" + this._title);
if (this._fixedTitle)
buffer.push("fixedTitle=true");
if (this._disabled)
buffer.push("disabled=true");
},
serializeFilters: function(buffer)
{
for each (let filter in this.filters)
buffer.push(filter.text.replace(/\[/g, "\\["));
},
toString: function()
{
let buffer = [];
this.serialize(buffer);
return buffer.join("\n");
}
};
/**
* Cache for known filter subscriptions, maps URL to subscription objects.
* @type Object
*/
Subscription.knownSubscriptions = {__proto__: null};
/**
* Returns a subscription from its URL, creates a new one if necessary.
* @param {String} url URL of the subscription
* @return {Subscription} subscription or null if the subscription couldn't be created
*/
Subscription.fromURL = function(url)
{
if (url in Subscription.knownSubscriptions)
return Subscription.knownSubscriptions[url];
try
{
// Test URL for validity
url = Utils.ioService.newURI(url, null, null).spec;
return new DownloadableSubscription(url, null);
}
catch (e)
{
return new SpecialSubscription(url);
}
}
/**
* Deserializes a subscription
*
* @param {Object} obj map of serialized properties and their values
* @return {Subscription} subscription or null if the subscription couldn't be created
*/
Subscription.fromObject = function(obj)
{
let result;
try
{
obj.url = Utils.ioService.newURI(obj.url, null, null).spec;
// URL is valid - this is a downloadable subscription
result = new DownloadableSubscription(obj.url, obj.title);
if ("nextURL" in obj)
result.nextURL = obj.nextURL;
if ("downloadStatus" in obj)
result._downloadStatus = obj.downloadStatus;
if ("lastModified" in obj)
result.lastModified = obj.lastModified;
if ("lastSuccess" in obj)
result.lastSuccess = parseInt(obj.lastSuccess) || 0;
if ("lastCheck" in obj)
result._lastCheck = parseInt(obj.lastCheck) || 0;
if ("expires" in obj)
result.expires = parseInt(obj.expires) || 0;
if ("softExpiration" in obj)
result.softExpiration = parseInt(obj.softExpiration) || 0;
if ("errors" in obj)
result._errors = parseInt(obj.errors) || 0;
if ("requiredVersion" in obj)
{
result.requiredVersion = obj.requiredVersion;
if (Utils.versionComparator.compare(result.requiredVersion, "3.5.0") > 0)
result.upgradeRequired = true;
}
if ("alternativeLocations" in obj)
result.alternativeLocations = obj.alternativeLocations;
if ("homepage" in obj)
result._homepage = obj.homepage;
if ("lastDownload" in obj)
result._lastDownload = parseInt(obj.lastDownload) || 0;
}
catch (e)
{
// Invalid URL - custom filter group
if (!("title" in obj))
{
// Backwards compatibility - titles and filter types were originally
// determined by group identifier.
if (obj.url == "~wl~")
obj.defaults = "whitelist";
else if (obj.url == "~fl~")
obj.defaults = "blocking";
else if (obj.url == "~eh~")
obj.defaults = "elemhide";
if ("defaults" in obj)
obj.title = Utils.getString(obj.defaults + "Group_title");
}
result = new SpecialSubscription(obj.url, obj.title);
if ("defaults" in obj)
result.defaults = obj.defaults.split(" ");
}
if ("fixedTitle" in obj)
result._fixedTitle = (obj.fixedTitle == "true");
if ("disabled" in obj)
result._disabled = (obj.disabled == "true");
return result;
}
/**
* Class for special filter subscriptions (user's filters)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments Subscription
*/
function SpecialSubscription(url, title)
{
Subscription.call(this, url, title);
}
SpecialSubscription.prototype =
{
__proto__: Subscription.prototype,
/**
* Filter types that should be added to this subscription by default
* (entries should correspond to keys in SpecialSubscription.defaultsMap).
* @type Array of String
*/
defaults: null,
/**
* Tests whether a filter should be added to this group by default
* @param {Filter} filter filter to be tested
* @return {Boolean}
*/
isDefaultFor: function(filter)
{
if (this.defaults && this.defaults.length)
{
for each (let type in this.defaults)
{
if (filter instanceof SpecialSubscription.defaultsMap[type])
return true;
if (!(filter instanceof ActiveFilter) && type == "blacklist")
return true;
}
}
return false;
},
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
Subscription.prototype.serialize.call(this, buffer);
if (this.defaults && this.defaults.length)
buffer.push("defaults=" + this.defaults.filter(function(type) type in SpecialSubscription.defaultsMap).join(" "));
if (this._lastDownload)
buffer.push("lastDownload=" + this._lastDownload);
}
};
SpecialSubscription.defaultsMap = {
__proto__: null,
"whitelist": WhitelistFilter,
"blocking": BlockingFilter,
"elemhide": ElemHideFilter
};
/**
* Creates a new user-defined filter group.
* @param {String} [title] title of the new filter group
* @result {SpecialSubscription}
*/
SpecialSubscription.create = function(title)
{
let url;
do
{
url = "~user~" + Math.round(Math.random()*1000000);
} while (url in Subscription.knownSubscriptions);
return new SpecialSubscription(url, title)
};
/**
* Creates a new user-defined filter group and adds the given filter to it.
* This group will act as the default group for this filter type.
*/
SpecialSubscription.createForFilter = function(/**Filter*/ filter) /**SpecialSubscription*/
{
let subscription = SpecialSubscription.create();
subscription.filters.push(filter);
for (let type in SpecialSubscription.defaultsMap)
{
if (filter instanceof SpecialSubscription.defaultsMap[type])
subscription.defaults = [type];
}
if (!subscription.defaults)
subscription.defaults = ["blocking"];
subscription.title = Utils.getString(subscription.defaults[0] + "Group_title");
return subscription;
};
/**
* Abstract base class for regular filter subscriptions (both internally and externally updated)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments Subscription
*/
function RegularSubscription(url, title)
{
Subscription.call(this, url, title || url);
}
RegularSubscription.prototype =
{
__proto__: Subscription.prototype,
_homepage: null,
_lastDownload: 0,
/**
* Filter subscription homepage if known
* @type String
*/
get homepage() this._homepage,
set homepage(value)
{
if (value != this._homepage)
{
let oldValue = this._homepage;
this._homepage = value;
FilterNotifier.triggerListeners("subscription.homepage", this, value, oldValue);
}
return this._homepage;
},
/**
* Time of the last subscription download (in seconds since the beginning of the epoch)
* @type Number
*/
get lastDownload() this._lastDownload,
set lastDownload(value)
{
if (value != this._lastDownload)
{
let oldValue = this._lastDownload;
this._lastDownload = value;
FilterNotifier.triggerListeners("subscription.lastDownload", this, value, oldValue);
}
return this._lastDownload;
},
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
Subscription.prototype.serialize.call(this, buffer);
if (this._homepage)
buffer.push("homepage=" + this._homepage);
if (this._lastDownload)
buffer.push("lastDownload=" + this._lastDownload);
}
};
/**
* Class for filter subscriptions updated by externally (by other extension)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments RegularSubscription
*/
function ExternalSubscription(url, title)
{
RegularSubscription.call(this, url, title);
}
ExternalSubscription.prototype =
{
__proto__: RegularSubscription.prototype,
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
throw new Error("Unexpected call, external subscriptions should not be serialized");
}
};
/**
* Class for filter subscriptions updated by externally (by other extension)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments RegularSubscription
*/
function DownloadableSubscription(url, title)
{
RegularSubscription.call(this, url, title);
}
DownloadableSubscription.prototype =
{
__proto__: RegularSubscription.prototype,
_downloadStatus: null,
_lastCheck: 0,
_errors: 0,
/**
* Next URL the downloaded should be attempted from (in case of redirects)
* @type String
*/
nextURL: null,
/**
* Status of the last download (ID of a string)
* @type String
*/
get downloadStatus() this._downloadStatus,
set downloadStatus(value)
{
let oldValue = this._downloadStatus;
this._downloadStatus = value;
FilterNotifier.triggerListeners("subscription.downloadStatus", this, value, oldValue);
return this._downloadStatus;
},
/**
* Value of the Last-Modified header returned by the server on last download
* @type String
*/
lastModified: null,
/**
* Time of the last successful download (in seconds since the beginning of the
* epoch).
*/
lastSuccess: 0,
/**
* Time when the subscription was considered for an update last time (in seconds
* since the beginning of the epoch). This will be used to increase softExpiration
* if the user doesn't use Adblock Plus for some time.
* @type Number
*/
get lastCheck() this._lastCheck,
set lastCheck(value)
{
if (value != this._lastCheck)
{
let oldValue = this._lastCheck;
this._lastCheck = value;
FilterNotifier.triggerListeners("subscription.lastCheck", this, value, oldValue);
}
return this._lastCheck;
},
/**
* Hard expiration time of the filter subscription (in seconds since the beginning of the epoch)
* @type Number
*/
expires: 0,
/**
* Soft expiration time of the filter subscription (in seconds since the beginning of the epoch)
* @type Number
*/
softExpiration: 0,
/**
* Number of download failures since last success
* @type Number
*/
get errors() this._errors,
set errors(value)
{
if (value != this._errors)
{
let oldValue = this._errors;
this._errors = value;
FilterNotifier.triggerListeners("subscription.errors", this, value, oldValue);
}
return this._errors;
},
/**
* Minimal Adblock Plus version required for this subscription
* @type String
*/
requiredVersion: null,
/**
* Should be true if requiredVersion is higher than current Adblock Plus version
* @type Boolean
*/
upgradeRequired: false,
/**
* Value of the X-Alternative-Locations header: comma-separated list of URLs
* with their weighting factors, e.g.: http://foo.example.com/;q=0.5,http://bar.example.com/;q=2
* @type String
*/
alternativeLocations: null,
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
RegularSubscription.prototype.serialize.call(this, buffer);
if (this.nextURL)
buffer.push("nextURL=" + this.nextURL);
if (this.downloadStatus)
buffer.push("downloadStatus=" + this.downloadStatus);
if (this.lastModified)
buffer.push("lastModified=" + this.lastModified);
if (this.lastSuccess)
buffer.push("lastSuccess=" + this.lastSuccess);
if (this.lastCheck)
buffer.push("lastCheck=" + this.lastCheck);
if (this.expires)
buffer.push("expires=" + this.expires);
if (this.softExpiration)
buffer.push("softExpiration=" + this.softExpiration);
if (this.errors)
buffer.push("errors=" + this.errors);
if (this.requiredVersion)
buffer.push("requiredVersion=" + this.requiredVersion);
if (this.alternativeLocations)
buffer.push("alternativeLocations=" + this.alternativeLocations);
}
};