SafeBrowsing.jsm 15 KB

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