From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- .../url-classifier/tests/mochitest/.eslintrc.js | 8 + .../tests/mochitest/allowlistAnnotatedFrame.html | 144 ++++++++++ .../url-classifier/tests/mochitest/bad.css | 1 + .../tests/mochitest/bad.css^headers^ | 1 + .../url-classifier/tests/mochitest/basic.vtt | 27 ++ .../tests/mochitest/basic.vtt^headers^ | 1 + .../tests/mochitest/bug_1281083.html | 35 +++ .../url-classifier/tests/mochitest/chrome.ini | 23 ++ .../tests/mochitest/classifiedAnnotatedFrame.html | 213 ++++++++++++++ .../mochitest/classifiedAnnotatedPBFrame.html | 24 ++ .../tests/mochitest/classifierCommon.js | 112 ++++++++ .../tests/mochitest/classifierFrame.html | 57 ++++ .../tests/mochitest/classifierHelper.js | 201 ++++++++++++++ .../url-classifier/tests/mochitest/cleanWorker.js | 10 + .../url-classifier/tests/mochitest/dnt.html | 31 +++ .../url-classifier/tests/mochitest/dnt.sjs | 9 + .../url-classifier/tests/mochitest/evil.css | 1 + .../tests/mochitest/evil.css^headers^ | 1 + .../url-classifier/tests/mochitest/evil.js | 1 + .../tests/mochitest/evil.js^headers^ | 2 + .../url-classifier/tests/mochitest/evilWorker.js | 3 + .../url-classifier/tests/mochitest/gethash.sjs | 130 +++++++++ .../tests/mochitest/gethashFrame.html | 62 +++++ .../url-classifier/tests/mochitest/good.js | 1 + .../url-classifier/tests/mochitest/import.css | 3 + .../url-classifier/tests/mochitest/mochitest.ini | 39 +++ .../url-classifier/tests/mochitest/ping.sjs | 16 ++ .../url-classifier/tests/mochitest/raptor.jpg | Bin 0 -> 49629 bytes .../url-classifier/tests/mochitest/seek.webm | Bin 0 -> 215529 bytes .../mochitest/test_allowlisted_annotations.html | 56 ++++ .../tests/mochitest/test_bug1254766.html | 305 +++++++++++++++++++++ .../mochitest/test_classified_annotations.html | 50 ++++ .../tests/mochitest/test_classifier.html | 65 +++++ .../mochitest/test_classifier_changetablepref.html | 149 ++++++++++ .../tests/mochitest/test_classifier_worker.html | 76 +++++ .../tests/mochitest/test_classify_ping.html | 121 ++++++++ .../tests/mochitest/test_classify_track.html | 162 +++++++++++ .../tests/mochitest/test_donottrack.html | 150 ++++++++++ .../tests/mochitest/test_gethash.html | 157 +++++++++++ .../mochitest/test_lookup_system_principal.html | 29 ++ .../test_privatebrowsing_trackingprotection.html | 154 +++++++++++ .../mochitest/test_safebrowsing_bug1272239.html | 87 ++++++ .../test_trackingprotection_bug1157081.html | 107 ++++++++ .../test_trackingprotection_whitelist.html | 153 +++++++++++ .../url-classifier/tests/mochitest/track.html | 7 + .../tests/mochitest/unwantedWorker.js | 3 + .../url-classifier/tests/mochitest/update.sjs | 114 ++++++++ .../url-classifier/tests/mochitest/vp9.webm | Bin 0 -> 97465 bytes .../tests/mochitest/whitelistFrame.html | 15 + .../tests/mochitest/workerFrame.html | 65 +++++ 50 files changed, 3181 insertions(+) create mode 100644 toolkit/components/url-classifier/tests/mochitest/.eslintrc.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/bad.css create mode 100644 toolkit/components/url-classifier/tests/mochitest/bad.css^headers^ create mode 100644 toolkit/components/url-classifier/tests/mochitest/basic.vtt create mode 100644 toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^ create mode 100644 toolkit/components/url-classifier/tests/mochitest/bug_1281083.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/chrome.ini create mode 100644 toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/classifierCommon.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/classifierFrame.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/classifierHelper.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/cleanWorker.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/dnt.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/dnt.sjs create mode 100644 toolkit/components/url-classifier/tests/mochitest/evil.css create mode 100644 toolkit/components/url-classifier/tests/mochitest/evil.css^headers^ create mode 100644 toolkit/components/url-classifier/tests/mochitest/evil.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/evil.js^headers^ create mode 100644 toolkit/components/url-classifier/tests/mochitest/evilWorker.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/gethash.sjs create mode 100644 toolkit/components/url-classifier/tests/mochitest/gethashFrame.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/good.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/import.css create mode 100644 toolkit/components/url-classifier/tests/mochitest/mochitest.ini create mode 100644 toolkit/components/url-classifier/tests/mochitest/ping.sjs create mode 100644 toolkit/components/url-classifier/tests/mochitest/raptor.jpg create mode 100644 toolkit/components/url-classifier/tests/mochitest/seek.webm create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_classifier.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_classify_track.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_donottrack.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_gethash.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_lookup_system_principal.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1157081.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/track.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js create mode 100644 toolkit/components/url-classifier/tests/mochitest/update.sjs create mode 100644 toolkit/components/url-classifier/tests/mochitest/vp9.webm create mode 100644 toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html create mode 100644 toolkit/components/url-classifier/tests/mochitest/workerFrame.html (limited to 'toolkit/components/url-classifier/tests/mochitest') diff --git a/toolkit/components/url-classifier/tests/mochitest/.eslintrc.js b/toolkit/components/url-classifier/tests/mochitest/.eslintrc.js new file mode 100644 index 000000000..58b3df4a7 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/mochitest.eslintrc.js", + "../../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html b/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html new file mode 100644 index 000000000..9aae1b841 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/components/url-classifier/tests/mochitest/bad.css b/toolkit/components/url-classifier/tests/mochitest/bad.css new file mode 100644 index 000000000..f57b36a77 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/bad.css @@ -0,0 +1 @@ +#styleBad { visibility: hidden; } diff --git a/toolkit/components/url-classifier/tests/mochitest/bad.css^headers^ b/toolkit/components/url-classifier/tests/mochitest/bad.css^headers^ new file mode 100644 index 000000000..4030ea1d3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/bad.css^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/toolkit/components/url-classifier/tests/mochitest/basic.vtt b/toolkit/components/url-classifier/tests/mochitest/basic.vtt new file mode 100644 index 000000000..7781790d0 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/basic.vtt @@ -0,0 +1,27 @@ +WEBVTT +Region: id=testOne lines=2 width=30% +Region: id=testTwo lines=4 width=20% + +1 +00:00.500 --> 00:00.700 region:testOne +This + +2 +00:01.200 --> 00:02.400 region:testTwo +Is + +2.5 +00:02.000 --> 00:03.500 region:testOne +(Over here?!) + +3 +00:02.710 --> 00:02.910 +A + +4 +00:03.217 --> 00:03.989 +Test + +5 +00:03.217 --> 00:03.989 +And more! diff --git a/toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^ b/toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^ new file mode 100644 index 000000000..23de552c1 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/basic.vtt^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * \ No newline at end of file diff --git a/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html b/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html new file mode 100644 index 000000000..cd5770177 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/toolkit/components/url-classifier/tests/mochitest/chrome.ini b/toolkit/components/url-classifier/tests/mochitest/chrome.ini new file mode 100644 index 000000000..1652e7421 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/chrome.ini @@ -0,0 +1,23 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + allowlistAnnotatedFrame.html + classifiedAnnotatedFrame.html + classifiedAnnotatedPBFrame.html + bug_1281083.html + +[test_lookup_system_principal.html] +[test_classified_annotations.html] +tags = trackingprotection +skip-if = os == 'linux' && asan # Bug 1202548 +[test_allowlisted_annotations.html] +tags = trackingprotection +[test_privatebrowsing_trackingprotection.html] +tags = trackingprotection +[test_trackingprotection_bug1157081.html] +tags = trackingprotection +[test_trackingprotection_whitelist.html] +tags = trackingprotection +[test_safebrowsing_bug1272239.html] +[test_donottrack.html] +[test_classifier_changetablepref.html] diff --git a/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html new file mode 100644 index 000000000..8aab13dd3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +The following should not be hidden: +
STYLE TEST
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html new file mode 100644 index 000000000..f11ec1de3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + +The following should not be hidden: +
STYLE TEST
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js b/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js new file mode 100644 index 000000000..49bda38db --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { classes: Cc, interfaces: Ci, results: Cr } = Components; + +var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"] + .getService(Ci.nsIUrlClassifierDBService); + +var timer; +function setTimeout(callback, delay) { + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback({ notify: callback }, + delay, + Ci.nsITimer.TYPE_ONE_SHOT); +} + +function doUpdate(update) { + let listener = { + QueryInterface: function(iid) + { + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIUrlClassifierUpdateObserver)) + return this; + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + updateUrlRequested: function(url) { }, + streamFinished: function(status) { }, + updateError: function(errorCode) { + sendAsyncMessage("updateError", errorCode); + }, + updateSuccess: function(requestedTimeout) { + sendAsyncMessage("updateSuccess"); + } + }; + + let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"] + .getService(Ci.nsIUrlClassifierDBService); + + try { + dbService.beginUpdate(listener, "test-malware-simple,test-unwanted-simple", ""); + dbService.beginStream("", ""); + dbService.updateStream(update); + dbService.finishStream(); + dbService.finishUpdate(); + } catch(e) { + // beginUpdate may fail if there's an existing update in progress + // retry until success or testcase timeout. + setTimeout(() => { doUpdate(update); }, 1000); + } +} + +function doReload() { + dbService.reloadDatabase(); + + sendAsyncMessage("reloadSuccess"); +} + +// SafeBrowsing.jsm is initialized after mozEntries are added. Add observer +// to receive "finished" event. For the case when this function is called +// after the event had already been notified, we lookup entries to see if +// they are already added to database. +function waitForInit() { + let observerService = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + + observerService.addObserver(function() { + sendAsyncMessage("safeBrowsingInited"); + }, "mozentries-update-finished", false); + + // This url must sync with the table, url in SafeBrowsing.jsm addMozEntries + const table = "test-phish-simple"; + const url = "http://itisatrap.org/firefox/its-a-trap.html"; + + let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(Ci.nsIScriptSecurityManager); + let iosvc = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + + let principal = secMan.createCodebasePrincipal( + iosvc.newURI(url, null, null), {}); + + let listener = { + QueryInterface: function(iid) + { + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIUrlClassifierUpdateObserver)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + handleEvent: function(value) + { + if (value === table) { + sendAsyncMessage("safeBrowsingInited"); + } + }, + }; + dbService.lookup(principal, table, listener); +} + +addMessageListener("doUpdate", ({ testUpdate }) => { + doUpdate(testUpdate); +}); + +addMessageListener("doReload", () => { + doReload(); +}); + +addMessageListener("waitForInit", () => { + waitForInit(); +}); diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html b/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html new file mode 100644 index 000000000..c7923f448 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + +The following should not be hidden: +
STYLE TEST
+
STYLE BAD
+
STYLE IMPORT
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js b/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js new file mode 100644 index 000000000..973f0c2c4 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js @@ -0,0 +1,201 @@ +if (typeof(classifierHelper) == "undefined") { + var classifierHelper = {}; +} + +const CLASSIFIER_COMMON_URL = SimpleTest.getTestFileURL("classifierCommon.js"); +var gScript = SpecialPowers.loadChromeScript(CLASSIFIER_COMMON_URL); + +const ADD_CHUNKNUM = 524; +const SUB_CHUNKNUM = 523; +const HASHLEN = 32; + +const PREFS = { + PROVIDER_LISTS : "browser.safebrowsing.provider.mozilla.lists", + DISALLOW_COMPLETIONS : "urlclassifier.disallow_completions", + PROVIDER_GETHASHURL : "browser.safebrowsing.provider.mozilla.gethashURL" +}; + +// addUrlToDB & removeUrlFromDB are asynchronous, queue the task to ensure +// the callback follow correct order. +classifierHelper._updates = []; + +// Keep urls added to database, those urls should be automatically +// removed after test complete. +classifierHelper._updatesToCleanup = []; + +classifierHelper._initsCB = []; + +// This function return a Promise, promise is resolved when SafeBrowsing.jsm +// is initialized. +classifierHelper.waitForInit = function() { + return new Promise(function(resolve, reject) { + classifierHelper._initsCB.push(resolve); + gScript.sendAsyncMessage("waitForInit"); + }); +} + +// This function is used to allow completion for specific "list", +// some lists like "test-malware-simple" is default disabled to ask for complete. +// "list" is the db we would like to allow it +// "url" is the completion server +classifierHelper.allowCompletion = function(lists, url) { + for (var list of lists) { + // Add test db to provider + var pref = SpecialPowers.getCharPref(PREFS.PROVIDER_LISTS); + pref += "," + list; + SpecialPowers.setCharPref(PREFS.PROVIDER_LISTS, pref); + + // Rename test db so we will not disallow it from completions + pref = SpecialPowers.getCharPref(PREFS.DISALLOW_COMPLETIONS); + pref = pref.replace(list, list + "-backup"); + SpecialPowers.setCharPref(PREFS.DISALLOW_COMPLETIONS, pref); + } + + // Set get hash url + SpecialPowers.setCharPref(PREFS.PROVIDER_GETHASHURL, url); +} + +// Pass { url: ..., db: ... } to add url to database, +// onsuccess/onerror will be called when update complete. +classifierHelper.addUrlToDB = function(updateData) { + return new Promise(function(resolve, reject) { + var testUpdate = ""; + for (var update of updateData) { + var LISTNAME = update.db; + var CHUNKDATA = update.url; + var CHUNKLEN = CHUNKDATA.length; + var HASHLEN = update.len ? update.len : 32; + + classifierHelper._updatesToCleanup.push(update); + testUpdate += + "n:1000\n" + + "i:" + LISTNAME + "\n" + + "ad:1\n" + + "a:" + ADD_CHUNKNUM + ":" + HASHLEN + ":" + CHUNKLEN + "\n" + + CHUNKDATA; + } + + classifierHelper._update(testUpdate, resolve, reject); + }); +} + +// Pass { url: ..., db: ... } to remove url from database, +// onsuccess/onerror will be called when update complete. +classifierHelper.removeUrlFromDB = function(updateData) { + return new Promise(function(resolve, reject) { + var testUpdate = ""; + for (var update of updateData) { + var LISTNAME = update.db; + var CHUNKDATA = ADD_CHUNKNUM + ":" + update.url; + var CHUNKLEN = CHUNKDATA.length; + var HASHLEN = update.len ? update.len : 32; + + testUpdate += + "n:1000\n" + + "i:" + LISTNAME + "\n" + + "s:" + SUB_CHUNKNUM + ":" + HASHLEN + ":" + CHUNKLEN + "\n" + + CHUNKDATA; + } + + classifierHelper._updatesToCleanup = + classifierHelper._updatesToCleanup.filter((v) => { + return updateData.indexOf(v) == -1; + }); + + classifierHelper._update(testUpdate, resolve, reject); + }); +}; + +// This API is used to expire all add/sub chunks we have updated +// by using addUrlToDB and removeUrlFromDB. +classifierHelper.resetDB = function() { + return new Promise(function(resolve, reject) { + var testUpdate = ""; + for (var update of classifierHelper._updatesToCleanup) { + if (testUpdate.includes(update.db)) + continue; + + testUpdate += + "n:1000\n" + + "i:" + update.db + "\n" + + "ad:" + ADD_CHUNKNUM + "\n" + + "sd:" + SUB_CHUNKNUM + "\n" + } + + classifierHelper._update(testUpdate, resolve, reject); + }); +}; + +classifierHelper.reloadDatabase = function() { + return new Promise(function(resolve, reject) { + gScript.addMessageListener("reloadSuccess", function handler() { + gScript.removeMessageListener('reloadSuccess', handler); + resolve(); + }); + + gScript.sendAsyncMessage("doReload"); + }); +} + +classifierHelper._update = function(testUpdate, onsuccess, onerror) { + // Queue the task if there is still an on-going update + classifierHelper._updates.push({"data": testUpdate, + "onsuccess": onsuccess, + "onerror": onerror}); + if (classifierHelper._updates.length != 1) { + return; + } + + gScript.sendAsyncMessage("doUpdate", { testUpdate }); +}; + +classifierHelper._updateSuccess = function() { + var update = classifierHelper._updates.shift(); + update.onsuccess(); + + if (classifierHelper._updates.length) { + var testUpdate = classifierHelper._updates[0].data; + gScript.sendAsyncMessage("doUpdate", { testUpdate }); + } +}; + +classifierHelper._updateError = function(errorCode) { + var update = classifierHelper._updates.shift(); + update.onerror(errorCode); + + if (classifierHelper._updates.length) { + var testUpdate = classifierHelper._updates[0].data; + gScript.sendAsyncMessage("doUpdate", { testUpdate }); + } +}; + +classifierHelper._inited = function() { + classifierHelper._initsCB.forEach(function (cb) { + cb(); + }); + classifierHelper._initsCB = []; +}; + +classifierHelper._setup = function() { + gScript.addMessageListener("updateSuccess", classifierHelper._updateSuccess); + gScript.addMessageListener("updateError", classifierHelper._updateError); + gScript.addMessageListener("safeBrowsingInited", classifierHelper._inited); + + // cleanup will be called at end of each testcase to remove all the urls added to database. + SimpleTest.registerCleanupFunction(classifierHelper._cleanup); +}; + +classifierHelper._cleanup = function() { + // clean all the preferences may touch by helper + for (var pref in PREFS) { + SpecialPowers.clearUserPref(pref); + } + + if (!classifierHelper._updatesToCleanup) { + return Promise.resolve(); + } + + return classifierHelper.resetDB(); +}; + +classifierHelper._setup(); diff --git a/toolkit/components/url-classifier/tests/mochitest/cleanWorker.js b/toolkit/components/url-classifier/tests/mochitest/cleanWorker.js new file mode 100644 index 000000000..685648373 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/cleanWorker.js @@ -0,0 +1,10 @@ +onmessage = function() { + try { + importScripts("evilWorker.js"); + } catch(ex) { + postMessage("success"); + return; + } + + postMessage("failure"); +}; diff --git a/toolkit/components/url-classifier/tests/mochitest/dnt.html b/toolkit/components/url-classifier/tests/mochitest/dnt.html new file mode 100644 index 000000000..effc3a4f8 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/dnt.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/toolkit/components/url-classifier/tests/mochitest/dnt.sjs b/toolkit/components/url-classifier/tests/mochitest/dnt.sjs new file mode 100644 index 000000000..bbb836482 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/dnt.sjs @@ -0,0 +1,9 @@ +function handleRequest(request, response) { + var dnt = "unspecified"; + if (request.hasHeader("DNT")) { + dnt = "1"; + } + + response.setHeader("Content-Type", "text/plain", false); + response.write(dnt); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.css b/toolkit/components/url-classifier/tests/mochitest/evil.css new file mode 100644 index 000000000..f6f08d7c5 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.css @@ -0,0 +1 @@ +#styleCheck { visibility: hidden; } \ No newline at end of file diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.css^headers^ b/toolkit/components/url-classifier/tests/mochitest/evil.css^headers^ new file mode 100644 index 000000000..4030ea1d3 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.css^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.js b/toolkit/components/url-classifier/tests/mochitest/evil.js new file mode 100644 index 000000000..27f2e8c43 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.js @@ -0,0 +1 @@ +scriptItem = "loaded malware javascript!"; diff --git a/toolkit/components/url-classifier/tests/mochitest/evil.js^headers^ b/toolkit/components/url-classifier/tests/mochitest/evil.js^headers^ new file mode 100644 index 000000000..3eced9614 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evil.js^headers^ @@ -0,0 +1,2 @@ +Access-Control-Allow-Origin: * +Cache-Control: no-store diff --git a/toolkit/components/url-classifier/tests/mochitest/evilWorker.js b/toolkit/components/url-classifier/tests/mochitest/evilWorker.js new file mode 100644 index 000000000..ac34977d7 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/evilWorker.js @@ -0,0 +1,3 @@ +onmessage = function() { + postMessage("loaded bad file"); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/gethash.sjs b/toolkit/components/url-classifier/tests/mochitest/gethash.sjs new file mode 100644 index 000000000..9dcc6e0d5 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/gethash.sjs @@ -0,0 +1,130 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + var query = {}; + request.queryString.split('&').forEach(function (val) { + var idx = val.indexOf('='); + query[val.slice(0, idx)] = unescape(val.slice(idx + 1)); + }); + + var responseBody; + + // Store fullhash in the server side. + if ("list" in query && "fullhash" in query) { + // In the server side we will store: + // 1. All the full hashes for a given list + // 2. All the lists we have right now + // data is separate by '\n' + let list = query["list"]; + let hashes = getState(list); + + let hash = base64ToString(query["fullhash"]); + hashes += hash + "\n"; + setState(list, hashes); + + let lists = getState("lists"); + if (lists.indexOf(list) == -1) { + lists += list + "\n"; + setState("lists", lists); + } + + return; + // gethash count return how many gethash request received. + // This is used by client to know if a gethash request is triggered by gecko + } else if ("gethashcount" == request.queryString) { + var counter = getState("counter"); + responseBody = counter == "" ? "0" : counter; + } else { + var body = new BinaryInputStream(request.bodyInputStream); + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + + var counter = getState("counter"); + counter = counter == "" ? "1" : (parseInt(counter) + 1).toString(); + setState("counter", counter); + + responseBody = parseV2Request(bytes); + } + + response.setHeader("Content-Type", "text/plain", false); + response.write(responseBody); + +} + +function parseV2Request(bytes) { + var request = String.fromCharCode.apply(this, bytes); + var [HEADER, PREFIXES] = request.split("\n"); + var [PREFIXSIZE, LENGTH] = HEADER.split(":").map(val => { + return parseInt(val); + }); + + var ret = ""; + for(var start = 0; start < LENGTH; start += PREFIXSIZE) { + getState("lists").split("\n").forEach(function(list) { + var completions = getState(list).split("\n"); + + for (var completion of completions) { + if (completion.indexOf(PREFIXES.substr(start, PREFIXSIZE)) == 0) { + ret += list + ":" + "1" + ":" + "32" + "\n"; + ret += completion; + } + } + }); + } + + return ret; +} + +/* Convert Base64 data to a string */ +const toBinaryTable = [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +]; +const base64Pad = '='; + +function base64ToString(data) { + var result = ''; + var leftbits = 0; // number of bits decoded, but yet to be appended + var leftdata = 0; // bits decoded, but yet to be appended + + // Convert one by one. + for (var i = 0; i < data.length; i++) { + var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + var padding = (data[i] == base64Pad); + // Skip illegal characters and whitespace + if (c == -1) continue; + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) + result += String.fromCharCode((leftdata >> leftbits) & 0xff); + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) + throw Components.Exception('Corrupted base64 string'); + + return result; +} diff --git a/toolkit/components/url-classifier/tests/mochitest/gethashFrame.html b/toolkit/components/url-classifier/tests/mochitest/gethashFrame.html new file mode 100644 index 000000000..560ddcde6 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/gethashFrame.html @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + +
+
STYLE EVIL
+
STYLE BAD
+
STYLE IMPORT
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/good.js b/toolkit/components/url-classifier/tests/mochitest/good.js new file mode 100644 index 000000000..015b9fe52 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/good.js @@ -0,0 +1 @@ +scriptItem = "loaded whitelisted javascript!"; diff --git a/toolkit/components/url-classifier/tests/mochitest/import.css b/toolkit/components/url-classifier/tests/mochitest/import.css new file mode 100644 index 000000000..9b86c8216 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/import.css @@ -0,0 +1,3 @@ +/* malware.example.com is in the malware database. */ +@import url("http://malware.example.com/tests/toolkit/components/url-classifier/tests/mochitest/bad.css"); +#styleImport { visibility: hidden; } diff --git a/toolkit/components/url-classifier/tests/mochitest/mochitest.ini b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini new file mode 100644 index 000000000..c5679e86b --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini @@ -0,0 +1,39 @@ +[DEFAULT] +support-files = + classifiedAnnotatedPBFrame.html + classifierCommon.js + classifierFrame.html + classifierHelper.js + cleanWorker.js + good.js + evil.css + evil.css^headers^ + evil.js + evil.js^headers^ + evilWorker.js + import.css + raptor.jpg + track.html + unwantedWorker.js + vp9.webm + whitelistFrame.html + workerFrame.html + ping.sjs + basic.vtt + basic.vtt^headers^ + dnt.html + dnt.sjs + update.sjs + bad.css + bad.css^headers^ + gethash.sjs + gethashFrame.html + seek.webm + +[test_classifier.html] +skip-if = (os == 'linux' && debug) #Bug 1199778 +[test_classifier_worker.html] +[test_classify_ping.html] +[test_classify_track.html] +[test_gethash.html] +[test_bug1254766.html] diff --git a/toolkit/components/url-classifier/tests/mochitest/ping.sjs b/toolkit/components/url-classifier/tests/mochitest/ping.sjs new file mode 100644 index 000000000..37a78956e --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/ping.sjs @@ -0,0 +1,16 @@ +function handleRequest(request, response) +{ + var query = {}; + request.queryString.split('&').forEach(function (val) { + var [name, value] = val.split('='); + query[name] = unescape(value); + }); + + if (request.method == "POST") { + setState(query["id"], "ping"); + } else { + var value = getState(query["id"]); + response.setHeader("Content-Type", "text/plain", false); + response.write(value); + } +} diff --git a/toolkit/components/url-classifier/tests/mochitest/raptor.jpg b/toolkit/components/url-classifier/tests/mochitest/raptor.jpg new file mode 100644 index 000000000..243ba9e2d Binary files /dev/null and b/toolkit/components/url-classifier/tests/mochitest/raptor.jpg differ diff --git a/toolkit/components/url-classifier/tests/mochitest/seek.webm b/toolkit/components/url-classifier/tests/mochitest/seek.webm new file mode 100644 index 000000000..72b029723 Binary files /dev/null and b/toolkit/components/url-classifier/tests/mochitest/seek.webm differ diff --git a/toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html b/toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html new file mode 100644 index 000000000..ba9c86f95 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html @@ -0,0 +1,56 @@ + + + + Test the URI Classifier + + + + + +

+ +
+
+
+
+
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html b/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html new file mode 100644 index 000000000..1c149406a --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html @@ -0,0 +1,305 @@ + + + + Bug 1272239 - Test gethash. + + + + + + +

+ +
+
+
+
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html b/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html new file mode 100644 index 000000000..5814fff00 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html @@ -0,0 +1,50 @@ + + + + Test the URI Classifier + + + + + +

+ +
+
+
+
+
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html new file mode 100644 index 000000000..9533db426 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html @@ -0,0 +1,65 @@ + + + + Test the URI Classifier + + + + + +

+ +
+
+
+
+
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html new file mode 100644 index 000000000..7423d3e8e --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html @@ -0,0 +1,149 @@ + + + + Bug 1281083 - Changing the urlclassifier.*Table prefs doesn't take effect before the next browser restart. + + + + + + +

+ +
+
+
+
+ + + + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html new file mode 100644 index 000000000..1f54d45b0 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_worker.html @@ -0,0 +1,76 @@ + + + + Test the URI Classifier + + + + + +

+ +
+
+
+
+
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html new file mode 100644 index 000000000..96fa2891a --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html @@ -0,0 +1,121 @@ + + + + Bug 1233914 - ping doesn't honor the TP list here. + + + + + +

+ +
+
+
+
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html new file mode 100644 index 000000000..a868d7960 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html @@ -0,0 +1,162 @@ + + + + Bug 1262406 - Track element doesn't use the URL classifier. + + + + + + +

+ +
+
+
+
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html b/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html new file mode 100644 index 000000000..56003e7eb --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html @@ -0,0 +1,150 @@ + + + + Bug 1258033 - Fix the DNT loophole for tracking protection + + + + + +

+ +
+
+
+
+
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_gethash.html b/toolkit/components/url-classifier/tests/mochitest/test_gethash.html new file mode 100644 index 000000000..af995e2a5 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_gethash.html @@ -0,0 +1,157 @@ + + + + Bug 1272239 - Test gethash. + + + + + + +

+ +
+
+
+
+
+
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_lookup_system_principal.html b/toolkit/components/url-classifier/tests/mochitest/test_lookup_system_principal.html new file mode 100644 index 000000000..fa61e6a00 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_lookup_system_principal.html @@ -0,0 +1,29 @@ + + + + Test that lookup() on a system principal doesn't crash + + + + + + +
+
+
+
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html b/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html new file mode 100644 index 000000000..02ef57b46 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html @@ -0,0 +1,154 @@ + + + + + Test Tracking Protection in Private Browsing mode + + + + + +

+ +
+
+
+
+
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html b/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html new file mode 100644 index 000000000..8066c2a37 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html @@ -0,0 +1,87 @@ + + + + Bug 1272239 - Only tables with provider could register gethash url in listmanager. + + + + + +

+ +
+
+
+
+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1157081.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1157081.html new file mode 100644 index 000000000..7611dd245 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1157081.html @@ -0,0 +1,107 @@ + + + + + Test Tracking Protection with and without Safe Browsing (Bug #1157081) + + + + + +

+ +
+
+
+
+
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html new file mode 100644 index 000000000..29de0dfed --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html @@ -0,0 +1,153 @@ + + + + + Test Tracking Protection in Private Browsing mode + + + + + +

+ +
+
+
+
+
+ + + diff --git a/toolkit/components/url-classifier/tests/mochitest/track.html b/toolkit/components/url-classifier/tests/mochitest/track.html new file mode 100644 index 000000000..8785e7c5b --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/track.html @@ -0,0 +1,7 @@ + + + + +

Tracking Works!

+ + diff --git a/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js b/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js new file mode 100644 index 000000000..ac34977d7 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/unwantedWorker.js @@ -0,0 +1,3 @@ +onmessage = function() { + postMessage("loaded bad file"); +} diff --git a/toolkit/components/url-classifier/tests/mochitest/update.sjs b/toolkit/components/url-classifier/tests/mochitest/update.sjs new file mode 100644 index 000000000..53efaafdf --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/update.sjs @@ -0,0 +1,114 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + var query = {}; + request.queryString.split('&').forEach(function (val) { + var idx = val.indexOf('='); + query[val.slice(0, idx)] = unescape(val.slice(idx + 1)); + }); + + // Store fullhash in the server side. + if ("list" in query && "fullhash" in query) { + // In the server side we will store: + // 1. All the full hashes for a given list + // 2. All the lists we have right now + // data is separate by '\n' + let list = query["list"]; + let hashes = getState(list); + + let hash = base64ToString(query["fullhash"]); + hashes += hash + "\n"; + setState(list, hashes); + + let lists = getState("lists"); + if (lists.indexOf(list) == -1) { + lists += list + "\n"; + setState("lists", lists); + } + + return; + } + + var body = new BinaryInputStream(request.bodyInputStream); + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + + var responseBody = parseV2Request(bytes); + + response.setHeader("Content-Type", "text/plain", false); + response.write(responseBody); +} + +function parseV2Request(bytes) { + var table = String.fromCharCode.apply(this, bytes).slice(0,-2); + + var ret = ""; + getState("lists").split("\n").forEach(function(list) { + if (list == table) { + var completions = getState(list).split("\n"); + ret += "n:1000\n" + ret += "i:" + list + "\n"; + ret += "a:1:32:" + 32*(completions.length - 1) + "\n"; + + for (var completion of completions) { + ret += completion; + } + } + }); + + return ret; +} + +/* Convert Base64 data to a string */ +const toBinaryTable = [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +]; +const base64Pad = '='; + +function base64ToString(data) { + var result = ''; + var leftbits = 0; // number of bits decoded, but yet to be appended + var leftdata = 0; // bits decoded, but yet to be appended + + // Convert one by one. + for (var i = 0; i < data.length; i++) { + var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + var padding = (data[i] == base64Pad); + // Skip illegal characters and whitespace + if (c == -1) continue; + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) + result += String.fromCharCode((leftdata >> leftbits) & 0xff); + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) + throw Components.Exception('Corrupted base64 string'); + + return result; +} diff --git a/toolkit/components/url-classifier/tests/mochitest/vp9.webm b/toolkit/components/url-classifier/tests/mochitest/vp9.webm new file mode 100644 index 000000000..221877e30 Binary files /dev/null and b/toolkit/components/url-classifier/tests/mochitest/vp9.webm differ diff --git a/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html b/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html new file mode 100644 index 000000000..620416fc7 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/whitelistFrame.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/toolkit/components/url-classifier/tests/mochitest/workerFrame.html b/toolkit/components/url-classifier/tests/mochitest/workerFrame.html new file mode 100644 index 000000000..69e8dd007 --- /dev/null +++ b/toolkit/components/url-classifier/tests/mochitest/workerFrame.html @@ -0,0 +1,65 @@ + + + + + + + + + + + -- cgit v1.2.3