| 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 | 
|---|