| 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 Manages synchronization of filter subscriptions. |  | 
| 20  */ |  | 
| 21 |  | 
| 22 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |  | 
| 23 Cu.import("resource://gre/modules/Services.jsm"); |  | 
| 24 |  | 
| 25 var {Downloader, Downloadable, |  | 
| 26     MILLIS_IN_SECOND, MILLIS_IN_MINUTE, MILLIS_IN_HOUR, MILLIS_IN_DAY} = require
     ("downloader"); |  | 
| 27 var {Filter, CommentFilter} = require("filterClasses"); |  | 
| 28 var {FilterStorage} = require("filterStorage"); |  | 
| 29 var {FilterNotifier} = require("filterNotifier"); |  | 
| 30 var {Prefs} = require("prefs"); |  | 
| 31 var {Subscription, DownloadableSubscription} = require("subscriptionClasses"); |  | 
| 32 var {Utils} = require("utils"); |  | 
| 33 |  | 
| 34 var INITIAL_DELAY = 1 * MILLIS_IN_MINUTE; |  | 
| 35 var CHECK_INTERVAL = 1 * MILLIS_IN_HOUR; |  | 
| 36 var DEFAULT_EXPIRATION_INTERVAL = 5 * MILLIS_IN_DAY; |  | 
| 37 |  | 
| 38 /** |  | 
| 39  * The object providing actual downloading functionality. |  | 
| 40  * @type Downloader |  | 
| 41  */ |  | 
| 42 var downloader = null; |  | 
| 43 |  | 
| 44 /** |  | 
| 45  * This object is responsible for downloading filter subscriptions whenever |  | 
| 46  * necessary. |  | 
| 47  * @class |  | 
| 48  */ |  | 
| 49 var Synchronizer = exports.Synchronizer = |  | 
| 50 { |  | 
| 51   /** |  | 
| 52    * Called on module startup. |  | 
| 53    */ |  | 
| 54   init: function() |  | 
| 55   { |  | 
| 56     downloader = new Downloader(this._getDownloadables.bind(this), INITIAL_DELAY
     , CHECK_INTERVAL); |  | 
| 57     onShutdown.add(function() |  | 
| 58     { |  | 
| 59       downloader.cancel(); |  | 
| 60     }); |  | 
| 61 |  | 
| 62     downloader.onExpirationChange = this._onExpirationChange.bind(this); |  | 
| 63     downloader.onDownloadStarted = this._onDownloadStarted.bind(this); |  | 
| 64     downloader.onDownloadSuccess = this._onDownloadSuccess.bind(this); |  | 
| 65     downloader.onDownloadError = this._onDownloadError.bind(this); |  | 
| 66   }, |  | 
| 67 |  | 
| 68   /** |  | 
| 69    * Checks whether a subscription is currently being downloaded. |  | 
| 70    * @param {String} url  URL of the subscription |  | 
| 71    * @return {Boolean} |  | 
| 72    */ |  | 
| 73   isExecuting: function(url) |  | 
| 74   { |  | 
| 75     return downloader.isDownloading(url); |  | 
| 76   }, |  | 
| 77 |  | 
| 78   /** |  | 
| 79    * Starts the download of a subscription. |  | 
| 80    * @param {DownloadableSubscription} subscription  Subscription to be download
     ed |  | 
| 81    * @param {Boolean} manual  true for a manually started download (should not t
     rigger fallback requests) |  | 
| 82    */ |  | 
| 83   execute: function(subscription, manual) |  | 
| 84   { |  | 
| 85     downloader.download(this._getDownloadable(subscription, manual)); |  | 
| 86   }, |  | 
| 87 |  | 
| 88   /** |  | 
| 89    * Yields Downloadable instances for all subscriptions that can be downloaded. |  | 
| 90    */ |  | 
| 91   _getDownloadables: function*() |  | 
| 92   { |  | 
| 93     if (!Prefs.subscriptions_autoupdate) |  | 
| 94       return; |  | 
| 95 |  | 
| 96     for (let subscription of FilterStorage.subscriptions) |  | 
| 97     { |  | 
| 98       if (subscription instanceof DownloadableSubscription) |  | 
| 99         yield this._getDownloadable(subscription, false); |  | 
| 100     } |  | 
| 101   }, |  | 
| 102 |  | 
| 103   /** |  | 
| 104    * Creates a Downloadable instance for a subscription. |  | 
| 105    */ |  | 
| 106   _getDownloadable: function(/**Subscription*/ subscription, /**Boolean*/ manual
     ) /**Downloadable*/ |  | 
| 107   { |  | 
| 108     let result = new Downloadable(subscription.url); |  | 
| 109     if (subscription.lastDownload != subscription.lastSuccess) |  | 
| 110       result.lastError = subscription.lastDownload * MILLIS_IN_SECOND; |  | 
| 111     result.lastCheck = subscription.lastCheck * MILLIS_IN_SECOND; |  | 
| 112     result.lastVersion = subscription.version; |  | 
| 113     result.softExpiration = subscription.softExpiration * MILLIS_IN_SECOND; |  | 
| 114     result.hardExpiration = subscription.expires * MILLIS_IN_SECOND; |  | 
| 115     result.manual = manual; |  | 
| 116     result.downloadCount = subscription.downloadCount; |  | 
| 117     return result; |  | 
| 118   }, |  | 
| 119 |  | 
| 120   _onExpirationChange: function(downloadable) |  | 
| 121   { |  | 
| 122     let subscription = Subscription.fromURL(downloadable.url); |  | 
| 123     subscription.lastCheck = Math.round(downloadable.lastCheck / MILLIS_IN_SECON
     D); |  | 
| 124     subscription.softExpiration = Math.round(downloadable.softExpiration / MILLI
     S_IN_SECOND); |  | 
| 125     subscription.expires = Math.round(downloadable.hardExpiration / MILLIS_IN_SE
     COND); |  | 
| 126   }, |  | 
| 127 |  | 
| 128   _onDownloadStarted: function(downloadable) |  | 
| 129   { |  | 
| 130     let subscription = Subscription.fromURL(downloadable.url); |  | 
| 131     FilterNotifier.triggerListeners("subscription.downloadStatus", subscription)
     ; |  | 
| 132   }, |  | 
| 133 |  | 
| 134   _onDownloadSuccess: function(downloadable, responseText, errorCallback, redire
     ctCallback) |  | 
| 135   { |  | 
| 136     let lines = responseText.split(/[\r\n]+/); |  | 
| 137     let match = /\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\]/i.exec(lines[0]); |  | 
| 138     if (!match) |  | 
| 139       return errorCallback("synchronize_invalid_data"); |  | 
| 140     let minVersion = match[1]; |  | 
| 141 |  | 
| 142     // Don't remove parameter comments immediately but add them to a list first, |  | 
| 143     // they need to be considered in the checksum calculation. |  | 
| 144     let remove = []; |  | 
| 145     let params = { |  | 
| 146       redirect: null, |  | 
| 147       homepage: null, |  | 
| 148       title: null, |  | 
| 149       version: null, |  | 
| 150       expires: null |  | 
| 151     }; |  | 
| 152     for (let i = 0; i < lines.length; i++) |  | 
| 153     { |  | 
| 154       let match = /^\s*!\s*(\w+)\s*:\s*(.*)/.exec(lines[i]); |  | 
| 155       if (match) |  | 
| 156       { |  | 
| 157         let keyword = match[1].toLowerCase(); |  | 
| 158         let value = match[2]; |  | 
| 159         if (keyword in params) |  | 
| 160         { |  | 
| 161           params[keyword] = value; |  | 
| 162           remove.push(i); |  | 
| 163         } |  | 
| 164         else if (keyword == "checksum") |  | 
| 165         { |  | 
| 166           lines.splice(i--, 1); |  | 
| 167           let checksum = Utils.generateChecksum(lines); |  | 
| 168           if (checksum && checksum != value.replace(/=+$/, "")) |  | 
| 169             return errorCallback("synchronize_checksum_mismatch"); |  | 
| 170         } |  | 
| 171       } |  | 
| 172     } |  | 
| 173 |  | 
| 174     if (params.redirect) |  | 
| 175       return redirectCallback(params.redirect); |  | 
| 176 |  | 
| 177     // Handle redirects |  | 
| 178     let subscription = Subscription.fromURL(downloadable.redirectURL || download
     able.url); |  | 
| 179     if (downloadable.redirectURL && downloadable.redirectURL != downloadable.url
     ) |  | 
| 180     { |  | 
| 181       let oldSubscription = Subscription.fromURL(downloadable.url); |  | 
| 182       subscription.title = oldSubscription.title; |  | 
| 183       subscription.disabled = oldSubscription.disabled; |  | 
| 184       subscription.lastCheck = oldSubscription.lastCheck; |  | 
| 185 |  | 
| 186       let listed = (oldSubscription.url in FilterStorage.knownSubscriptions); |  | 
| 187       if (listed) |  | 
| 188         FilterStorage.removeSubscription(oldSubscription); |  | 
| 189 |  | 
| 190       delete Subscription.knownSubscriptions[oldSubscription.url]; |  | 
| 191 |  | 
| 192       if (listed) |  | 
| 193         FilterStorage.addSubscription(subscription); |  | 
| 194     } |  | 
| 195 |  | 
| 196     // The download actually succeeded |  | 
| 197     subscription.lastSuccess = subscription.lastDownload = Math.round(Date.now()
      / MILLIS_IN_SECOND); |  | 
| 198     subscription.downloadStatus = "synchronize_ok"; |  | 
| 199     subscription.downloadCount = downloadable.downloadCount; |  | 
| 200     subscription.errors = 0; |  | 
| 201 |  | 
| 202     // Remove lines containing parameters |  | 
| 203     for (let i = remove.length - 1; i >= 0; i--) |  | 
| 204       lines.splice(remove[i], 1); |  | 
| 205 |  | 
| 206     // Process parameters |  | 
| 207     if (params.homepage) |  | 
| 208     { |  | 
| 209       let uri = Utils.makeURI(params.homepage); |  | 
| 210       if (uri && (uri.scheme == "http" || uri.scheme == "https")) |  | 
| 211         subscription.homepage = uri.spec; |  | 
| 212     } |  | 
| 213 |  | 
| 214     if (params.title) |  | 
| 215     { |  | 
| 216       subscription.title = params.title; |  | 
| 217       subscription.fixedTitle = true; |  | 
| 218     } |  | 
| 219     else |  | 
| 220       subscription.fixedTitle = false; |  | 
| 221 |  | 
| 222     subscription.version = (params.version ? parseInt(params.version, 10) : 0); |  | 
| 223 |  | 
| 224     let expirationInterval = DEFAULT_EXPIRATION_INTERVAL; |  | 
| 225     if (params.expires) |  | 
| 226     { |  | 
| 227       let match = /^(\d+)\s*(h)?/.exec(params.expires); |  | 
| 228       if (match) |  | 
| 229       { |  | 
| 230         let interval = parseInt(match[1], 10); |  | 
| 231         if (match[2]) |  | 
| 232           expirationInterval = interval * MILLIS_IN_HOUR; |  | 
| 233         else |  | 
| 234           expirationInterval = interval * MILLIS_IN_DAY; |  | 
| 235       } |  | 
| 236     } |  | 
| 237 |  | 
| 238     let [softExpiration, hardExpiration] = downloader.processExpirationInterval(
     expirationInterval); |  | 
| 239     subscription.softExpiration = Math.round(softExpiration / MILLIS_IN_SECOND); |  | 
| 240     subscription.expires = Math.round(hardExpiration / MILLIS_IN_SECOND); |  | 
| 241 |  | 
| 242     delete subscription.requiredVersion; |  | 
| 243     delete subscription.upgradeRequired; |  | 
| 244     if (minVersion) |  | 
| 245     { |  | 
| 246       let {addonVersion} = require("info"); |  | 
| 247       subscription.requiredVersion = minVersion; |  | 
| 248       if (Services.vc.compare(minVersion, addonVersion) > 0) |  | 
| 249         subscription.upgradeRequired = true; |  | 
| 250     } |  | 
| 251 |  | 
| 252     // Process filters |  | 
| 253     lines.shift(); |  | 
| 254     let filters = []; |  | 
| 255     for (let line of lines) |  | 
| 256     { |  | 
| 257       line = Filter.normalize(line); |  | 
| 258       if (line) |  | 
| 259         filters.push(Filter.fromText(line)); |  | 
| 260     } |  | 
| 261 |  | 
| 262     FilterStorage.updateSubscriptionFilters(subscription, filters); |  | 
| 263 |  | 
| 264     return undefined; |  | 
| 265   }, |  | 
| 266 |  | 
| 267   _onDownloadError: function(downloadable, downloadURL, error, channelStatus, re
     sponseStatus, redirectCallback) |  | 
