Version 1.9.0

- Added #284
- Added #56
- Fixed #241
- Possible fix & workaround for #203
- Fixed bug in "history tracking injection protection". This option was not disabled, when the global filter switch are on off
- Added an option to im-/export the log (requires the `downloads` permission)
- Added an option to im-/export the settings (requires the `downloads` permission)
- Added information page for blocked sites, when they are called in the `main_frame`
- Added "multiple times URL encodes" recognition
- Refactoring
- Changed background script loading sequence to prevent that required functions are not yet loaded.
This commit is contained in:
Kevin Röbert
2019-10-23 01:16:12 +02:00
parent 9df322a49a
commit d064537c07
23 changed files with 839 additions and 272 deletions

View File

@@ -33,9 +33,9 @@ $(document).ready(function(){
* This function cleans all URLs line by line in the textarea.
*/
function cleanURLs() {
var cleanTArea = $('#cleanURLs');
var dirtyTArea = $('#dirtyURLs');
var urls = dirtyTArea.val().split('\n');
const cleanTArea = $('#cleanURLs');
const dirtyTArea = $('#dirtyURLs');
const urls = dirtyTArea.val().split('\n');
cleanedURLs = [];
length = urls.length;

View File

@@ -25,8 +25,8 @@
"use strict";
function injectFunction() {
var ele = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
let ele = document.createElement('script');
let s = document.getElementsByTagName('script')[0];
ele.type = 'text/javascript';
ele.textContent = "Object.defineProperty(window, 'rwt', {" +
@@ -46,13 +46,13 @@
injectFunction();
document.addEventListener('mouseover', function (event) {
var a = event.target, depth = 1;
let a = event.target, depth = 1;
while (a && a.tagName != 'A' && depth-- > 0) {
while (a && a.tagName !== 'A' && depth-- > 0) {
a = a.parentNode;
}
if (a && a.tagName == 'A') {
if (a && a.tagName === 'A') {
try {
a.removeAttribute('data-cthref');
delete a.dataset.cthref;

View File

@@ -37,14 +37,16 @@ function historyListenerStart() {
* which is associated with the new history entry created by replaceState()
*/
function historyCleaner(details) {
var urlBefore = details.url;
var urlAfter = pureCleaning(details.url);
if(storage.globalStatus) {
const urlBefore = details.url;
const urlAfter = pureCleaning(details.url);
if(urlBefore != urlAfter) {
browser.tabs.executeScript(details.tabId, {
frameId: details.frameId,
code: 'history.replaceState({state: "cleaned_history"},"",'+JSON.stringify(urlAfter)+');'
}).then(() => {}, onError);
if(urlBefore !== urlAfter) {
browser.tabs.executeScript(details.tabId, {
frameId: details.frameId,
code: 'history.replaceState({state: "cleaned_history"},"",'+JSON.stringify(urlAfter)+');'
}).then(() => {}, onError);
}
}
}

View File

@@ -26,7 +26,7 @@ var log = {};
* Reset the global log
*/
function resetGlobalLog(){
obj = {"log": []};
let obj = {"log": []};
browser.runtime.sendMessage({
function: "setData",
@@ -53,11 +53,11 @@ function getLog()
return b.timestamp - a.timestamp;
});
var length = Object.keys(log.log).length;
var row;
if(length != 0)
const length = Object.keys(log.log).length;
let row;
if(length !== 0)
{
for(var i=0; i<length;i++)
for(let i=0; i<length;i++)
{
row = "<tr>" +
"<td>"+log.log[i].before+"</td>" +
@@ -81,11 +81,9 @@ function getLog()
*/
function getDataTableTranslation()
{
var lang = browser.i18n.getUILanguage();
let lang = browser.i18n.getUILanguage();
lang = lang.substring(0,2);
var file = browser.extension.getURL('./external_js/dataTables/i18n/'+lang+'.lang');
return file;
return browser.extension.getURL('./external_js/dataTables/i18n/' + lang + '.lang');
}
/**
@@ -96,6 +94,42 @@ function toDate(time)
return new Date(time).toLocaleString();
}
/**
* This function export the global log as json file.
*/
function exportGlobalLog() {
browser.runtime.sendMessage({
function: "getData",
params: ['log']
}).then((data) => {
let blob = new Blob([JSON.stringify(data.response)], {type: 'application/json'});
browser.downloads.download({
'url': URL.createObjectURL(blob),
'filename': 'ClearURLsLogExport.json',
'saveAs': true
});
});
}
/**
* This function imports an exported global log and overwrites the old one.
*/
function importGlobalLog(evt) {
let file = evt.target.files[0];
let fileReader = new FileReader();
fileReader.onload = function(e) {
browser.runtime.sendMessage({
function: "setData",
params: ["log", e.target.result]
}).then(() => {
location.reload();
}, handleError);
};
fileReader.readAsText(file);
}
/**
* Load only when document is ready
*/
@@ -103,6 +137,8 @@ $(document).ready(function(){
setText();
getLog();
$('#reset_log_btn').on("click", resetGlobalLog);
$('#export_log_btn').on("click", exportGlobalLog);
$('#importLog').on("change", importGlobalLog);
});
/**
@@ -122,12 +158,16 @@ function setText()
{
document.title = translate('log_html_page_title');
$('#page_title').text(translate('log_html_page_title'));
$('#reset_log_btn').text(translate('log_html_reset_button'));
$('#log_html_reset_button').prop('title', translate('log_html_reset_button_title'));
$('#reset_log_btn').text(translate('log_html_reset_button'))
.prop('title', translate('log_html_reset_button_title'));
$('#head_1').text(translate('log_html_table_head_1'));
$('#head_2').text(translate('log_html_table_head_2'));
$('#head_3').text(translate('log_html_table_head_3'));
$('#head_4').text(translate('log_html_table_head_4'));
$('#export_log_btn_text').text(translate('log_html_export_button'));
$('#export_log_btn').prop('title', translate('log_html_export_button_title'));
$('#import_log_btn_text').text(translate('log_html_import_button'));
$('#import_log_btn').prop('title', translate('log_html_import_button_title'));
}
function handleError(error) {

View File

@@ -29,11 +29,11 @@
*/
function handleMessage(request, sender, sendResponse)
{
var fn = window[request.function];
let fn = window[request.function];
if(typeof fn === "function")
{
var response = fn.apply(null, request.params);
let response = fn.apply(null, request.params);
return Promise.resolve({response});
}

View File

@@ -107,7 +107,6 @@ function init()
/**
* Get the globalCounter and globalurlcounter value from the storage
* @param {(data){} Return value form storage
*/
function changeStatistics()
{
@@ -127,7 +126,7 @@ function changeStatistics()
*/
function setHashStatus()
{
var element = $('#hashStatus');
let element = $('#hashStatus');
if(hashStatus)
{
@@ -146,7 +145,7 @@ function setHashStatus()
*/
function changeSwitchButton(id, storageID)
{
var element = $('#'+id);
let element = $('#'+id);
changeVisibility(id, storageID);
@@ -155,7 +154,7 @@ function changeSwitchButton(id, storageID)
function: "setData",
params: [storageID, element.is(':checked')]
}).then((data) => {
if(storageID == "globalStatus"){
if(storageID === "globalStatus"){
browser.runtime.sendMessage({
function: "changeIcon",
params: []
@@ -176,7 +175,7 @@ function changeSwitchButton(id, storageID)
*/
function changeVisibility(id, storageID)
{
var element;
let element;
switch(storageID)
{
@@ -190,7 +189,7 @@ function changeVisibility(id, storageID)
element = "undefine";
}
if(element != "undefine")
if(element !== "undefine")
{
if($('#'+id).is(':checked'))
{
@@ -211,7 +210,7 @@ function changeVisibility(id, storageID)
*/
function setSwitchButton(id, varname)
{
var element = $('#'+id);
let element = $('#'+id);
element.prop('checked', this[varname]);
}
@@ -281,11 +280,11 @@ function setText()
*
* @param {string} id ID of the HTML element
* @param {string} attribute Name of the attribute used for localization
* @param {boolean} tooltip
* @param {string} tooltip
*/
function injectText(id, attribute, tooltip)
function injectText(id, attribute, tooltip = "")
{
object = $('#'+id);
let object = $('#'+id);
object.text(translate(attribute));
/*
@@ -294,7 +293,7 @@ function injectText(id, attribute, tooltip)
*/
tooltip = translate(attribute+"_title");
if(tooltip != "")
if(tooltip !== "")
{
object.prop('title', tooltip);
}

View File

@@ -19,20 +19,23 @@
/*jshint esversion: 6 */
/**
* Cleans given links. Also do automatic redirection.
*
* @param {[type]} url url as string
* @return {Array} redirectUrl or none
*/
function pureCleaning(url) {
var cleanURL = url;
var URLbeforeReplaceCount = countFields(url);
* Cleans given links. Also do automatic redirection.
*
* @param {String} url url as string
* @param {boolean} quiet if the action should be displayed in log and statistics
* @return {String} redirectUrl or none
*/
function pureCleaning(url, quiet = false) {
let cleanURL = url;
const URLbeforeReplaceCount = countFields(url);
//Add Fields form Request to global url counter
increaseGlobalURLCounter(URLbeforeReplaceCount);
if(!quiet) {
//Add Fields form Request to global url counter
increaseGlobalURLCounter(URLbeforeReplaceCount);
}
for (var i = 0; i < providers.length; i++) {
var result = {
for (let i = 0; i < providers.length; i++) {
let result = {
"changes": false,
"url": "",
"redirect": false,
@@ -41,7 +44,7 @@ function pureCleaning(url) {
if(providers[i].matchURL(cleanURL))
{
result = removeFieldsFormURL(providers[i], cleanURL);
result = removeFieldsFormURL(providers[i], cleanURL, quiet);
cleanURL = result.url;
}

View File

@@ -27,6 +27,8 @@ $(document).ready(function(){
setText();
$(".pick-a-color").pickAColor();
$('#reset_settings_btn').on("click", reset);
$('#export_settings_btn').on("click", exportSettings);
$('#importSettings').on("change", importSettings);
$('#save_settings_btn').on("click", save);
$("#badged_color input").on("change", function () {
@@ -101,6 +103,11 @@ function save()
params: ["types", $('input[name=types]').val()]
}).then(handleResponse, handleError);
browser.runtime.sendMessage({
function: "setData",
params: ["logLimit", $('input[name=logLimit]').val()]
}).then(handleResponse, handleError);
browser.runtime.sendMessage({
function: "saveOnExit",
params: []
@@ -113,13 +120,14 @@ function save()
}
/**
* Translate a string with the i18n API.
*
* @param {string} string Name of the attribute used for localization
* Translate a string with the i18n API.
*
* @param {string} string Name of the attribute used for localization
* @param {string[]} placeholders Array of placeholders
*/
function translate(string)
function translate(string, ...placeholders)
{
return browser.i18n.getMessage(string);
return browser.i18n.getMessage(string, placeholders);
}
/**
@@ -147,6 +155,18 @@ function getData()
params: ["types"]
}).then((data) => handleResponseData(data, "types", "types"), handleError);
browser.runtime.sendMessage({
function: "getData",
params: ["logLimit"]
}).then((data) => {
handleResponseData(data, "logLimit", "logLimit");
if(data.response === undefined || data.response === -1) {
$('#logLimit_label').text(translate('setting_log_limit_label', "∞"));
} else {
$('#logLimit_label').text(translate('setting_log_limit_label', data.response));
}
}, handleError);
browser.runtime.sendMessage({
function: "getData",
params: ["contextMenuEnabled"]
@@ -162,9 +182,16 @@ function getData()
params: ["localHostsSkipping"]
}).then((data) => {
handleResponseData(data, "localHostsSkipping", "localHostsSkipping");
changeSwitchButton("contextMenuEnabled", "contextMenuEnabled");
changeSwitchButton("historyListenerEnabled", "historyListenerEnabled");
changeSwitchButton("localHostsSkipping", "localHostsSkipping");
browser.runtime.sendMessage({
function: "getData",
params: ["referralMarketing"]
}).then((data) => {
handleResponseData(data, "referralMarketing", "referralMarketing");
changeSwitchButton("contextMenuEnabled", "contextMenuEnabled");
changeSwitchButton("historyListenerEnabled", "historyListenerEnabled");
changeSwitchButton("localHostsSkipping", "localHostsSkipping");
changeSwitchButton("referralMarketing", "referralMarketing");
}, handleError);
}, handleError);
}, handleError);
}, handleError);
@@ -178,22 +205,74 @@ function setText()
document.title = translate('settings_html_page_title');
$('#page_title').text(translate('settings_html_page_title'));
$('#badged_color_label').text(translate('badged_color_label'));
$('#reset_settings_btn').text(translate('setting_html_reset_button'));
$('#reset_settings_btn').prop('title', translate('setting_html_reset_button_title'));
$('#reset_settings_btn').text(translate('setting_html_reset_button'))
.prop('title', translate('setting_html_reset_button_title'));
$('#rule_url_label').text(translate('setting_rule_url_label'));
$('#hash_url_label').text(translate('setting_hash_url_label'));
$('#types_label').html(translate('setting_types_label'));
$('#save_settings_btn').text(translate('settings_html_save_button'));
$('#save_settings_btn').prop('title', translate('settings_html_save_button_title'));
$('#save_settings_btn').text(translate('settings_html_save_button'))
.prop('title', translate('settings_html_save_button_title'));
injectText("context_menu_enabled", "context_menu_enabled");
$('#history_listener_enabled').html(translate('history_listener_enabled'));
injectText("local_hosts_skipping", "local_hosts_skipping");
$('#export_settings_btn_text').text(translate('setting_html_export_button'));
$('#export_settings_btn').prop('title', translate('setting_html_export_button_title'));
$('#import_settings_btn_text').text(translate('setting_html_import_button'));
$('#import_settings_btn').prop('title', translate('setting_html_import_button_title'));
injectText("referral_marketing_enabled", "referral_marketing_enabled");
}
/**
* Handle the response from the storage and saves the data.
* @param {JSON-Object} data Data JSON-Object
*/
* This function exports all ClearURLs settings with statistics and rules.
*/
function exportSettings() {
browser.runtime.sendMessage({
function: "storageAsJSON",
params: []
}).then((data) => {
let blob = new Blob([JSON.stringify(data.response)], {type: 'application/json'});
browser.downloads.download({
'url': URL.createObjectURL(blob),
'filename': 'ClearURLs.conf',
'saveAs': true
});
});
}
/**
* This function imports an exported ClearURLs setting and overwrites the old one.
*/
function importSettings(evt) {
let file = evt.target.files[0];
let fileReader = new FileReader();
fileReader.onload = function(e) {
let data = JSON.parse(e.target.result);
const length = Object.keys(data).length;
let i=0;
Object.entries(data).forEach(([key, value]) => {
browser.runtime.sendMessage({
function: "setData",
params: [key, value]
}).then(() => {
i++;
if(i === length) {
location.reload();
}
}, handleError);
});
};
fileReader.readAsText(file);
}
/**
* Handle the response from the storage and saves the data.
* @param {JSON-Object} data Data JSON-Object
* @param varName
* @param inputID
*/
function handleResponseData(data, varName, inputID)
{
settings[varName] = data.response;
@@ -215,14 +294,14 @@ function handleError(error) {
*/
function changeSwitchButton(id, storageID)
{
var element = $('#'+id);
let element = $('#'+id);
element.on('change', function(){
browser.runtime.sendMessage({
function: "setData",
params: [storageID, element.is(':checked')]
}).then((data) => {
if(storageID == "globalStatus"){
if(storageID === "globalStatus"){
browser.runtime.sendMessage({
function: "changeIcon",
params: []
@@ -245,9 +324,9 @@ function changeSwitchButton(id, storageID)
* @param {string} attribute Name of the attribute used for localization
* @param {boolean} tooltip
*/
function injectText(id, attribute, tooltip)
function injectText(id, attribute, tooltip = "")
{
object = $('#'+id);
let object = $('#'+id);
object.text(translate(attribute));
/*
@@ -256,7 +335,7 @@ function injectText(id, attribute, tooltip)
*/
tooltip = translate(attribute+"_title");
if(tooltip != "")
if(tooltip !== "")
{
object.prop('title', tooltip);
}
@@ -269,6 +348,6 @@ function injectText(id, attribute, tooltip)
*/
function setSwitchButton(id, varname)
{
var element = $('#'+id);
let element = $('#'+id);
element.prop('checked', settings[varname]);
}

View File

@@ -0,0 +1,51 @@
/*
* ClearURLs
* Copyright (c) 2017-2019 Kevin Röbert
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*jshint esversion: 6 */
/*
* This script is responsible for the blocked alert page.
*/
/**
* Set the text for the UI.
*/
function setText()
{
document.title = translate('blocked_html_title');
$('#title').html(translate('blocked_html_title'));
$('#body').html(translate('blocked_html_body'));
$('#page').text(translate('blocked_html_button'));
}
$(document).ready(function(){
setText();
let source = new URLSearchParams(window.location.search).get("source");
$('#page').attr('href', decodeURIComponent(source));
});
/**
* Translate a string with the i18n API.
*
* @param {string} string Name of the attribute used for localization
*/
function translate(string)
{
return browser.i18n.getMessage(string);
}

View File

@@ -29,23 +29,39 @@ var pendingSaves = new Set();
*/
function saveOnExit()
{
var json = {};
saveOnDisk(Object.keys(storage));
}
/**
* Returns the storage as JSON.
*/
function storageAsJSON() {
let json = {};
Object.entries(storage).forEach(([key, value]) => {
switch (key) {
case "ClearURLsData":
case "log":
json[key] = JSON.stringify(value);
break;
case "types":
json[key] = value.toString();
break;
default:
json[key] = value;
}
json[key] = storageDataAsString(key);
});
console.log(translate('core_save_on_disk'));
browser.storage.local.set(json);
return json;
}
/**
* Converts a given storage data to its string representation.
* @param key key of the storage data
* @returns {string} string representation
*/
function storageDataAsString(key) {
let value = storage[key];
switch (key) {
case "ClearURLsData":
case "log":
return JSON.stringify(value);
case "types":
return value.toString();
default:
return value;
}
}
/**
@@ -54,22 +70,12 @@ function saveOnExit()
*/
function saveOnDisk(keys)
{
var json = {};
let json = {};
keys.forEach(function(key) {
var value = storage[key];
switch (key) {
case "ClearURLsData":
case "log":
json[key] = JSON.stringify(value);
break;
case "types":
json[key] = value.toString();
break;
default:
json[key] = value;
}
json[key] = storageDataAsString(key);
});
console.log(translate('core_save_on_disk'));
browser.storage.local.set(json);
}
@@ -145,17 +151,20 @@ function setData(key, value)
switch (key) {
case "ClearURLsData":
case "log":
storage[key] = JSON.parse(value);
break;
storage[key] = JSON.parse(value);
break;
case "hashURL":
case "ruleURL":
storage[key] = replaceOldURLs(value);
break;
storage[key] = replaceOldURLs(value);
break;
case "types":
storage[key] = value.split(',');
break;
storage[key] = value.split(',');
break;
case "logLimit":
storage[key] = Number(value);
break;
default:
storage[key] = value;
storage[key] = value;
}
}
@@ -204,6 +213,8 @@ function initSettings()
storage.contextMenuEnabled = true;
storage.historyListenerEnabled = true;
storage.localHostsSkipping = true;
storage.referralMarketing = false;
storage.logLimit = -1;
if(getBrowser() === "Firefox") {
storage.types = ["font", "image", "imageset", "main_frame", "media", "object", "object_subrequest", "other", "script", "stylesheet", "sub_frame", "websocket", "xbl", "xml_dtd", "xmlhttprequest", "xslt"];

View File

@@ -75,19 +75,13 @@ async function checkOSAndroid()
});
}
if(os == "android")
{
return true;
}
else{
return false;
}
return os === "android";
}
/**
* Extract the host without port from an url.
* @param {String} url URL as String
* @return {Array} host as string
* @return {String} host as string
*/
function extractHost(url) {
let parsed_url = new URL(url);
@@ -126,8 +120,8 @@ function countFields(url)
*/
function existsFields(url)
{
var matches = (url.match(/\?.+/i) || []);
var count = matches.length;
let matches = (url.match(/\?.+/i) || []);
let count = matches.length;
return (count > 0);
}
@@ -140,7 +134,7 @@ function existsFields(url)
function extractFileds(url)
{
if(existsFields(url)) {
var fields = url.replace(new RegExp(".*?\\?", "i"), "");
let fields = url.replace(new RegExp(".*?\\?", "i"), "");
if(existsFragments(url)) {
fields = fields.replace(new RegExp("#.*", "i"), "");
}
@@ -169,7 +163,7 @@ function countFragments(url)
function extractFragments(url)
{
if(existsFragments(url)) {
var fragments = url.replace(new RegExp(".*?#", "i"), "");
let fragments = url.replace(new RegExp(".*?#", "i"), "");
return (fragments.match(/[^&]+=?[^&]*/gi) || []);
}
@@ -183,8 +177,8 @@ function extractFragments(url)
*/
function existsFragments(url)
{
var matches = (url.match(/\#.+/i) || []);
var count = matches.length;
let matches = (url.match(/\#.+/i) || []);
let count = matches.length;
return (count > 0);
}
@@ -298,3 +292,16 @@ function getBrowser() {
return "Chrome";
}
}
/**
* Decodes an URL, also one that is encoded multiple times.
* @param url the url, that should be decoded
*/
function decodeURL(url) {
const rtn = decodeURIComponent(url);
if(rtn.indexOf("http://") === -1 && rtn.indexOf("https://") === -1) {
return decodeURL(rtn);
}
return rtn;
}

38
core_js/watchdog.js Normal file
View File

@@ -0,0 +1,38 @@
/*
* ClearURLs
* Copyright (c) 2017-2019 Kevin Röbert
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*jshint esversion: 6 */
/*
* This script is responsible to check in fixed intervals, that ClearURLs works properly.
* In issue #203, some users reported, that ClearURLs filter function doesn't work after
* some time, but without any recognizable reason.
*
* This watchdog restarts the whole Add-on, when the check fails.
*/
const CHECK_INTERVAL = 15000;
setInterval(function() {
const dirtyURL = "https://clearurls.roebert.eu?utm_source=addon";
const cleanURL = "https://clearurls.roebert.eu";
if(pureCleaning(dirtyURL, true) !== cleanURL) {
console.log(translate('watchdog'));
saveOnExit();
reload();
}
}, CHECK_INTERVAL);

View File

@@ -20,5 +20,5 @@
* This file writes only the version into every page.
* @return version
*/
var version = browser.runtime.getManifest().version;
const version = browser.runtime.getManifest().version;
$('#version').text(version);