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