console.debug("main_background.js");
/**
* This file is the "skeleton" of the final system to determine
* if a script is accepted or blocked.
*
* Some assets taken from script_detector.js
*
*/
// the list of all available event attributes
var intrinsicEvents = [
"onload",
"onunload",
"onclick",
"ondblclick",
"onmousedown",
"onmouseup",
"onmouseover",
"onmousemove",
"onmouseout",
"onfocus",
"onblur",
"onkeypress",
"onkeydown",
"onkeyup",
"onsubmit",
"onreset",
"onselect",
"onchange"
];
/*
NONTRIVIAL THINGS:
- Fetch
- XMLhttpRequest
- eval()
- ?
JAVASCRIPT CAN BE FOUND IN:
- Event handlers (onclick, onload, onsubmit, etc.)
-
-
WAYS TO DETERMINE PASS/FAIL:
- "// @license [magnet link] [identifier]" then "// @license-end" (may also use /* comments)
- Automatic whitelist: (http://bzr.savannah.gnu.org/lh/librejs/dev/annotate/head:/data/script_libraries/script-libraries.json_
-
which may be linked to by a link tag identified by rel="jslicense" or data-jslicense="1"
- In the first script tag, declare the license with @licstart/@licend
*/
var licenses = {
'Apache-2.0':{
'URL': 'http://www.apache.org/licenses/LICENSE-2.0',
'Magnet link': 'magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt'
},
// No identifier was present in documentation
'Artistic-2.0':{
'URL': 'http://www.perlfoundation.org/artistic_license_2_0',
'Magnet link': 'magnet:?xt=urn:btih:54fd2283f9dbdf29466d2df1a98bf8f65cafe314&dn=artistic-2.0.txt'
},
// No identifier was present in documentation
'Boost':{
'URL': 'http://www.boost.org/LICENSE_1_0.txt',
'Magnet link': 'magnet:?xt=urn:btih:89a97c535628232f2f3888c2b7b8ffd4c078cec0&dn=Boost-1.0.txt'
},
// No identifier was present in documentation
'BSD-3-Clause':{
'URL': 'http://opensource.org/licenses/BSD-3-Clause',
'Magnet link': 'magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt',
},
'CPAL-1.0':{
'URL': 'http://opensource.org/licenses/cpal_1.0',
'Magnet link': 'magnet:?xt=urn:btih:84143bc45939fc8fa42921d619a95462c2031c5c&dn=cpal-1.0.txt'
},
'CC0-1.0':{
'URL': 'http://creativecommons.org/publicdomain/zero/1.0/legalcode',
'Magnet link': 'magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt'
},
'EPL-1.0':{
'URL': 'http://www.eclipse.org/legal/epl-v10.html',
'Magnet link': 'magnet:?xt=urn:btih:4c6a2ad0018cd461e9b0fc44e1b340d2c1828b22&dn=epl-1.0.txt'
},
'Expat':{
'URL': 'http://www.jclark.com/xml/copying.txt',
'Magnet link': 'magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt'
},
'FreeBSD':{
'URL': 'http://www.freebsd.org/copyright/freebsd-license.html',
'Magnet link': 'magnet:?xt=urn:btih:87f119ba0b429ba17a44b4bffcab33165ebdacc0&dn=freebsd.txt'
},
'GPL-2.0':{
'URL': 'http://www.gnu.org/licenses/gpl-2.0.html',
'Magnet link': 'magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt'
},
'GPL-3.0':{
'URL': 'http://www.gnu.org/licenses/gpl-3.0.html',
'Magnet link': 'magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt'
},
'LGPL-2.1':{
'URL': 'http://www.gnu.org/licenses/lgpl-2.1.html',
'Magnet link': 'magnet:?xt=urn:btih:5de60da917303dbfad4f93fb1b985ced5a89eac2&dn=lgpl-2.1.txt'
},
'LGPL-3.0':{
'URL': 'http://www.gnu.org/licenses/lgpl-3.0.html',
'Magnet link': 'magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt'
},
'AGPL-3.0':{
'URL': 'http://www.gnu.org/licenses/agpl-3.0.html',
'Magnet link': 'magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt'
},
'ISC':{
'URL': 'https://www.isc.org/downloads/software-support-policy/isc-license/',
'Magnet link': 'magnet:?xt=urn:btih:b8999bbaf509c08d127678643c515b9ab0836bae&dn=ISC.txt'
},
'MPL-2.0':{
'URL': 'http://www.mozilla.org/MPL/2.0',
'Magnet link': 'magnet:?xt=urn:btih:3877d6d54b3accd4bc32f8a48bf32ebc0901502a&dn=mpl-2.0.txt'
},
// "Public domain is not a license"
// Replace with CC0?
'Public-Domain':{
'URL': 'https://www.gnu.org/licenses/license-list.html#PublicDomain',
'Magnet link': 'magnet:?xt=urn:btih:e95b018ef3580986a04669f1b5879592219e2a7a&dn=public-domain.txt'
},
'UPL-1.0': {
'URL': 'https://oss.oracle.com/licenses/upl/',
'Magnet link': 'magnet:?xt=urn:btih:478974f4d41c3fa84c4befba25f283527fad107d&dn=upl-1.0.txt'
},
'WTFPL': {
'URL': 'http://www.wtfpl.net/txt/copying/',
'Magnet link': 'magnet:?xt=urn:btih:723febf9f6185544f57f0660a41489c7d6b4931b&dn=wtfpl.txt'
},
'Unlicense':{
'URL': 'http://unlicense.org/UNLICENSE',
'Magnet link': 'magnet:?xt=urn:btih:5ac446d35272cc2e4e85e4325b146d0b7ca8f50c&dn=unlicense.txt'
},
// No identifier was present in documentation
'X11':{
'URL': 'http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3',
'Magnet link': 'magnet:?xt=urn:btih:5305d91886084f776adcf57509a648432709a7c7&dn=x11.txt'
},
// Picked one of the two links that were there
'Modified-BSD':{
'URL': 'http://www.xfree86.org/current/LICENSE4.html',
'Magnet link': 'magnet:?xt=urn:btih:12f2ec9e8de2a3b0002a33d518d6010cc8ab2ae9&dn=xfree86.txt'
}
}
// Objects which could be used to do nontrivial things
// Bracket suffix notation could still be exploited to get some of these objects
var reserved_objects = [
"fetch",
"XMLHttpRequest",
"chrome", // only on chrome
"browser", // only on firefox
"eval"
];
/**
*
* Sets global variable "webex" to either "chrome" or "browser" for
* use on Chrome or a Firefox variant.
*
* Change this to support a new browser that isn't Chrome or Firefox,
* given that it supports webExtensions.
*
* (Use the variable "webex" for all API calls after calling this)
*/
var webex;
function set_webex(){
if(typeof(browser) == "object"){
webex = browser;
}
if(typeof(chrome) == "object"){
webex = chrome;
}
}
/*
*
* Called when something changes the persistent data of the add-on.
*
* The only things that should need to change this data are:
* a) The "Whitelist this page" button
* b) The options screen
*
* When the actual blocking is implemented, this will need to comminicate
* with its code to update accordingly
*
*/
function options_listener(changes, area){
// The cache must be flushed when settings are changed
// TODO: See if this can be minimized
function flushed(){
console.log("cache flushed");
}
//var flushingCache = webex.webRequest.handlerBehaviorChanged(flushed);
console.log("Items updated in area" + area +": ");
var changedItems = Object.keys(changes);
var changed_items = "";
for (var i = 0; i < changedItems.length; i++){
var item = changedItems[i];
changed_items += item + ",";
}
console.log(changed_items);
}
/**
* Executes the "Display this report in new tab" function
* by opening a new tab with whatever HTML is in the popup
* at the moment.
*/
var active_connections = {};
var unused_data = {};
function open_popup_tab(data){
console.log(data);
function gotPopup(popupURL){
var creating = webex.tabs.create({"url":popupURL},function(a){
console.log("[TABID:"+a["id"]+"] creating unused data entry from parent window's content");
unused_data[a["id"]] = data;
});
}
var gettingPopup = webex.browserAction.getPopup({},gotPopup);
}
/**
*
* Clears local storage (the persistent data)
*
*/
function debug_delete_local(){
webex.storage.local.clear();
console.log("Local storage cleared");
}
/**
*
* Prints local storage (the persistent data) as well as the temporary popup object
*
*/
function debug_print_local(){
function storage_got(items){
console.log("%c Local storage: ", 'color: red;');
for(var i in items){
console.log("%c "+i+" = "+items[i], 'color: blue;');
}
}
console.log("%c Variable 'unused_data': ", 'color: red;');
console.log(unused_data);
webex.storage.local.get(storage_got);
}
/**
*
*
* Sends a message to the content script that sets the popup entries for a tab.
*
* var example_blocked_info = {
* "accepted": [["REASON 1","SOURCE 1"],["REASON 2","SOURCE 2"]],
* "blocked": [["REASON 1","SOURCE 1"],["REASON 2","SOURCE 2"]],
* "url": "example.com"
* }
*
* NOTE: This WILL break if you provide inconsistent URLs to it.
* Make sure it will use the right URL when refering to a certain script.
*
*/
function update_popup(tab_id,blocked_info,update=false){
var new_blocked_data;
function get_sto(items){
//************************************************************************//
// Move scripts that are accepted/blocked but whitelisted to "whitelisted" category
// (Ideally, they just would not be tested in the first place because that would be faster)
var url = blocked_info["url"];
if(url === undefined){
console.error("No url passed to update_popup");
return 1;
}
function get_status(script_name){
var script_key = encodeURI(url)+" "+encodeURI(script_name);
if(items[script_key] === undefined){
return "none";
}
return items[script_key];
}
function is_bl(script_name){
if(get_status(script_name) == "blacklist"){
return true;
}
return false;
}
function is_wl(script_name){
if(get_status(script_name) == "whitelist"){
return true;
}
return false;
}
new_blocked_data = {
"accepted":[],
"blocked":[],
"blacklisted":[],
"whitelisted":[],
"url": url
};
for(var type in blocked_info){
for(var script_arr in blocked_info[type]){
if(is_bl(blocked_info[type][script_arr][0])){
new_blocked_data["blacklisted"].push(blocked_info[type][script_arr]);
console.log("Script " + blocked_info[type][script_arr][0] + " is blacklisted");
continue;
}
if(is_wl(blocked_info[type][script_arr][0])){
new_blocked_data["whitelisted"].push(blocked_info[type][script_arr]);
console.log("Script " + blocked_info[type][script_arr][0] + " is whitelisted");
continue;
}
if(type == "url"){
continue;
}
// either "blocked" or "accepted"
new_blocked_data[type].push(blocked_info[type][script_arr]);
console.log("Script " + blocked_info[type][script_arr][0] + " isn't whitelisted or blacklisted");
}
}
console.log(new_blocked_data);
//***********************************************************************************************//
// store the blocked info until it is opened and needed
if(update == false && active_connections[tab_id] === undefined){
console.log("[TABID:"+tab_id+"]"+"Storing blocked_info for when the browser action is opened or asks for it.");
unused_data[tab_id] = new_blocked_data;
} else{
unused_data[tab_id] = new_blocked_data;
console.log("[TABID:"+tab_id+"]"+"Sending blocked_info directly to browser action");
active_connections[tab_id].postMessage({"show_info":new_blocked_data});
delete active_connections[tab_id];
}
}
webex.storage.local.get(get_sto);
}
/**
*
* This is what you call when a page gets changed to update the info box.
*
* Sends a message to the content script that adds a popup entry for a tab.
*
* var example_blocked_info = {
* "accepted"or "blocked": ["name","reason"],
* "url": "example.com"
* }
*
* Returns true/false based on if script should be accepted/denied respectively
*
* NOTE: This WILL break if you provide inconsistent URLs to it.
* Make sure it will use the right URL when refering to a certain script.
*
*/
function add_popup_entry(tab_id,blocked_info,update=false){
return new Promise((resolve, reject) => {
var new_blocked_data;
// Make sure the entry in unused_data exists
var url = blocked_info["url"];
if(url === undefined){
console.error("No url passed to update_popup");
return 1;
}
if(unused_data[tab_id] === undefined){
unused_data[tab_id] = {
"accepted":[],
"blocked":[],
"blacklisted":[],
"whitelisted":[],
"url": url
};
}
if(unused_data[tab_id]["accepted"] === undefined){unused_data[tab_id]["accepted"] = [];}
if(unused_data[tab_id]["blocked"] === undefined){unused_data[tab_id]["blocked"] = [];}
if(unused_data[tab_id]["blacklisted"] === undefined){unused_data[tab_id]["blacklisted"] = [];}
if(unused_data[tab_id]["whitelisted"] === undefined){unused_data[tab_id]["whitelisted"] = [];}
var type = "";
if(blocked_info["accepted"] !== undefined){
type = "accepted";
}
if(blocked_info["blocked"] !== undefined){
type = "blocked";
}
function get_sto(items){
function get_status(script_name){
var script_key = encodeURI(url)+" "+encodeURI(script_name);
if(items[script_key] === undefined){
return "none";
}
return items[script_key];
}
function is_bl(script_name){
if(get_status(script_name) == "blacklist"){
return true;
}
return false;
}
function is_wl(script_name){
if(get_status(script_name) == "whitelist"){
return true;
}
return false;
}
if(is_bl(blocked_info[type][0])){
unused_data[tab_id]["blacklisted"].push(blocked_info[type]);
//console.log("Script " + blocked_info[type][0] + " is blacklisted");
resolve("bl");
}
else if(is_wl(blocked_info[type][0])){
unused_data[tab_id]["whitelisted"].push(blocked_info[type]);
//console.log("Script " + blocked_info[type][0] + " is whitelisted");
resolve("wl");
} else{
// either "blocked" or "accepted"
unused_data[tab_id][type].push(blocked_info[type]);
//console.log("Script " + blocked_info[type][0] + " isn't whitelisted or blacklisted");
resolve("none");
}
}
webex.storage.local.get(get_sto);
});
}
function get_domain(url){
var domain = url.replace('http://','').replace('https://','').split(/[/?#]/)[0];
if(url.indexOf("http://") == 0){
domain = "http://" + domain;
}
else if(url.indexOf("https://") == 0){
domain = "https://" + domain;
}
domain = domain + "/";
domain = domain.replace(/ /g,"");
return domain;
}
/**
*
* This is the callback where the content scripts of the browser action will contact the background script.
*
*/
var portFromCS;
function connected(p) {
p.onMessage.addListener(function(m) {
/**
* Updates the entry of the current URL in storage
*/
function set_script(script,val){
if(val != "whitelist" && val != "forget" && val != "blacklist"){
console.error("Key must be either 'whitelist', 'blacklist' or 'forget'");
}
// (Remember that we do not trust the names of scripts.)
var current_url = "";
function geturl(tabs) {
current_url = tabs[0]["url"];
var domain = get_domain(current_url);
// The space char is a valid delimiter because encodeURI() replaces it with %20
var scriptkey = encodeURI(domain)+" "+encodeURI(script);
if(val == "forget"){
var prom = webex.storage.local.remove(scriptkey);
// TODO: This should produce a "Refresh the page for this change to take effect" message
} else{
var newitem = {};
newitem[scriptkey] = val;
webex.storage.local.set(newitem);
}
}
var querying = webex.tabs.query({active: true,currentWindow: true},geturl);
return;
}
var update = false;
var contact_finder = false;
if(m["whitelist"] !== undefined){
set_script(m["whitelist"][0],"whitelist");
update = true;
}
if(m["blacklist"] !== undefined){
set_script(m["blacklist"][0],"blacklist");
update = true;
}
if(m["forget"] !== undefined){
set_script(m["forget"][0],"forget");
update = true;
}
//
if(m["open_popup_tab"] !== undefined){
open_popup_tab(m["open_popup_tab"]);
}
// a debug feature
if(m["printlocalstorage"] !== undefined){
debug_print_local();
}
// invoke_contact_finder
if(m["invoke_contact_finder"] !== undefined){
contact_finder = true;
inject_contact_finder();
}
// a debug feature (maybe give the user an option to do this?)
if(m["deletelocalstorage"] !== undefined){
debug_delete_local();
}
function logTabs(tabs) {
if(contact_finder){
console.log("[TABID:"+tab_id+"] Injecting contact finder");
inject_contact_finder(tabs[0]["id"]);
}
if(update){
console.log("%c updating tab "+tabs[0]["id"],"color: red;");
update_popup(tabs[0]["id"],unused_data[tabs[0]["id"]],true);
active_connections[tabs[0]["id"]] = p;
}
for(var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
var tab_id = tab["id"];
if(unused_data[tab_id] !== undefined){
// If we have some data stored here for this tabID, send it
console.log("[TABID:"+tab_id+"]"+"Sending stored data associated with browser action");
p.postMessage({"show_info":unused_data[tab_id]});
} else{
// create a new entry
unused_data[tab_id] = {"url":tab["url"],"blocked":"","accepted":""};
p.postMessage({"show_info":unused_data[tab_id]});
console.log("[TABID:"+tab_id+"]"+"No data found, creating a new entry for this window.");
}
}
}
var querying = webex.tabs.query({active: true,currentWindow: true},logTabs);
});
}
/**
* The callback for tab closings.
*
* Delete the info we are storing about this tab if there is any.
*
*/
function delete_removed_tab_info(tab_id, remove_info){
console.log("[TABID:"+tab_id+"]"+"Deleting stored info about closed tab");
if(unused_data[tab_id] !== undefined){
delete unused_data[tab_id];
}
if(active_connections[tab_id] !== undefined){
delete active_connections[tab_id];
}
}
/**
* Makes it so we can return redirect requests to local blob URLs
*
* TODO: Make it so that it adds the website itself to the permissions of all keys
*
*/
function change_csp(e) {
var index = 0;
var csp_header = "";
for(var i = 0; i < e["responseHeaders"].length; i++){
if(e["responseHeaders"][i]["name"].toLowerCase() == "content-security-policy"){
csp_header = e["responseHeaders"][i]["value"];
index = i;
var keywords = csp_header.replace(/;/g,'","');
keywords = JSON.parse('["' + keywords.substr(0,keywords.length) + '"]');
// Iterates over the keywords inside the CSP header
for(var j = 0; j < keywords.length; j++){
var matchres = keywords[j].match(/[\-\w]+/g);
if(matchres != null && matchres[0] == "script-src"){
// Test to see if they have a hash and then delete it
// TODO: Make sure this is a good idea.
keywords[j] = keywords[j].replace(/\s?'sha256-[\w+/]+=+'/g,"");
keywords[j] = keywords[j].replace(/\s?'sha384-[\w+/]+=+'/g,"");
keywords[j] = keywords[j].replace(/\s?'sha512-[\w+/]+=+'/g,"");
keywords[j] = keywords[j].replace(/'strict-dynamic'/g,"");
keywords[j] = keywords[j].replace(/;/g,"");
// This is the string that we add to every CSP
keywords[j] += " data: blob: 'report-sample'";
console.log("%c new script-src section:","color:green;")
console.log(keywords[j]+ "; ");
}
}
var csp_header = "";
for(var j = 0; j < keywords.length; j++){
csp_header = csp_header + keywords[j] + "; ";
}
e["responseHeaders"][i]["value"] = csp_header;
}
}
if(csp_header == ""){
//console.log("%c no CSP.","color: red;");
}else{
//console.log("%c new CSP:","color: green;");
//console.log(e["responseHeaders"][index]["value"]);
}
return {responseHeaders: e.responseHeaders};
}
/*
*
* XMLHttpRequests the content of a script so we can modify it
* before turning it to a blob and redirecting to its URL
*
*/
function get_content(url){
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open("get",url);
xhr.onload = function(){
resolve(this);
}
xhr.onerror = function(){
console.log("%c could not get content of "+url+".","color:red;")
reject(JSON.stringify(this));
}
xhr.send();
});
}
/**
* Turns a blob URL into a data URL
*
*/
function get_data_url(blob,url){
return new Promise((resolve, reject) => {
//var url = URL.createObjectURL(blob);
var reader = new FileReader();
reader.addEventListener("load", function(){
//console.log("redirecting");
//console.log(url);
//console.log("to");
//console.log(reader.result);
resolve({"redirectUrl": reader.result});
});
reader.readAsDataURL(blob);
});
}
/* *********************************************************************************************** */
// (This is part of eval_test.js with a few console.logs/comments removed)
function evaluate(script,name){
function reserved_object_regex(object){
var arith_operators = "\\+\\-\\*\\/\\%\\=";
var scope_chars = "\{\}\]\[\(\)\,";
var trailing_chars = "\s*"+"\(\.\[";
return new RegExp("(?:[^\\w\\d]|^|(?:"+arith_operators+"))"+object+'(?:\\s*?(?:[\\;\\,\\.\\(\\[])\\s*?)',"g");
}
reserved_object_regex("window");
var all_strings = new RegExp('".*?"'+"|'.*?'","gm");
var ml_comment = /\/\*([\s\S]+?)\*\//g;
var il_comment = /\/\/.+/gm;
var bracket_pairs = /\[.+?\]/g;
var temp = script.replace(/'.+?'+/gm,"'string'");
temp = temp.replace(/".+?"+/gm,'"string"');
temp = temp.replace(ml_comment,"");
temp = temp.replace(il_comment,"");
console.log("------evaluation results for "+ name +"------");
console.log("Script accesses reserved objects?");
var flag = true;
var reason = ""
// This is where individual "passes" are made over the code
for(var i = 0; i < reserved_objects.length; i++){
var res = reserved_object_regex(reserved_objects[i]).exec(script);
if(res != null){
console.log("%c fail","color:red;");
flag = false;
reason = "Script uses a reserved object (" + reserved_objects[i] + ")";
}
}
if(flag){
console.log("%c pass","color:green;");
}
// If flag is set true at this point, the script is trivial
if(flag){
reason = "Script was determined to be trivial.";
}
return [flag,reason+"
"];
}
function license_valid(matches){
if(matches.length != 4){
return [false, "malformed or unrecognized license tag"];
}
if(matches[1] != "@license"){
return [false, "malformed or unrecognized license tag"];
}
if(licenses[matches[3]] === undefined){
return [false, "malformed or unrecognized license tag"];
}
if(licenses[matches[3]]["Magnet link"] != matches[2]){
return [false, "malformed or unrecognized license tag"];
}
return [true,"Recognized license as '"+matches[3]+"'
"];
}
/**
*
* Evaluates the content of a script (license, if it is non-trivial)
*
* Returns
* [
* true (accepted) or false (denied),
* edited content,
* reason text
* ]
*/
function license_read(script_src,name){
var reason_text = "";
var edited_src = "";
var unedited_src = script_src;
var nontrivial_status;
var parts_denied = false;
var parts_accepted = false;
while(true){
// TODO: support multiline comments
var matches = /\/\/\s*?(@license)\s([\S]+)\s([\S]+$)/gm.exec(unedited_src);
if(matches == null){
nontrivial_status = evaluate(unedited_src,name);
if(nontrivial_status[0] == true){
parts_accepted = true;
edited_src += unedited_src;
} else{
parts_denied = true;
edited_src += "\n/*\nLIBREJS BLOCKED:"+nontrivial_status[1]+"\n*/\n";
}
reason_text += "\n" + nontrivial_status[1];
if(parts_denied == true && parts_accepted == true){
reason_text = "Script was determined partly non-trivial after editing. (check source for details)\n"+reason_text;
}
if(parts_denied == true && parts_accepted == false){
return [false,edited_src,reason_text];
}
return [true,edited_src,reason_text];
}
console.log("Found a license tag");
var before = unedited_src.substr(0,matches["index"]);
nontrivial_status = evaluate(before,name);
if(nontrivial_status[0] == true){
parts_accepted = true;
edited_src += before;
} else{
parts_denied = true;
edited_src += "\n/*\nLIBREJS BLOCKED:"+nontrivial_status[1]+"\n*/\n";
}
unedited_src = unedited_src.substr(matches["index"],unedited_src.length);
// TODO: support multiline comments
matches_end = /\/\/\s*?(@license-end)/gm.exec(unedited_src);
if(matches_end == null){
console.log("ERROR: @license with no @license-end");
return [false,"\n/*\n ERROR: @license with no @license-end \n*/\n","ERROR: @license with no @license-end"];
}
var endtag_end_index = matches_end["index"]+matches_end[0].length;
var license_res = license_valid(matches);
if(license_res[0] == true){
edited_src = edited_src + unedited_src.substr(0,endtag_end_index);
reason_text += "\n" + license_res[1];
} else{
edited_src = edited_src + "\n/*\n"+license_res[1]+"\n*/\n";
reason_text += "\n" + license_res[1];
}
// trim off everything we just evaluated
unedited_src = unedited_src.substr(endtag_end_index,unedited_src.length);
}
}
/* *********************************************************************************************** */
// TODO: Test if this script is being loaded from another domain compared to unused_data[tabid]["url"]
// TODO: test if this script is whitelisted by name (from the GUI with the button)
function get_script(url,tabid,wl){
return new Promise((resolve, reject) => {
var response = get_content(url);
response.then(function(response) {
if(unused_data[tabid] === undefined){
unused_data[tabid] = {"url":url,"accepted":[],"blocked":[]};
}
var tok_index = url.split("/").length;
var scriptname = url.split("/")[tok_index-1];
if(wl == true){
// Accept without reading script, it was explicitly whitelisted
if(typeof(unused_data[tabid]["accepted"].push) != "function"){
unused_data[tabid]["accepted"] = [[scriptname,"Page is whitelisted in preferences"]];
} else{
unused_data[tabid]["accepted"].push([scriptname,"Page is whitelisted in preferences"]);
}
var blob = new Blob([response.responseText], {type : 'application/javascript'});
resolve(get_data_url(blob,url));
return;
}
var edited = license_read(response.responseText,scriptname);
var verdict = edited[0];
var popup_res;
var domain = get_domain(url);
if(verdict == true){
popup_res = add_popup_entry(tabid,{"url":domain,"accepted":[scriptname,edited[2]]});
} else{
popup_res = add_popup_entry(tabid,{"url":domain,"blocked":[scriptname,edited[2]]});
}
popup_res.then(function(list_verdict){
var blob;
if(list_verdict == "wl"){
// redirect to the unedited version
blob = new Blob(["\n/*\n LibreJS: Script whitelisted by user \n*/\n"+response.responseText], {type : 'application/javascript'});
}else if(list_verdict == "bl"){
// Blank the entire script
blob = new Blob(["\n/*\n LibreJS: Script blacklisted by user \n*/\n"], {type : 'application/javascript'});
} else{
// Return the edited (normal) version
blob = new Blob([edited[1]], {type : 'application/javascript'});
}
//blob = new Blob(["console.log('LibreJS edited script');\n"+edited[1]], {type : 'application/javascript'});
resolve(get_data_url(blob,url));
});
});
});
}
function read_script(a){
return new Promise((resolve, reject) => {
var res = test_url_whitelisted(a.url);
res.then(function(whitelisted){
if(whitelisted == true){
// Doesn't matter if this is accepted or blocked, it will still be whitelisted
resolve(get_script(a.url,a["tabId"],true));
} else{
resolve(get_script(a.url,a["tabId"],false));
}
});
});
/*
// Minimal example of how to edit scripts
var edited = "console.log('it worked');\n";
var blob = new Blob([edited], {type : 'application/javascript'});
return get_data_url(blob);
*/
}
function read_document(a){
// This needs to be handled in a different way because it sets the domain
// of the document to "data:" which breaks relative URLs.
return new Promise((resolve, reject) => {
var response = get_content(a.url);
response.then(function(res){
// Reset the block scripts since we just opened a new document
unused_data[a["tabId"]] = {"url":a.url,"accepted":[],"blocked":[]};
//setup_counter(res.response,a["tabId"])
resolve();
//var blob = new Blob([res.response], {type : 'text/html'});
//resolve(get_data_url(blob));
});
});
}
/**
* Initializes various add-on functions
* only meant to be called once when the script starts
*/
function init_addon(){
set_webex();
webex.runtime.onConnect.addListener(connected);
webex.storage.onChanged.addListener(options_listener);
webex.tabs.onRemoved.addListener(delete_removed_tab_info);
var targetPage = "https://developer.mozilla.org/en-US/Firefox/Developer_Edition";
// Updates the content security policy so we can redirect to local URLs
webex.webRequest.onHeadersReceived.addListener(
change_csp,
{urls: [""]},
["blocking", "responseHeaders"]
);
// Analyzes remote scripts
webex.webRequest.onBeforeRequest.addListener(
read_script,
{urls:[""], types:["script"]},
["blocking"]
);
// Analyzes the scripts inside of HTML
webex.webRequest.onBeforeRequest.addListener(
read_document,
{urls:[""], types:["main_frame"]},
["blocking"]
);
}
/**
* Test if a page is whitelisted/blacklisted.
*
* The input here is tested against the comma seperated string found in the options.
*
* It does NOT test against the individual entries created by hitting the "whitelist"
* button for a script in the browser action.
*/
function test_url_whitelisted(url){
return new Promise((resolve, reject) => {
function cb(items){
var wl = items["pref_whitelist"];
if(wl !== undefined){
wl = wl.split(",");
} else{
resolve(false);
return;
}
var regex;
for(i in wl){
var s = wl[i].replace(/\*/g,"\\S*");
s = s.replace(/\./g,"\\.");
regex = new RegExp(s, "g");
if(url.match(regex)){
//console.log("%c" + wl[i] + " matched " + url,"color: purple;");
resolve(true);
return;
} else{
//console.log("%c" + wl[i] + " didn't match " + url,"color: #dd0000;");
}
}
resolve(false);
return;
}
webex.storage.local.get(cb);
});
}
/**
* Loads the contact finder on the given tab ID.
*/
function inject_contact_finder(tab_id){
function executed(result) {
console.log("[TABID:"+tab_id+"]"+"finished executing contact finder: " + result);
}
var executing = webex.tabs.executeScript(tab_id, {file: "/contact_finder.js"}, executed);
}
init_addon();