aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bg/ListManager.js9
-rw-r--r--common/Storage.js17
-rw-r--r--html/display_panel/content/display-panel.html6
-rw-r--r--html/display_panel/content/main_panel.js20
-rw-r--r--html/display_panel/content/panel-styles.css22
-rw-r--r--main_background.js96
-rw-r--r--manifest.json2
-rw-r--r--test/spec/LibreJSSpec.js19
8 files changed, 140 insertions, 51 deletions
diff --git a/bg/ListManager.js b/bg/ListManager.js
index e0a85e9..2f5f74e 100644
--- a/bg/ListManager.js
+++ b/bg/ListManager.js
@@ -50,6 +50,14 @@ class ListManager {
*/
getStatus(key, defValue = "unknown") {
let {blacklist, whitelist} = this.lists;
+ let inline = ListStore.inlineItem(key);
+ if (inline) {
+ return blacklist.contains(inline)
+ ? "blacklisted"
+ : whitelist.contains(inline) ? "whitelisted"
+ : defValue;
+ }
+
let match = key.match(/\(([^)]+)\)(?=[^()]*$)/);
if (!match) {
let url = ListStore.urlItem(key);
@@ -61,7 +69,6 @@ class ListManager {
}
let [hashItem, srcHash] = match; // (hash), hash
-
return blacklist.contains(hashItem) ? "blacklisted"
: this.builtInHashes.has(srcHash) || whitelist.contains(hashItem)
? "whitelisted"
diff --git a/common/Storage.js b/common/Storage.js
index a83ce8f..8010f3f 100644
--- a/common/Storage.js
+++ b/common/Storage.js
@@ -66,6 +66,13 @@ class ListStore {
});
}
+ static inlineItem(url) {
+ // here we simplify and hash inline script references
+ return url.startsWith("inline:") ? url
+ : url.startsWith("view-source:")
+ && url.replace(/^view-source:[\w-+]+:\/+([^/]+).*#line\d+/,"inline://$1#")
+ .replace(/\n[^]*/, s => s.replace(/\s+/g, ' ').substring(0, 16) + "…" + hash(s.trim()));
+ }
static hashItem(hash) {
return hash.startsWith("(") ? hash : `(${hash})`;
}
@@ -125,6 +132,14 @@ class ListStore {
return this.items.has(item);
}
}
+
+var jssha = require('jssha');
+function hash(source){
+ var shaObj = new jssha("SHA-256","TEXT")
+ shaObj.update(source);
+ return shaObj.getHash("HEX");
+}
+
if (typeof module === "object") {
- module.exports = { ListStore, Storage };
+ module.exports = { ListStore, Storage, hash };
}
diff --git a/html/display_panel/content/display-panel.html b/html/display_panel/content/display-panel.html
index 0e4df97..5b9f99f 100644
--- a/html/display_panel/content/display-panel.html
+++ b/html/display_panel/content/display-panel.html
@@ -73,7 +73,10 @@
</p>
<ul>
<li id="li-template">
- <a class="script-url" href="#"></a>:
+ <button class="toggle-source show" title="Show code inline">Show</button>
+ <button class="toggle-source hide" title="Hide code inline">Hide</button>
+ <a class="script-url" href="#" target="librejs_viewsource"></a>:
+ <pre class="source"></pre>
<p class="reason"></p>
<div class="buttons">
<button class="whitelist">Whitelist</button>
@@ -81,6 +84,7 @@
<button class="forget">Forget</button>
<button class="forget" name="*">Forget <span class="domain"></span></button>
</div>
+
</li>
</ul>
</div>
diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js
index 7934fee..83c3a6b 100644
--- a/html/display_panel/content/main_panel.js
+++ b/html/display_panel/content/main_panel.js
@@ -55,6 +55,19 @@ liTemplate.remove();
document.querySelector("#info").addEventListener("click", e => {
let button = e.target;
+ if (button.tagName === "A") {
+ setTimeout(close, 100);
+ return;
+ }
+ if (button.matches(".toggle-source")) {
+ let parent = button.parentNode;
+ if (!parent.querySelector(".source").textContent) {
+ parent.querySelector("a").click();
+ } else {
+ parent.classList.toggle("visible");
+ }
+ return;
+ }
if (!button.matches(".buttons > button")) return;
let li = button.closest("li");
let entry = li && li._scriptEntry || [currentReport.url, "Page's site"];
@@ -111,7 +124,7 @@ function createList(data, group){
container.classList.add("empty");
}
// generate list
- let viewSourceToHuman = /^view-source:(.*)#line(\d+)\(([^)]*)\).*/;
+ let viewSourceToHuman = /^view-source:(.*)#line(\d+)\(([^)]*)\)[^]*/;
for (let entry of entries) {
let [scriptId, reason] = entry;
let li = liTemplate.cloneNode(true);
@@ -119,6 +132,11 @@ function createList(data, group){
a.href = scriptId.split("(")[0];
if (scriptId.startsWith("view-source:")) {
a.target ="LibreJS-ViewSource";
+ let source = scriptId.match(/\n([^]*)/);
+ if (source) {
+ li.querySelector(".source").textContent = source[1];
+ li.querySelector(".toggle-source").style.display = "inline";
+ }
scriptId = scriptId.replace(viewSourceToHuman, "$3 at line $2 of $1");
}
a.textContent = scriptId;
diff --git a/html/display_panel/content/panel-styles.css b/html/display_panel/content/panel-styles.css
index d5c3528..502323f 100644
--- a/html/display_panel/content/panel-styles.css
+++ b/html/display_panel/content/panel-styles.css
@@ -109,6 +109,28 @@ button:disabled {
color: #888 !important;
}
+button.toggle-source {
+ color: #004;
+ margin-right: .5em;
+ width: 4em;
+ overflow: hide;
+}
+
+pre.source {
+ display: none;
+ background: white;
+ border: 1px solid #444;
+ padding: .5em;
+ overflow: auto;
+ max-height: 8em;
+ white-space: pre-wrap;
+}
+
+button.hide { display: none }
+.visible > button.show { display: none !important}
+.visible > pre.source { display: block }
+.visible > button.hide { display: initial }
+
span.accepted, span.blocked {
color:#008e00;
font-size:145%;
diff --git a/main_background.js b/main_background.js
index 65ffe60..8060ebd 100644
--- a/main_background.js
+++ b/main_background.js
@@ -22,10 +22,9 @@
var acorn = require('acorn');
var acornLoose = require('acorn-loose');
-var jssha = require('jssha');
var legacy_license_lib = require("./legacy_license_check.js");
var {ResponseProcessor} = require("./bg/ResponseProcessor");
-var {Storage, ListStore} = require("./common/Storage");
+var {Storage, ListStore, hash} = require("./common/Storage");
var {ListManager} = require("./bg/ListManager");
var {ExternalLicenses} = require("./bg/ExternalLicenses");
@@ -51,16 +50,6 @@ function dbg_print(a,b){
}
}
-/**
-* Wrapper around crypto lib
-*
-*/
-function hash(source){
- var shaObj = new jssha("SHA-256","TEXT")
- shaObj.update(source);
- return shaObj.getHash("HEX");
-}
-
/*
NONTRIVIAL THINGS:
- Fetch
@@ -336,7 +325,11 @@ async function connected(p) {
for (let action of ["whitelist", "blacklist", "forget"]) {
if (m[action]) {
let [key] = m[action];
- if (m.site) key = ListStore.siteItem(key);
+ if (m.site) {
+ key = ListStore.siteItem(key);
+ } else {
+ key = ListStore.inlineItem(key) || key;
+ }
await listManager[action](key);
update = true;
}
@@ -776,23 +769,20 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index
let report = activityReports[tabId] || (activityReports[tabId] = await createReport({tabId}));
updateBadge(tabId, report, !verdict);
let category = await addReportEntry(tabId, sourceHash, {"url": domain, [verdict ? "accepted" : "blocked"]: [url, reason]});
- let scriptSource = verdict ? response : editedSource;
switch(category) {
case "blacklisted":
- if (response.startsWith("javascript:"))
- return result(`# LibreJS: script ${category} by user.`);
- else
- return result(`/* LibreJS: script ${category} by user. */`);
+ editedSource = `/* LibreJS: script ${category} by user. */`;
+ return result(response.startsWith("javascript:")
+ ? `javascript:void(${encodeURIComponent(editedSource)})` : editedSource);
case "whitelisted":
- if (response.startsWith("javascript:"))
- return result(scriptSource);
- else
- return result(`/* LibreJS: script ${category} by user. */\n${scriptSource}`);
+ return result(response.startsWith("javascript:")
+ ? response : `/* LibreJS: script ${category} by user. */\n${response}`);
default:
- if (response.startsWith("javascript:"))
- return result(scriptSource);
- else
- return result(`/* LibreJS: script ${category}. */\n${scriptSource}`);
+ let scriptSource = verdict ? response : editedSource;
+ return result(response.startsWith("javascript:")
+ ? (verdict ? scriptSource : `javascript:void(/* ${scriptSource} */)`)
+ : `/* LibreJS: script ${category}. */\n${scriptSource}`
+ );
}
}
@@ -1053,17 +1043,20 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
let findLine = finder => finder.test(html) && html.substring(0, finder.lastIndex).split(/\n/).length || 0;
if (read_metadata(meta_element) || license) {
console.log("Valid license for intrinsic events found");
- let line = 0;
+ let line, extras;
if (meta_element) {
line = findLine(/id\s*=\s*['"]?LibreJS-info\b/gi);
+ extras = "(0)";
} else if (license) {
line = html.substring(0, html.indexOf(first_script_src)).split(/\n/).length;
+ extras = "\n" + first_script_src;
}
- let viewUrl = line ? `view-source:${documentUrl}#line${line}(<${meta_element ? meta_element.tagName : "SCRIPT"}>)(0)` : url;
+ let viewUrl = line ? `view-source:${documentUrl}#line${line}(<${meta_element ? meta_element.tagName : "SCRIPT"}>)${extras}` : url;
addReportEntry(tabId, url, {url, "accepted":[viewUrl, `Global license for the page: ${license}`]});
// Do not process inline scripts
scripts = [];
} else {
+ let dejaVu = new Map(); // deduplication map & edited script cache
let modified = false;
// Deal with intrinsic events
let intrinsecindex = 0;
@@ -1071,21 +1064,27 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
for (let element of html_doc.all) {
let line = -1;
for (let attr of element.attributes) {
- if (attr.name.startsWith("on") || (attr.name === "href" && attr.value.toLowerCase().startsWith("javascript:"))){
+ let {name, value} = attr;
+ value = value.trim();
+ if (name.startsWith("on") || (name === "href" && value.toLowerCase().startsWith("javascript:"))){
intrinsecindex++;
if (line === -1) {
line = findLine(intrinsicFinder);
}
try {
- let url = `view-source:${documentUrl}#line${line}(<${element.tagName} ${attr.name}>)(${intrinsicIndex})`;
- let edited = await get_script(attr.value, url, tabId, whitelist.contains(url));
- if (edited) {
- let value = edited;
- if (value !== attr.value) {
- modified = true;
- attr.value = value;
- }
- }
+ let key = `<${element.tagName} ${name}="${value}">`;
+ let edited;
+ if (dejaVu.has(key)) {
+ edited = dejaVu.get(key);
+ } else {
+ let url = `view-source:${documentUrl}#line${line}(<${element.tagName} ${name}>)\n${value.trim()}`;
+ if (name === "href") value = decodeURIComponent(value);
+ edited = await get_script(value, url, tabId, whitelist.contains(url)); dejaVu.set(key, edited);
+ }
+ if (edited && edited !== value) {
+ modified = true;
+ attr.value = edited;
+ }
} catch (e) {
console.error(e);
}
@@ -1099,14 +1098,19 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){
let script = scripts[i];
let line = findLine(scriptFinder);
if (!script.src && !(script.type && script.type !== "text/javascript")) {
- let source = script.textContent;
- let url = `view-source:${documentUrl}#line${line}(<SCRIPT>)(${i})`;
- let edited = await get_script(source, url, tabId, whitelisted, i);
- if (edited) {
- let edited_source = edited[0];
- let unedited_source = source.trim();
- if (edited_source.trim() !== unedited_source) {
- script.textContent = edited_source;
+ let source = script.textContent.trim();
+ let editedSource;
+ if (dejaVu.has(source)) {
+ editedSource = dejaVu.get(source);
+ } else {
+ let url = `view-source:${documentUrl}#line${line}(<SCRIPT>)\n${source}`;
+ let edited = await get_script(source, url, tabId, whitelisted, i);
+ editedSource = edited && edited[0].trim();
+ dejaVu.set(url, editedSource);
+ }
+ if (editedSource) {
+ if (source !== editedSource) {
+ script.textContent = editedSource;
modified = modifiedInline = true;
}
}
diff --git a/manifest.json b/manifest.json
index 1a0471e..b299846 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "GNU LibreJS [webExtensions]",
"short_name": "LibreJS [experimental]",
- "version": "7.19rc3",
+ "version": "7.19rc4",
"author": "various",
"description": "Only allows free and/or trivial Javascript to run.",
"applications": {
diff --git a/test/spec/LibreJSSpec.js b/test/spec/LibreJSSpec.js
index 3d61973..57c7f65 100644
--- a/test/spec/LibreJSSpec.js
+++ b/test/spec/LibreJSSpec.js
@@ -94,6 +94,10 @@ describe("LibreJS' components", () => {
let addScript = (html, script, before = "</head>") =>
html.replace(before, `<script>${script}</script>${before}`);
+ let addToBody = (html, fragment) => html.replace("</body>", `${fragment}</body>`);
+
+ let jsUrl = js => `javascript:${encodeURIComponent(js)}`;
+
function extractScripts(html, def = "") {
let matches = html && html.match(/<script>[^]*?<\/script>/g);
return matches && matches.join("") || def;
@@ -165,6 +169,21 @@ describe("LibreJS' components", () => {
expect(scripts).toContain(licensed);
expect(scripts.replace(licensed, "")).not.toContain(nontrivial);
});
+
+ it("should correctly process (de)duplicated inline scripts", async () => {
+ let trivialAsUrl = jsUrl(trivial);
+ let nontrivialAsUrl = jsUrl(nontrivial);
+ let a = (url, label) => `<a href="${url}">${label}</a>`;
+ let mixedPage = `<body></body>`;
+ for (let dup = 0; dup < 3; dup++) {
+ mixedPage = addToBody(mixedPage, a(trivialAsUrl, `Trivial #${dup}`));
+ mixedPage = addToBody(mixedPage, a(nontrivialAsUrl, `Nontrivial #${dup}`));
+ }
+ let processed = await processHtml(mixedPage);
+ expect(processed).not.toBeNull();
+ expect(processed).toContain(trivialAsUrl);
+ expect(processed).not.toContain(nontrivialAsUrl);
+ });
});
describe("The external (Web Labels) license checker", () => {