From d6d9e376c9be9da7826b131e1ae0ce35b233281d Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Oct 31 2018 17:53:54 +0000 Subject: [PATCH 1/5] Reimplement intrinsic event iteration. The new code processes any attribute that starts with "on", or href attributes that start with "javascript:" and parses them assigning them a unique url, so that all of them are independently addressable by the panel and by white/blacklisting. This fixes the fact that the previous code would only parse some types of attributes using a list of names, would only parse the first matching attribute in an element, and would only list one intrinsic event per page in the panel. It also fixes the content of the attribute being mangled to the first character of the original code. --- diff --git a/main_background.js b/main_background.js index 5c2e8df..14d2668 100644 --- a/main_background.js +++ b/main_background.js @@ -61,29 +61,6 @@ function hash(source){ return shaObj.getHash("HEX"); } - -// the list of all available event attributes -var intrinsic_events = [ - "onload", - "onunload", - "onclick", - "ondblclick", - "onmousedown", - "onmouseup", - "onmouseovr", - "onmousemove", - "onmouseout", - "onfocus", - "onblur", - "onkeypress", - "onkeydown", - "onkeyup", - "onsubmit", - "onreset", - "onselect", - "onchange" -]; - /* NONTRIVIAL THINGS: - Fetch @@ -1056,20 +1033,21 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){ let modified = false; // Deal with intrinsic events + let intrinsecindex = 0; for (let element of html_doc.all) { - let attributes = element.attributes; - for (let event of intrinsic_events) { - if (event in attributes) { - let attr = attributes[event]; + for (let attr of element.attributes){ + if (attr.name.startsWith("on") || (attr.name === "href" && attr.value.startsWith("javascript:"))){ + intrinsecindex++; try { - let edited = await get_script(attr.value, `Intrinsic event [${event}]`); - if (edited) { - let value = edited[0]; - if (value !== attr.value) { - modified = true; - attr.value = value; + let url = `${documentUrl}# Intrinsic event ${intrinsecindex} [${attr.name}]`; + 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; + } } - } } catch (e) { console.error(e); } From 8c1bd035a207836c337b3169fe7b7316668c68fd Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Oct 31 2018 18:03:43 +0000 Subject: [PATCH 2/5] Defining or calling functions does not qualify as nontrivial --- diff --git a/main_background.js b/main_background.js index 14d2668..8848956 100644 --- a/main_background.js +++ b/main_background.js @@ -493,6 +493,7 @@ function full_evaluate(script){ return script.charAt(end+i) == "["; } var error_count = 0; + var defines_functions = false; while(toke !== undefined && toke.type != acorn.tokTypes.eof){ if(toke.type.keyword !== undefined){ //dbg_print("Keyword:"); @@ -501,10 +502,8 @@ function full_evaluate(script){ // This type of loop detection ignores functional loop alternatives and ternary operators if(toke.type.keyword == "function"){ - dbg_print("%c NONTRIVIAL: Function declaration.","color:red"); - if(DEBUG == false){ - return [false,"NONTRIVIAL: Function declaration."]; - } + dbg_print("%c NOTICE: Function declaration.","color:green"); + defines_functions = true; } if(loopkeys[toke.type.keyword] !== undefined){ @@ -535,13 +534,6 @@ function full_evaluate(script){ } } }else if(status === undefined){// is the identifier user defined? - // Are arguments being passed to a user defined variable? - if(being_called(toke.end)){ - dbg_print("%c NONTRIVIAL: User defined variable '"+toke.value+"' called as function","color:red"); - if(DEBUG == false){ - return [false,"NONTRIVIAL: User defined variable '"+toke.value+"' called as function"]; - } - } // Is there bracket suffix notation? if(is_bsn(toke.end)){ dbg_print("%c NONTRIVIAL: Bracket suffix notation on variable '"+toke.value+"'","color:red"); @@ -563,7 +555,10 @@ function full_evaluate(script){ } dbg_print("%cAppears to be trivial.","color:green;"); - return [true,"Script appears to be trivial."]; + if (defines_functions === true) + return [true,"Script appears to be trivial but defines functions."]; + else + return [true,"Script appears to be trivial."]; } From 93578165ff522bca4f8ee031e43a363f7f2ecc01 Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Oct 31 2018 18:04:18 +0000 Subject: [PATCH 3/5] More generalized license matching * Allow for length >= 4 since somebody may write "GPL2.0 or later" or something like that. It should still work if the tag follows the new recommendations. * Match the link to the URL or Magnet Link fields in the licenses table. Currently it matches only by id, and the link field is only used to fail the match if the link is not the same as in the table. --- diff --git a/main_background.js b/main_background.js index 8848956..0b6e407 100644 --- a/main_background.js +++ b/main_background.js @@ -616,15 +616,23 @@ function evaluate(script,name){ function validateLicense(matches) { - if (!(Array.isArray(matches) && matches.length === 4)){ + if (!(Array.isArray(matches) && matches.length >= 4)){ return [false, "Malformed or unrecognized license tag."]; } let [all, tag, link, id] = matches; - let license = licenses[id]; + let license = null; + if (licenses[id]) + license = licenses[id]; + for (let key in licenses){ + if (licenses[key]["Magnet link"] === link) + license = licenses[key]; + if (licenses[key]["URL"] === link) + license = licenses[key]; + } if(!license){ return [false, `Unrecognized license "${id}"`]; } - if(license["Magnet link"] != link){ + if (!(license["Magnet link"] === link || license["URL"] === link)){ return [false, `License magnet link does not match for "${id}".`]; } return [true, `Recognized license: "${id}".`]; From 82ad83dd6fb74ff26d41c57332bb8d0df37c408d Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Oct 31 2018 18:05:26 +0000 Subject: [PATCH 4/5] Correctly handle multiple inline scripts, multiple intrinsic events, whitelisting/blacklisting and listing in the panel. Before this, only the first inline script would get properly handled. This also corrects script comments for href="javascript:foo" types of scripts, which cannot get comments added in to state the result of LibreJS parsing. --- diff --git a/main_background.js b/main_background.js index 0b6e407..06fd6cb 100644 --- a/main_background.js +++ b/main_background.js @@ -676,7 +676,10 @@ function license_read(scriptSrc, name, external = false){ editedSrc += s; } else { partsDenied = true; - editedSrc += `\n/*\nLIBREJS BLOCKED: ${message}\n*/\n`; + if (s.startsWith("javascript:")) + editedSrc += `# LIBREJS BLOCKED: ${message}`; + else + editedSrc += `/*\nLIBREJS BLOCKED: ${message}\n*/`; } reason += `\n${message}`; return trivial; @@ -756,7 +759,10 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index : "Address whitelisted by user"; addReportEntry(tabId, url, {"whitelisted": [url, reason], url}); } - return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); + if (response.startsWith("javascript:")) + return result(response); + else + return result(`/* LibreJS: script whitelisted by user preference. */\n${response}`); } let [verdict, editedSource, reason] = license_read(response, scriptName, index === -2); @@ -773,10 +779,20 @@ async function get_script(response, url, tabId = -1, whitelisted = false, index 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. */`); case "whitelisted": - return result(`/* LibreJS: script ${category} by user. */\n${scriptSource}`); + if (response.startsWith("javascript:")) + return result(scriptSource); + else + return result(`/* LibreJS: script ${category} by user. */\n${scriptSource}`); default: - return result(`/* LibreJS: script ${category}. */\n${scriptSource}`); + if (response.startsWith("javascript:")) + return result(scriptSource); + else + return result(`/* LibreJS: script ${category}. */\n${scriptSource}`); } } @@ -1061,6 +1077,7 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){ let modifiedInline = false; for(let i = 0, len = scripts.length; i < len; i++) { let script = scripts[i]; + let url = `${documentUrl}# script ${i}`; if (!script.src && !(script.type && script.type !== "text/javascript")) { let edited = await get_script(script.textContent, url, tabId, whitelisted, i); if (edited) { @@ -1072,11 +1089,11 @@ async function editHtml(html, documentUrl, tabId, frameId, whitelisted){ } } } - if (modified) { - return modifiedInline - ? await remove_noscripts(html_doc) - : doc2HTML(html_doc); - } + } + if (modified) { + return modifiedInline + ? await remove_noscripts(html_doc) + : doc2HTML(html_doc); } } return null; From e2cd5ae6556359adf5116cd982a7645a69516306 Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Oct 31 2018 18:25:23 +0000 Subject: [PATCH 5/5] Tokens onerror and top should not be considered nontrivial --- diff --git a/fname_data.json b/fname_data.json index 8caa6d7..e8ab932 100644 --- a/fname_data.json +++ b/fname_data.json @@ -335,7 +335,7 @@ module.exports = { "onwebkitanimationiteration": true, "onwebkitanimationstart": true, "onwebkittransitionend": true, - "onerror": true, + "onerror": false, "onafterprint": true, "onbeforeprint": true, "onbeforeunload": true, @@ -808,7 +808,7 @@ module.exports = { "window": false, "document": true, "location": false, - "top": true, + "top": false, "netscape": true, "Node": true, "Document": true,