| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | |
| 3 * Copyright (C) 2006-2016 Eyeo GmbH | |
| 4 * | |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | |
| 6 * it under the terms of the GNU General Public License version 3 as | |
| 7 * published by the Free Software Foundation. | |
| 8 * | |
| 9 * Adblock Plus is distributed in the hope that it will be useful, | |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 * GNU General Public License for more details. | |
| 13 * | |
| 14 * You should have received a copy of the GNU General Public License | |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | |
| 16 */ | |
| 17 | |
| 18 /** | |
| 19 * @fileOverview Definition of Subscription class and its subclasses. | |
| 20 */ | |
| 21 | |
| 22 Cu.import("resource://gre/modules/Services.jsm"); | |
| 23 | |
| 24 let {ActiveFilter, BlockingFilter, WhitelistFilter, ElemHideBase} = require("fil
terClasses"); | |
| 25 let {FilterNotifier} = require("filterNotifier"); | |
| 26 | |
| 27 /** | |
| 28 * Abstract base class for filter subscriptions | |
| 29 * | |
| 30 * @param {String} url download location of the subscription | |
| 31 * @param {String} [title] title of the filter subscription | |
| 32 * @constructor | |
| 33 */ | |
| 34 function Subscription(url, title) | |
| 35 { | |
| 36 this.url = url; | |
| 37 this.filters = []; | |
| 38 if (title) | |
| 39 this._title = title; | |
| 40 else | |
| 41 { | |
| 42 let {Utils} = require("utils"); | |
| 43 this._title = Utils.getString("newGroup_title"); | |
| 44 } | |
| 45 Subscription.knownSubscriptions[url] = this; | |
| 46 } | |
| 47 exports.Subscription = Subscription; | |
| 48 | |
| 49 Subscription.prototype = | |
| 50 { | |
| 51 /** | |
| 52 * Download location of the subscription | |
| 53 * @type String | |
| 54 */ | |
| 55 url: null, | |
| 56 | |
| 57 /** | |
| 58 * Filters contained in the filter subscription | |
| 59 * @type Filter[] | |
| 60 */ | |
| 61 filters: null, | |
| 62 | |
| 63 _title: null, | |
| 64 _fixedTitle: false, | |
| 65 _disabled: false, | |
| 66 | |
| 67 /** | |
| 68 * Title of the filter subscription | |
| 69 * @type String | |
| 70 */ | |
| 71 get title() | |
| 72 { | |
| 73 return this._title; | |
| 74 }, | |
| 75 set title(value) | |
| 76 { | |
| 77 if (value != this._title) | |
| 78 { | |
| 79 let oldValue = this._title; | |
| 80 this._title = value; | |
| 81 FilterNotifier.triggerListeners("subscription.title", this, value, oldValu
e); | |
| 82 } | |
| 83 return this._title; | |
| 84 }, | |
| 85 | |
| 86 /** | |
| 87 * Determines whether the title should be editable | |
| 88 * @type Boolean | |
| 89 */ | |
| 90 get fixedTitle() | |
| 91 { | |
| 92 return this._fixedTitle; | |
| 93 }, | |
| 94 set fixedTitle(value) | |
| 95 { | |
| 96 if (value != this._fixedTitle) | |
| 97 { | |
| 98 let oldValue = this._fixedTitle; | |
| 99 this._fixedTitle = value; | |
| 100 FilterNotifier.triggerListeners("subscription.fixedTitle", this, value, ol
dValue); | |
| 101 } | |
| 102 return this._fixedTitle; | |
| 103 }, | |
| 104 | |
| 105 /** | |
| 106 * Defines whether the filters in the subscription should be disabled | |
| 107 * @type Boolean | |
| 108 */ | |
| 109 get disabled() | |
| 110 { | |
| 111 return this._disabled; | |
| 112 }, | |
| 113 set disabled(value) | |
| 114 { | |
| 115 if (value != this._disabled) | |
| 116 { | |
| 117 let oldValue = this._disabled; | |
| 118 this._disabled = value; | |
| 119 FilterNotifier.triggerListeners("subscription.disabled", this, value, oldV
alue); | |
| 120 } | |
| 121 return this._disabled; | |
| 122 }, | |
| 123 | |
| 124 /** | |
| 125 * Serializes the subscription to an array of strings for writing out on the d
isk. | |
| 126 * @param {string[]} buffer buffer to push the serialization results into | |
| 127 */ | |
| 128 serialize: function(buffer) | |
| 129 { | |
| 130 buffer.push("[Subscription]"); | |
| 131 buffer.push("url=" + this.url); | |
| 132 buffer.push("title=" + this._title); | |
| 133 if (this._fixedTitle) | |
| 134 buffer.push("fixedTitle=true"); | |
| 135 if (this._disabled) | |
| 136 buffer.push("disabled=true"); | |
| 137 }, | |
| 138 | |
| 139 serializeFilters: function(buffer) | |
| 140 { | |
| 141 for (let filter of this.filters) | |
| 142 buffer.push(filter.text.replace(/\[/g, "\\[")); | |
| 143 }, | |
| 144 | |
| 145 toString: function() | |
| 146 { | |
| 147 let buffer = []; | |
| 148 this.serialize(buffer); | |
| 149 return buffer.join("\n"); | |
| 150 } | |
| 151 }; | |
| 152 | |
| 153 /** | |
| 154 * Cache for known filter subscriptions, maps URL to subscription objects. | |
| 155 * @type Object | |
| 156 */ | |
| 157 Subscription.knownSubscriptions = Object.create(null); | |
| 158 | |
| 159 /** | |
| 160 * Returns a subscription from its URL, creates a new one if necessary. | |
| 161 * @param {String} url URL of the subscription | |
| 162 * @return {Subscription} subscription or null if the subscription couldn't be c
reated | |
| 163 */ | |
| 164 Subscription.fromURL = function(url) | |
| 165 { | |
| 166 if (url in Subscription.knownSubscriptions) | |
| 167 return Subscription.knownSubscriptions[url]; | |
| 168 | |
| 169 try | |
| 170 { | |
| 171 // Test URL for validity | |
| 172 url = Services.io.newURI(url, null, null).spec; | |
| 173 return new DownloadableSubscription(url, null); | |
| 174 } | |
| 175 catch (e) | |
| 176 { | |
| 177 return new SpecialSubscription(url); | |
| 178 } | |
| 179 }; | |
| 180 | |
| 181 /** | |
| 182 * Deserializes a subscription | |
| 183 * | |
| 184 * @param {Object} obj map of serialized properties and their values | |
| 185 * @return {Subscription} subscription or null if the subscription couldn't be c
reated | |
| 186 */ | |
| 187 Subscription.fromObject = function(obj) | |
| 188 { | |
| 189 let result; | |
| 190 try | |
| 191 { | |
| 192 obj.url = Services.io.newURI(obj.url, null, null).spec; | |
| 193 | |
| 194 // URL is valid - this is a downloadable subscription | |
| 195 result = new DownloadableSubscription(obj.url, obj.title); | |
| 196 if ("downloadStatus" in obj) | |
| 197 result._downloadStatus = obj.downloadStatus; | |
| 198 if ("lastSuccess" in obj) | |
| 199 result.lastSuccess = parseInt(obj.lastSuccess, 10) || 0; | |
| 200 if ("lastCheck" in obj) | |
| 201 result._lastCheck = parseInt(obj.lastCheck, 10) || 0; | |
| 202 if ("expires" in obj) | |
| 203 result.expires = parseInt(obj.expires, 10) || 0; | |
| 204 if ("softExpiration" in obj) | |
| 205 result.softExpiration = parseInt(obj.softExpiration, 10) || 0; | |
| 206 if ("errors" in obj) | |
| 207 result._errors = parseInt(obj.errors, 10) || 0; | |
| 208 if ("version" in obj) | |
| 209 result.version = parseInt(obj.version, 10) || 0; | |
| 210 if ("requiredVersion" in obj) | |
| 211 { | |
| 212 let {addonVersion} = require("info"); | |
| 213 result.requiredVersion = obj.requiredVersion; | |
| 214 if (Services.vc.compare(result.requiredVersion, addonVersion) > 0) | |
| 215 result.upgradeRequired = true; | |
| 216 } | |
| 217 if ("homepage" in obj) | |
| 218 result._homepage = obj.homepage; | |
| 219 if ("lastDownload" in obj) | |
| 220 result._lastDownload = parseInt(obj.lastDownload, 10) || 0; | |
| 221 if ("downloadCount" in obj) | |
| 222 result.downloadCount = parseInt(obj.downloadCount, 10) || 0; | |
| 223 } | |
| 224 catch (e) | |
| 225 { | |
| 226 // Invalid URL - custom filter group | |
| 227 if (!("title" in obj)) | |
| 228 { | |
| 229 // Backwards compatibility - titles and filter types were originally | |
| 230 // determined by group identifier. | |
| 231 if (obj.url == "~wl~") | |
| 232 obj.defaults = "whitelist"; | |
| 233 else if (obj.url == "~fl~") | |
| 234 obj.defaults = "blocking"; | |
| 235 else if (obj.url == "~eh~") | |
| 236 obj.defaults = "elemhide"; | |
| 237 if ("defaults" in obj) | |
| 238 { | |
| 239 let {Utils} = require("utils"); | |
| 240 obj.title = Utils.getString(obj.defaults + "Group_title"); | |
| 241 } | |
| 242 } | |
| 243 result = new SpecialSubscription(obj.url, obj.title); | |
| 244 if ("defaults" in obj) | |
| 245 result.defaults = obj.defaults.split(" "); | |
| 246 } | |
| 247 if ("fixedTitle" in obj) | |
| 248 result._fixedTitle = (obj.fixedTitle == "true"); | |
| 249 if ("disabled" in obj) | |
| 250 result._disabled = (obj.disabled == "true"); | |
| 251 | |
| 252 return result; | |
| 253 }; | |
| 254 | |
| 255 /** | |
| 256 * Class for special filter subscriptions (user's filters) | |
| 257 * @param {String} url see Subscription() | |
| 258 * @param {String} [title] see Subscription() | |
| 259 * @constructor | |
| 260 * @augments Subscription | |
| 261 */ | |
| 262 function SpecialSubscription(url, title) | |
| 263 { | |
| 264 Subscription.call(this, url, title); | |
| 265 } | |
| 266 exports.SpecialSubscription = SpecialSubscription; | |
| 267 | |
| 268 SpecialSubscription.prototype = | |
| 269 { | |
| 270 __proto__: Subscription.prototype, | |
| 271 | |
| 272 /** | |
| 273 * Filter types that should be added to this subscription by default | |
| 274 * (entries should correspond to keys in SpecialSubscription.defaultsMap). | |
| 275 * @type string[] | |
| 276 */ | |
| 277 defaults: null, | |
| 278 | |
| 279 /** | |
| 280 * Tests whether a filter should be added to this group by default | |
| 281 * @param {Filter} filter filter to be tested | |
| 282 * @return {Boolean} | |
| 283 */ | |
| 284 isDefaultFor: function(filter) | |
| 285 { | |
| 286 if (this.defaults && this.defaults.length) | |
| 287 { | |
| 288 for (let type of this.defaults) | |
| 289 { | |
| 290 if (filter instanceof SpecialSubscription.defaultsMap[type]) | |
| 291 return true; | |
| 292 if (!(filter instanceof ActiveFilter) && type == "blacklist") | |
| 293 return true; | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 return false; | |
| 298 }, | |
| 299 | |
| 300 /** | |
| 301 * See Subscription.serialize() | |
| 302 */ | |
| 303 serialize: function(buffer) | |
| 304 { | |
| 305 Subscription.prototype.serialize.call(this, buffer); | |
| 306 if (this.defaults && this.defaults.length) | |
| 307 buffer.push("defaults=" + this.defaults.filter((type) => type in SpecialSu
bscription.defaultsMap).join(" ")); | |
| 308 if (this._lastDownload) | |
| 309 buffer.push("lastDownload=" + this._lastDownload); | |
| 310 } | |
| 311 }; | |
| 312 | |
| 313 SpecialSubscription.defaultsMap = { | |
| 314 __proto__: null, | |
| 315 "whitelist": WhitelistFilter, | |
| 316 "blocking": BlockingFilter, | |
| 317 "elemhide": ElemHideBase | |
| 318 }; | |
| 319 | |
| 320 /** | |
| 321 * Creates a new user-defined filter group. | |
| 322 * @param {String} [title] title of the new filter group | |
| 323 * @result {SpecialSubscription} | |
| 324 */ | |
| 325 SpecialSubscription.create = function(title) | |
| 326 { | |
| 327 let url; | |
| 328 do | |
| 329 { | |
| 330 url = "~user~" + Math.round(Math.random()*1000000); | |
| 331 } while (url in Subscription.knownSubscriptions); | |
| 332 return new SpecialSubscription(url, title); | |
| 333 }; | |
| 334 | |
| 335 /** | |
| 336 * Creates a new user-defined filter group and adds the given filter to it. | |
| 337 * This group will act as the default group for this filter type. | |
| 338 */ | |
| 339 SpecialSubscription.createForFilter = function(/**Filter*/ filter) /**SpecialSub
scription*/ | |
| 340 { | |
| 341 let subscription = SpecialSubscription.create(); | |
| 342 subscription.filters.push(filter); | |
| 343 for (let type in SpecialSubscription.defaultsMap) | |
| 344 { | |
| 345 if (filter instanceof SpecialSubscription.defaultsMap[type]) | |
| 346 subscription.defaults = [type]; | |
| 347 } | |
| 348 if (!subscription.defaults) | |
| 349 subscription.defaults = ["blocking"]; | |
| 350 | |
| 351 let {Utils} = require("utils"); | |
| 352 subscription.title = Utils.getString(subscription.defaults[0] + "Group_title")
; | |
| 353 return subscription; | |
| 354 }; | |
| 355 | |
| 356 /** | |
| 357 * Abstract base class for regular filter subscriptions (both internally and ext
ernally updated) | |
| 358 * @param {String} url see Subscription() | |
| 359 * @param {String} [title] see Subscription() | |
| 360 * @constructor | |
| 361 * @augments Subscription | |
| 362 */ | |
| 363 function RegularSubscription(url, title) | |
| 364 { | |
| 365 Subscription.call(this, url, title || url); | |
| 366 } | |
| 367 exports.RegularSubscription = RegularSubscription; | |
| 368 | |
| 369 RegularSubscription.prototype = | |
| 370 { | |
| 371 __proto__: Subscription.prototype, | |
| 372 | |
| 373 _homepage: null, | |
| 374 _lastDownload: 0, | |
| 375 | |
| 376 /** | |
| 377 * Filter subscription homepage if known | |
| 378 * @type String | |
| 379 */ | |
| 380 get homepage() | |
| 381 { | |
| 382 return this._homepage; | |
| 383 }, | |
| 384 set homepage(value) | |
| 385 { | |
| 386 if (value != this._homepage) | |
| 387 { | |
| 388 let oldValue = this._homepage; | |
| 389 this._homepage = value; | |
| 390 FilterNotifier.triggerListeners("subscription.homepage", this, value, oldV
alue); | |
| 391 } | |
| 392 return this._homepage; | |
| 393 }, | |
| 394 | |
| 395 /** | |
| 396 * Time of the last subscription download (in seconds since the beginning of t
he epoch) | |
| 397 * @type Number | |
| 398 */ | |
| 399 get lastDownload() | |
| 400 { | |
| 401 return this._lastDownload; | |
| 402 }, | |
| 403 set lastDownload(value) | |
| 404 { | |
| 405 if (value != this._lastDownload) | |
| 406 { | |
| 407 let oldValue = this._lastDownload; | |
| 408 this._lastDownload = value; | |
| 409 FilterNotifier.triggerListeners("subscription.lastDownload", this, value,
oldValue); | |
| 410 } | |
| 411 return this._lastDownload; | |
| 412 }, | |
| 413 | |
| 414 /** | |
| 415 * See Subscription.serialize() | |
| 416 */ | |
| 417 serialize: function(buffer) | |
| 418 { | |
| 419 Subscription.prototype.serialize.call(this, buffer); | |
| 420 if (this._homepage) | |
| 421 buffer.push("homepage=" + this._homepage); | |
| 422 if (this._lastDownload) | |
| 423 buffer.push("lastDownload=" + this._lastDownload); | |
| 424 } | |
| 425 }; | |
| 426 | |
| 427 /** | |
| 428 * Class for filter subscriptions updated externally (by other extension) | |
| 429 * @param {String} url see Subscription() | |
| 430 * @param {String} [title] see Subscription() | |
| 431 * @constructor | |
| 432 * @augments RegularSubscription | |
| 433 */ | |
| 434 function ExternalSubscription(url, title) | |
| 435 { | |
| 436 RegularSubscription.call(this, url, title); | |
| 437 } | |
| 438 exports.ExternalSubscription = ExternalSubscription; | |
| 439 | |
| 440 ExternalSubscription.prototype = | |
| 441 { | |
| 442 __proto__: RegularSubscription.prototype, | |
| 443 | |
| 444 /** | |
| 445 * See Subscription.serialize() | |
| 446 */ | |
| 447 serialize: function(buffer) | |
| 448 { | |
| 449 throw new Error("Unexpected call, external subscriptions should not be seria
lized"); | |
| 450 } | |
| 451 }; | |
| 452 | |
| 453 /** | |
| 454 * Class for filter subscriptions updated externally (by other extension) | |
| 455 * @param {String} url see Subscription() | |
| 456 * @param {String} [title] see Subscription() | |
| 457 * @constructor | |
| 458 * @augments RegularSubscription | |
| 459 */ | |
| 460 function DownloadableSubscription(url, title) | |
| 461 { | |
| 462 RegularSubscription.call(this, url, title); | |
| 463 } | |
| 464 exports.DownloadableSubscription = DownloadableSubscription; | |
| 465 | |
| 466 DownloadableSubscription.prototype = | |
| 467 { | |
| 468 __proto__: RegularSubscription.prototype, | |
| 469 | |
| 470 _downloadStatus: null, | |
| 471 _lastCheck: 0, | |
| 472 _errors: 0, | |
| 473 | |
| 474 /** | |
| 475 * Status of the last download (ID of a string) | |
| 476 * @type String | |
| 477 */ | |
| 478 get downloadStatus() | |
| 479 { | |
| 480 return this._downloadStatus; | |
| 481 }, | |
| 482 set downloadStatus(value) | |
| 483 { | |
| 484 let oldValue = this._downloadStatus; | |
| 485 this._downloadStatus = value; | |
| 486 FilterNotifier.triggerListeners("subscription.downloadStatus", this, value,
oldValue); | |
| 487 return this._downloadStatus; | |
| 488 }, | |
| 489 | |
| 490 /** | |
| 491 * Time of the last successful download (in seconds since the beginning of the | |
| 492 * epoch). | |
| 493 */ | |
| 494 lastSuccess: 0, | |
| 495 | |
| 496 /** | |
| 497 * Time when the subscription was considered for an update last time (in secon
ds | |
| 498 * since the beginning of the epoch). This will be used to increase softExpira
tion | |
| 499 * if the user doesn't use Adblock Plus for some time. | |
| 500 * @type Number | |
| 501 */ | |
| 502 get lastCheck() | |
| 503 { | |
| 504 return this._lastCheck; | |
| 505 }, | |
| 506 set lastCheck(value) | |
| 507 { | |
| 508 if (value != this._lastCheck) | |
| 509 { | |
| 510 let oldValue = this._lastCheck; | |
| 511 this._lastCheck = value; | |
| 512 FilterNotifier.triggerListeners("subscription.lastCheck", this, value, old
Value); | |
| 513 } | |
| 514 return this._lastCheck; | |
| 515 }, | |
| 516 | |
| 517 /** | |
| 518 * Hard expiration time of the filter subscription (in seconds since the begin
ning of the epoch) | |
| 519 * @type Number | |
| 520 */ | |
| 521 expires: 0, | |
| 522 | |
| 523 /** | |
| 524 * Soft expiration time of the filter subscription (in seconds since the begin
ning of the epoch) | |
| 525 * @type Number | |
| 526 */ | |
| 527 softExpiration: 0, | |
| 528 | |
| 529 /** | |
| 530 * Number of download failures since last success | |
| 531 * @type Number | |
| 532 */ | |
| 533 get errors() | |
| 534 { | |
| 535 return this._errors; | |
| 536 }, | |
| 537 set errors(value) | |
| 538 { | |
| 539 if (value != this._errors) | |
| 540 { | |
| 541 let oldValue = this._errors; | |
| 542 this._errors = value; | |
| 543 FilterNotifier.triggerListeners("subscription.errors", this, value, oldVal
ue); | |
| 544 } | |
| 545 return this._errors; | |
| 546 }, | |
| 547 | |
| 548 /** | |
| 549 * Version of the subscription data retrieved on last successful download | |
| 550 * @type Number | |
| 551 */ | |
| 552 version: 0, | |
| 553 | |
| 554 /** | |
| 555 * Minimal Adblock Plus version required for this subscription | |
| 556 * @type String | |
| 557 */ | |
| 558 requiredVersion: null, | |
| 559 | |
| 560 /** | |
| 561 * Should be true if requiredVersion is higher than current Adblock Plus versi
on | |
| 562 * @type Boolean | |
| 563 */ | |
| 564 upgradeRequired: false, | |
| 565 | |
| 566 /** | |
| 567 * Number indicating how often the object was downloaded. | |
| 568 * @type Number | |
| 569 */ | |
| 570 downloadCount: 0, | |
| 571 | |
| 572 /** | |
| 573 * See Subscription.serialize() | |
| 574 */ | |
| 575 serialize: function(buffer) | |
| 576 { | |
| 577 RegularSubscription.prototype.serialize.call(this, buffer); | |
| 578 if (this.downloadStatus) | |
| 579 buffer.push("downloadStatus=" + this.downloadStatus); | |
| 580 if (this.lastSuccess) | |
| 581 buffer.push("lastSuccess=" + this.lastSuccess); | |
| 582 if (this.lastCheck) | |
| 583 buffer.push("lastCheck=" + this.lastCheck); | |
| 584 if (this.expires) | |
| 585 buffer.push("expires=" + this.expires); | |
| 586 if (this.softExpiration) | |
| 587 buffer.push("softExpiration=" + this.softExpiration); | |
| 588 if (this.errors) | |
| 589 buffer.push("errors=" + this.errors); | |
| 590 if (this.version) | |
| 591 buffer.push("version=" + this.version); | |
| 592 if (this.requiredVersion) | |
| 593 buffer.push("requiredVersion=" + this.requiredVersion); | |
| 594 if (this.downloadCount) | |
| 595 buffer.push("downloadCount=" + this.downloadCount); | |
| 596 } | |
| 597 }; | |
| OLD | NEW |