292 lines
9.6 KiB
JavaScript
292 lines
9.6 KiB
JavaScript
/** \file
|
|
* \brief Functions that summarize fingerprints and generate FPD report
|
|
* \ingroup FPD
|
|
*
|
|
* \author Copyright (C) 2022 Marek Salon
|
|
*
|
|
* \license SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
let hiddenTraces = {};
|
|
|
|
/**
|
|
* Event listener that listens for a load of FPD report page. If the page is loaded, fetch FPD data from background.
|
|
*
|
|
* \param callback Function that initializes FPD report creation from fetched data.
|
|
*/
|
|
window.addEventListener('load', () => {
|
|
browser.runtime.sendMessage({
|
|
purpose: "fpd-get-report-data",
|
|
tabId: new URLSearchParams(window.location.search).get("id")
|
|
}).then((result) => {
|
|
createReport(result);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* The function that populates FPD report page with data from the latest evaluation and hooks up listeners.
|
|
*
|
|
* \param data Information about latest fingerprinting evaluation consisting of all essential FPD objects.
|
|
*/
|
|
function createReport(data) {
|
|
var {tabObj, groups, latestEvals, fpDb, exceptionWrappers} = data;
|
|
var reportContainer = document.getElementById("fpd-report-container");
|
|
if (!tabObj || !groups || !groups.root || !groups.all || !fpDb || !latestEvals) {
|
|
report.innerHTML =`<div class="alert">${browser.i18n.getMessage("FPDReportMissingData")}</div>`;
|
|
return;
|
|
}
|
|
|
|
// parse latestEvals to create more useful representation for FPD report generation
|
|
var processedEvals = {};
|
|
for (let item of latestEvals.evalStats) {
|
|
processedEvals[item.title] = processedEvals[item.title] || {};
|
|
processedEvals[item.title].type = item.type;
|
|
let total = 0;
|
|
if (fpDb[item.title]) {
|
|
for (let stat of Object.values(fpDb[item.title])) {
|
|
total += stat.total;
|
|
}
|
|
}
|
|
processedEvals[item.title].total = total;
|
|
}
|
|
|
|
// add page URL and FavIcon to header section of the report
|
|
if (tabObj) {
|
|
let urlObj = new URL(tabObj.url);
|
|
var url = urlObj.hostname + (urlObj.pathname.length > 1 ? urlObj.pathname : "");
|
|
document.getElementById("report-url").innerHTML = url;
|
|
let img = document.getElementById("pageFavicon");
|
|
img.src = tabObj.favIconUrl;
|
|
}
|
|
|
|
var html = "";
|
|
|
|
// generate html code for evaluated group
|
|
let generateGroup = (group) => {
|
|
if (processedEvals[group]) {
|
|
if (groups.all[group].description) {
|
|
html += "<div id=\"" + group + "\" class=\"fpd-group access\">";
|
|
html += "<h2>" + group + "</h2>";
|
|
html += `<button class="help" id="expand${group}" >⤵</button>`;
|
|
html += "<section>";
|
|
html += "<p>" + groups.all[group].description + "</p>";
|
|
}
|
|
for (let [item, type] of Object.entries(groups.all[group].items)) {
|
|
if (type == "group") {
|
|
generateGroup(item);
|
|
}
|
|
else {
|
|
generateResource(item)
|
|
}
|
|
}
|
|
if (groups.all[group].description) {
|
|
html += "</section></div>";
|
|
}
|
|
}
|
|
}
|
|
|
|
// generate html code for evaluated resource (get,set,call)
|
|
let generateResource = (resource) => {
|
|
if (processedEvals[resource]) {
|
|
let callers = "";
|
|
if (fpDb[resource]) {
|
|
for (let t of Object.values(fpDb[resource])) {
|
|
let traces = Object.keys(t.callers);
|
|
for (trace of traces) {
|
|
if (trace !== "" && !(trace in hiddenTraces)) {
|
|
callers += "<p>" + trace.replace(/\n/g, '<br>') + "</p>";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let accessRaw = processedEvals[resource].total;
|
|
let accessCount = accessRaw >= 1000 ? "1000+" : accessRaw;
|
|
html += `<div class="details ${accessRaw > 0 ? "access" : "no-access"}"><h4><span class="dot">-</span> ` +
|
|
`${resource} (${exceptionWrappers.includes(resource) ? "n/a" : accessCount})</h4>\n${callers}</div>`;
|
|
}
|
|
}
|
|
|
|
// start generating FPD report from the first group (root group)
|
|
generateGroup(groups.root);
|
|
var report = document.getElementById("fpd-report");
|
|
report.innerHTML = html;
|
|
|
|
// process generated document
|
|
let groupElements = document.querySelectorAll(".fpd-group.access");
|
|
for (let i = groupElements.length; i > 0; i--) {
|
|
// remove duplicit entries from groups
|
|
let duplicitArray = [];
|
|
let elements = groupElements[i-1].querySelectorAll(":scope > section > div.details > h4");
|
|
elements.forEach((d) => {
|
|
if (duplicitArray.indexOf(d.innerHTML) > -1) {
|
|
d.remove();
|
|
}
|
|
else {
|
|
duplicitArray.push(d.innerHTML);
|
|
}
|
|
});
|
|
|
|
// hide groups with no relevant entries
|
|
if (!document.querySelectorAll(`#${groupElements[i-1].id} > section > .access`).length) {
|
|
groupElements[i-1].classList.replace("access", "no-access");
|
|
}
|
|
}
|
|
|
|
// function that enables to show accessed resources of the group
|
|
function toggleResources(event) {
|
|
let parent = event.target.parentElement;
|
|
for (let i = 0; i < parent.children.length; i++) {
|
|
let child = parent.children[i];
|
|
if (child.tagName == "SECTION") {
|
|
child.classList.toggle("hidden");
|
|
}
|
|
}
|
|
}
|
|
|
|
// make group name clickable only if it makes sense - groups with resources
|
|
function makeGroupExpansionsClickable() {
|
|
for (let element of document.querySelectorAll(".fpd-group")) {
|
|
let button;
|
|
|
|
for (let i = 0; i < element.children.length; i++) {
|
|
let child = element.children[i];
|
|
if (child.tagName == "BUTTON") {
|
|
button = child;
|
|
}
|
|
}
|
|
|
|
if (button) {
|
|
button.classList.add("clickable");
|
|
button.addEventListener("click", toggleResources);
|
|
}
|
|
}
|
|
}
|
|
makeGroupExpansionsClickable();
|
|
|
|
// show resources for every group in FPD report
|
|
let showAll = (event) => {
|
|
for (let element of document.querySelectorAll(".fpd-group div.details")) {
|
|
element.classList.remove("hidden");
|
|
}
|
|
showBtn.classList.add("hidden");
|
|
hideBtn.classList.remove("hidden");
|
|
}
|
|
|
|
// hide resources for every group in FPD report
|
|
let hideDetails = (event) => {
|
|
for (let element of document.querySelectorAll(".fpd-group div.details")) {
|
|
element.classList.add("hidden");
|
|
}
|
|
showBtn.classList.remove("hidden");
|
|
hideBtn.classList.add("hidden");
|
|
}
|
|
hideDetails();
|
|
|
|
// refresh data in the report
|
|
function refreshReport() {
|
|
browser.runtime.sendMessage({
|
|
purpose: "fpd-get-report-data",
|
|
tabId: tabId
|
|
}).then((result) => {
|
|
createReport(result);
|
|
showAll();
|
|
});
|
|
browser.runtime.sendMessage({purpose: "fpd-track-callers-stop"});
|
|
document.getElementById("updateReportBtn").classList.remove("hidden");
|
|
document.getElementById("forgetCurrentBtn").classList.remove("hidden");
|
|
let trackCallersBtn = document.getElementById("trackCallersBtn");
|
|
trackCallersBtn.innerText = browser.i18n.getMessage("FPDReportTrackCallersRestart");
|
|
trackCallersBtn.classList.remove("hidden");
|
|
}
|
|
|
|
// Reload the report with data on the identity of the calling scripts
|
|
function trackCallers() {
|
|
tabId = new URLSearchParams(window.location.search).get("id");
|
|
function onReloaded() {
|
|
setTimeout(refreshReport, 5000);
|
|
report.innerHTML = browser.i18n.getMessage("FPDReportTrackCallersWaiting");
|
|
document.getElementById("trackCallersBtn").classList.add("hidden");
|
|
document.getElementById("updateReportBtn").classList.add("hidden");
|
|
document.getElementById("forgetCurrentBtn").classList.add("hidden");
|
|
}
|
|
function onError(error) {
|
|
document.getElementById("fpdError").innerHTML = browser.i18n.getMessage("FPDReportTrackCallersFailed", error);
|
|
browser.runtime.sendMessage({purpose: "fpd-track-callers-stop"});
|
|
}
|
|
browser.runtime.sendMessage({
|
|
purpose: "fpd-track-callers",
|
|
tabId: tabId
|
|
}).then(onReloaded, onError);
|
|
}
|
|
|
|
function updateReport() {
|
|
}
|
|
|
|
// show all groups/resources even if not accessed
|
|
let showNotAccessed = () => {
|
|
for (let element of document.querySelectorAll(".no-access")) {
|
|
element.classList.remove("no-access");
|
|
}
|
|
makeGroupExpansionsClickable();
|
|
}
|
|
|
|
function forgetTraces() {
|
|
for (resource of Object.values(fpDb)) {
|
|
for (type of Object.values(resource)) {
|
|
for (trace of Object.keys(type.callers)) {
|
|
hiddenTraces[trace] = true;
|
|
}
|
|
}
|
|
}
|
|
createReport(data);
|
|
}
|
|
|
|
// create on-site JSON representation of FPD evaluation data and download it
|
|
function exportReport(filename) {
|
|
let element = document.createElement("a");
|
|
let obj = {
|
|
fpd_evaluation_statistics: latestEvals.evalStats,
|
|
fpd_access_logs: fpDb
|
|
}
|
|
element.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(obj)));
|
|
element.setAttribute("download", filename);
|
|
element.style.display = "none";
|
|
document.body.appendChild(element);
|
|
element.click();
|
|
document.body.removeChild(element);
|
|
}
|
|
|
|
document.getElementById("showBtn").onclick = showAll;
|
|
document.getElementById("hideBtn").onclick = hideDetails;
|
|
document.getElementById("exportBtn").onclick = exportReport.bind(null, `fpd_report_${url}.json`);
|
|
document.getElementById("trackCallersBtn").onclick = trackCallers;
|
|
document.getElementById("forgetCurrentBtn").onclick = forgetTraces;
|
|
document.getElementById("updateReportBtn").onclick = refreshReport;
|
|
document.getElementById("unhideAll").onclick = showNotAccessed;
|
|
}
|
|
|
|
// show description/help for the report
|
|
let showDescription = () => {
|
|
for (let element of document.querySelectorAll(".description")) {
|
|
element.classList.toggle("hidden");
|
|
}
|
|
}
|
|
|
|
window.addEventListener("DOMContentLoaded", function() {
|
|
document.getElementById("titletext").innerHTML += '<button id="help" class="help">?</button>';
|
|
document.getElementById("help").addEventListener("click", showDescription);
|
|
});
|