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 |