| 268   { |  | 
| 269     let subscription = Subscription.fromURL(downloadable.url); |  | 
| 270     subscription.lastDownload = Math.round(Date.now() / MILLIS_IN_SECOND); |  | 
| 271     subscription.downloadStatus = error; |  | 
| 272 |  | 
| 273     // Request fallback URL if necessary - for automatic updates only |  | 
| 274     if (!downloadable.manual) |  | 
| 275     { |  | 
| 276       subscription.errors++; |  | 
| 277 |  | 
| 278       if (redirectCallback && subscription.errors >= Prefs.subscriptions_fallbac
     kerrors && /^https?:\/\//i.test(subscription.url)) |  | 
| 279       { |  | 
| 280         subscription.errors = 0; |  | 
| 281 |  | 
| 282         let fallbackURL = Prefs.subscriptions_fallbackurl; |  | 
| 283         let {addonVersion} = require("info"); |  | 
| 284         fallbackURL = fallbackURL.replace(/%VERSION%/g, encodeURIComponent(addon
     Version)); |  | 
| 285         fallbackURL = fallbackURL.replace(/%SUBSCRIPTION%/g, encodeURIComponent(
     subscription.url)); |  | 
| 286         fallbackURL = fallbackURL.replace(/%URL%/g, encodeURIComponent(downloadU
     RL)); |  | 
| 287         fallbackURL = fallbackURL.replace(/%ERROR%/g, encodeURIComponent(error))
     ; |  | 
| 288         fallbackURL = fallbackURL.replace(/%CHANNELSTATUS%/g, encodeURIComponent
     (channelStatus)); |  | 
| 289         fallbackURL = fallbackURL.replace(/%RESPONSESTATUS%/g, encodeURIComponen
     t(responseStatus)); |  | 
| 290 |  | 
| 291         let request = new XMLHttpRequest(); |  | 
| 292         request.mozBackgroundRequest = true; |  | 
| 293         request.open("GET", fallbackURL); |  | 
| 294         request.overrideMimeType("text/plain"); |  | 
| 295         request.channel.loadFlags = request.channel.loadFlags | |  | 
| 296                                     request.channel.INHIBIT_CACHING | |  | 
| 297                                     request.channel.VALIDATE_ALWAYS; |  | 
| 298         request.addEventListener("load", function(ev) |  | 
| 299         { |  | 
| 300           if (onShutdown.done) |  | 
| 301             return; |  | 
| 302 |  | 
| 303           if (!(subscription.url in FilterStorage.knownSubscriptions)) |  | 
| 304             return; |  | 
| 305 |  | 
| 306           let match = /^(\d+)(?:\s+(\S+))?$/.exec(request.responseText); |  | 
| 307           if (match && match[1] == "301" && match[2] && /^https?:\/\//i.test(mat
     ch[2])) // Moved permanently |  | 
| 308             redirectCallback(match[2]); |  | 
| 309           else if (match && match[1] == "410")        // Gone |  | 
| 310           { |  | 
| 311             let data = "[Adblock]\n" + subscription.filters.map((f) => f.text).j
     oin("\n"); |  | 
| 312             redirectCallback("data:text/plain," + encodeURIComponent(data)); |  | 
| 313           } |  | 
| 314         }, false); |  | 
| 315         request.send(null); |  | 
| 316       } |  | 
| 317     } |  | 
| 318   }, |  | 
| 319 }; |  | 
| 320 Synchronizer.init(); |  | 
| OLD | NEW | 
|---|