1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5this.EXPORTED_SYMBOLS = ["SafeBrowsing"]; 6 7const Cc = Components.classes; 8const Ci = Components.interfaces; 9const Cu = Components.utils; 10 11Cu.import("resource://gre/modules/Services.jsm"); 12 13// Log only if browser.safebrowsing.debug is true 14function log(...stuff) { 15 let logging = null; 16 try { 17 logging = Services.prefs.getBoolPref("browser.safebrowsing.debug"); 18 } catch(e) { 19 return; 20 } 21 if (!logging) { 22 return; 23 } 24 25 var d = new Date(); 26 let msg = "SafeBrowsing: " + d.toTimeString() + ": " + stuff.join(" "); 27 dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n"); 28} 29 30function getLists(prefName) { 31 log("getLists: " + prefName); 32 let pref = null; 33 try { 34 pref = Services.prefs.getCharPref(prefName); 35 } catch(e) { 36 return null; 37 } 38 // Splitting an empty string returns [''], we really want an empty array. 39 if (!pref) { 40 return []; 41 } 42 return pref.split(",") 43 .map(function(value) { return value.trim(); }); 44} 45 46const tablePreferences = [ 47 "urlclassifier.phishTable", 48 "urlclassifier.malwareTable", 49 "urlclassifier.downloadBlockTable", 50 "urlclassifier.downloadAllowTable", 51 "urlclassifier.trackingTable", 52 "urlclassifier.trackingWhitelistTable", 53 "urlclassifier.blockedTable" 54]; 55 56this.SafeBrowsing = { 57 58 init: function() { 59 if (this.initialized) { 60 log("Already initialized"); 61 return; 62 } 63 64 Services.prefs.addObserver("browser.safebrowsing", this, false); 65 Services.prefs.addObserver("privacy.trackingprotection", this, false); 66 Services.prefs.addObserver("urlclassifier", this, false); 67 68 this.readPrefs(); 69 this.addMozEntries(); 70 71 this.controlUpdateChecking(); 72 this.initialized = true; 73 74 log("init() finished"); 75 }, 76 77 registerTableWithURLs: function(listname) { 78 let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"]. 79 getService(Ci.nsIUrlListManager); 80 81 let providerName = this.listToProvider[listname]; 82 let provider = this.providers[providerName]; 83 84 if (!providerName || !provider) { 85 log("No provider info found for " + listname); 86 log("Check browser.safebrowsing.provider.[google/mozilla].lists"); 87 return; 88 } 89 90 listManager.registerTable(listname, providerName, provider.updateURL, provider.gethashURL); 91 }, 92 93 registerTables: function() { 94 for (let i = 0; i < this.phishingLists.length; ++i) { 95 this.registerTableWithURLs(this.phishingLists[i]); 96 } 97 for (let i = 0; i < this.malwareLists.length; ++i) { 98 this.registerTableWithURLs(this.malwareLists[i]); 99 } 100 for (let i = 0; i < this.downloadBlockLists.length; ++i) { 101 this.registerTableWithURLs(this.downloadBlockLists[i]); 102 } 103 for (let i = 0; i < this.downloadAllowLists.length; ++i) { 104 this.registerTableWithURLs(this.downloadAllowLists[i]); 105 } 106 for (let i = 0; i < this.trackingProtectionLists.length; ++i) { 107 this.registerTableWithURLs(this.trackingProtectionLists[i]); 108 } 109 for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) { 110 this.registerTableWithURLs(this.trackingProtectionWhitelists[i]); 111 } 112 for (let i = 0; i < this.blockedLists.length; ++i) { 113 this.registerTableWithURLs(this.blockedLists[i]); 114 } 115 }, 116 117 118 initialized: false, 119 phishingEnabled: false, 120 malwareEnabled: false, 121 trackingEnabled: false, 122 blockedEnabled: false, 123 124 phishingLists: [], 125 malwareLists: [], 126 downloadBlockLists: [], 127 downloadAllowLists: [], 128 trackingProtectionLists: [], 129 trackingProtectionWhitelists: [], 130 blockedLists: [], 131 132 updateURL: null, 133 gethashURL: null, 134 135 reportURL: null, 136 137 getReportURL: function(kind, URI) { 138 let pref; 139 switch (kind) { 140 case "Phish": 141 pref = "browser.safebrowsing.reportPhishURL"; 142 break; 143 case "PhishMistake": 144 pref = "browser.safebrowsing.reportPhishMistakeURL"; 145 break; 146 case "MalwareMistake": 147 pref = "browser.safebrowsing.reportMalwareMistakeURL"; 148 break; 149 150 default: 151 let err = "SafeBrowsing getReportURL() called with unknown kind: " + kind; 152 Components.utils.reportError(err); 153 throw err; 154 } 155 let reportUrl = Services.urlFormatter.formatURLPref(pref); 156 157 let pageUri = URI.clone(); 158 159 // Remove the query to avoid including potentially sensitive data 160 if (pageUri instanceof Ci.nsIURL) 161 pageUri.query = ''; 162 163 reportUrl += encodeURIComponent(pageUri.asciiSpec); 164 165 return reportUrl; 166 }, 167 168 observe: function(aSubject, aTopic, aData) { 169 // skip nextupdatetime and lastupdatetime 170 if (aData.indexOf("lastupdatetime") >= 0 || aData.indexOf("nextupdatetime") >= 0) { 171 return; 172 } 173 this.readPrefs(); 174 }, 175 176 readPrefs: function() { 177 log("reading prefs"); 178 179 this.debug = Services.prefs.getBoolPref("browser.safebrowsing.debug"); 180 this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled"); 181 this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled"); 182 this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled"); 183 this.blockedEnabled = Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled"); 184 185 [this.phishingLists, 186 this.malwareLists, 187 this.downloadBlockLists, 188 this.downloadAllowLists, 189 this.trackingProtectionLists, 190 this.trackingProtectionWhitelists, 191 this.blockedLists] = tablePreferences.map(getLists); 192 193 this.updateProviderURLs(); 194 this.registerTables(); 195 196 // XXX The listManager backend gets confused if this is called before the 197 // lists are registered. So only call it here when a pref changes, and not 198 // when doing initialization. I expect to refactor this later, so pardon the hack. 199 if (this.initialized) { 200 this.controlUpdateChecking(); 201 } 202 }, 203 204 205 updateProviderURLs: function() { 206 try { 207 var clientID = Services.prefs.getCharPref("browser.safebrowsing.id"); 208 } catch(e) { 209 clientID = Services.appinfo.name; 210 } 211 212 log("initializing safe browsing URLs, client id", clientID); 213 214 // Get the different providers 215 let branch = Services.prefs.getBranch("browser.safebrowsing.provider."); 216 let children = branch.getChildList("", {}); 217 this.providers = {}; 218 this.listToProvider = {}; 219 220 for (let child of children) { 221 log("Child: " + child); 222 let prefComponents = child.split("."); 223 let providerName = prefComponents[0]; 224 this.providers[providerName] = {}; 225 } 226 227 if (this.debug) { 228 let providerStr = ""; 229 Object.keys(this.providers).forEach(function(provider) { 230 if (providerStr === "") { 231 providerStr = provider; 232 } else { 233 providerStr += ", " + provider; 234 } 235 }); 236 log("Providers: " + providerStr); 237 } 238 239 Object.keys(this.providers).forEach(function(provider) { 240 let updateURL = Services.urlFormatter.formatURLPref( 241 "browser.safebrowsing.provider." + provider + ".updateURL"); 242 let gethashURL = Services.urlFormatter.formatURLPref( 243 "browser.safebrowsing.provider." + provider + ".gethashURL"); 244 updateURL = updateURL.replace("SAFEBROWSING_ID", clientID); 245 gethashURL = gethashURL.replace("SAFEBROWSING_ID", clientID); 246 247 log("Provider: " + provider + " updateURL=" + updateURL); 248 log("Provider: " + provider + " gethashURL=" + gethashURL); 249 250 // Urls used to update DB 251 this.providers[provider].updateURL = updateURL; 252 this.providers[provider].gethashURL = gethashURL; 253 254 // Get lists this provider manages 255 let lists = getLists("browser.safebrowsing.provider." + provider + ".lists"); 256 if (lists) { 257 lists.forEach(function(list) { 258 this.listToProvider[list] = provider; 259 }, this); 260 } else { 261 log("Update URL given but no lists managed for provider: " + provider); 262 } 263 }, this); 264 }, 265 266 controlUpdateChecking: function() { 267 log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:", 268 this.malwareEnabled, "trackingEnabled:", this.trackingEnabled, 269 "blockedEnabled:", this.blockedEnabled); 270 271 let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"]. 272 getService(Ci.nsIUrlListManager); 273 274 for (let i = 0; i < this.phishingLists.length; ++i) { 275 if (this.phishingEnabled) { 276 listManager.enableUpdate(this.phishingLists[i]); 277 } else { 278 listManager.disableUpdate(this.phishingLists[i]); 279 } 280 } 281 for (let i = 0; i < this.malwareLists.length; ++i) { 282 if (this.malwareEnabled) { 283 listManager.enableUpdate(this.malwareLists[i]); 284 } else { 285 listManager.disableUpdate(this.malwareLists[i]); 286 } 287 } 288 for (let i = 0; i < this.downloadBlockLists.length; ++i) { 289 if (this.malwareEnabled) { 290 listManager.enableUpdate(this.downloadBlockLists[i]); 291 } else { 292 listManager.disableUpdate(this.downloadBlockLists[i]); 293 } 294 } 295 for (let i = 0; i < this.downloadAllowLists.length; ++i) { 296 if (this.malwareEnabled) { 297 listManager.enableUpdate(this.downloadAllowLists[i]); 298 } else { 299 listManager.disableUpdate(this.downloadAllowLists[i]); 300 } 301 } 302 for (let i = 0; i < this.trackingProtectionLists.length; ++i) { 303 if (this.trackingEnabled) { 304 listManager.enableUpdate(this.trackingProtectionLists[i]); 305 } else { 306 listManager.disableUpdate(this.trackingProtectionLists[i]); 307 } 308 } 309 for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) { 310 if (this.trackingEnabled) { 311 listManager.enableUpdate(this.trackingProtectionWhitelists[i]); 312 } else { 313 listManager.disableUpdate(this.trackingProtectionWhitelists[i]); 314 } 315 } 316 for (let i = 0; i < this.blockedLists.length; ++i) { 317 if (this.blockedEnabled) { 318 listManager.enableUpdate(this.blockedLists[i]); 319 } else { 320 listManager.disableUpdate(this.blockedLists[i]); 321 } 322 } 323 listManager.maybeToggleUpdateChecking(); 324 }, 325 326 327 addMozEntries: function() { 328 // Add test entries to the DB. 329 // XXX bug 779008 - this could be done by DB itself? 330 const phishURL = "itisatrap.org/firefox/its-a-trap.html"; 331 const malwareURL = "itisatrap.org/firefox/its-an-attack.html"; 332 const unwantedURL = "itisatrap.org/firefox/unwanted.html"; 333 const trackerURLs = [ 334 "trackertest.org/", 335 "itisatracker.org/", 336 ]; 337 const whitelistURL = "itisatrap.org/?resource=itisatracker.org"; 338 const blockedURL = "itisatrap.org/firefox/blocked.html"; 339 340 const flashDenyURL = "flashblock.itisatrap.org/"; 341 const flashDenyExceptURL = "except.flashblock.itisatrap.org/"; 342 const flashAllowURL = "flashallow.itisatrap.org/"; 343 const flashAllowExceptURL = "except.flashallow.itisatrap.org/"; 344 const flashSubDocURL = "flashsubdoc.itisatrap.org/"; 345 const flashSubDocExceptURL = "except.flashsubdoc.itisatrap.org/"; 346 347 let update = "n:1000\ni:test-malware-simple\nad:1\n" + 348 "a:1:32:" + malwareURL.length + "\n" + 349 malwareURL + "\n"; 350 update += "n:1000\ni:test-phish-simple\nad:1\n" + 351 "a:1:32:" + phishURL.length + "\n" + 352 phishURL + "\n"; 353 update += "n:1000\ni:test-unwanted-simple\nad:1\n" + 354 "a:1:32:" + unwantedURL.length + "\n" + 355 unwantedURL + "\n"; 356 update += "n:1000\ni:test-track-simple\n" + 357 "ad:" + trackerURLs.length + "\n"; 358 trackerURLs.forEach((trackerURL, i) => { 359 update += "a:" + (i + 1) + ":32:" + trackerURL.length + "\n" + 360 trackerURL + "\n"; 361 }); 362 update += "n:1000\ni:test-trackwhite-simple\nad:1\n" + 363 "a:1:32:" + whitelistURL.length + "\n" + 364 whitelistURL; 365 update += "n:1000\ni:test-block-simple\nad:1\n" + 366 "a:1:32:" + blockedURL.length + "\n" + 367 blockedURL; 368 update += "n:1000\ni:test-flash-simple\nad:1\n" + 369 "a:1:32:" + flashDenyURL.length + "\n" + 370 flashDenyURL; 371 update += "n:1000\ni:testexcept-flash-simple\nad:1\n" + 372 "a:1:32:" + flashDenyExceptURL.length + "\n" + 373 flashDenyExceptURL; 374 update += "n:1000\ni:test-flashallow-simple\nad:1\n" + 375 "a:1:32:" + flashAllowURL.length + "\n" + 376 flashAllowURL; 377 update += "n:1000\ni:testexcept-flashallow-simple\nad:1\n" + 378 "a:1:32:" + flashAllowExceptURL.length + "\n" + 379 flashAllowExceptURL; 380 update += "n:1000\ni:test-flashsubdoc-simple\nad:1\n" + 381 "a:1:32:" + flashSubDocURL.length + "\n" + 382 flashSubDocURL; 383 update += "n:1000\ni:testexcept-flashsubdoc-simple\nad:1\n" + 384 "a:1:32:" + flashSubDocExceptURL.length + "\n" + 385 flashSubDocExceptURL; 386 log("addMozEntries:", update); 387 388 let db = Cc["@mozilla.org/url-classifier/dbservice;1"]. 389 getService(Ci.nsIUrlClassifierDBService); 390 391 // nsIUrlClassifierUpdateObserver 392 let dummyListener = { 393 updateUrlRequested: function() { }, 394 streamFinished: function() { }, 395 // We notify observers when we're done in order to be able to make perf 396 // test results more consistent 397 updateError: function() { 398 Services.obs.notifyObservers(db, "mozentries-update-finished", "error"); 399 }, 400 updateSuccess: function() { 401 Services.obs.notifyObservers(db, "mozentries-update-finished", "success"); 402 } 403 }; 404 405 try { 406 let tables = "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,test-flash-simple,testexcept-flash-simple,test-flashallow-simple,testexcept-flashallow-simple,test-flashsubdoc-simple,testexcept-flashsubdoc-simple"; 407 db.beginUpdate(dummyListener, tables, ""); 408 db.beginStream("", ""); 409 db.updateStream(update); 410 db.finishStream(); 411 db.finishUpdate(); 412 } catch(ex) { 413 // beginUpdate will throw harmlessly if there's an existing update in progress, ignore failures. 414 log("addMozEntries failed!", ex); 415 Services.obs.notifyObservers(db, "mozentries-update-finished", "exception"); 416 } 417 }, 418 419 addMozEntriesFinishedPromise: new Promise(resolve => { 420 let finished = (subject, topic, data) => { 421 Services.obs.removeObserver(finished, "mozentries-update-finished"); 422 if (data == "error") { 423 Cu.reportError("addMozEntries failed to update the db!"); 424 } 425 resolve(); 426 }; 427 Services.obs.addObserver(finished, "mozentries-update-finished", false); 428 }), 429}; 430