The command pattern has been implemented so that ClearURLs also works in private mode. See also: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage
		
			
				
	
	
		
			633 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			633 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*jshint esversion: 6 */
 | |
| /*
 | |
| * This script is responsible for the core functionalities.
 | |
| */
 | |
| var providers = [];
 | |
| var prvKeys = [];
 | |
| var badges = [];
 | |
| var tabid = 0;
 | |
| var siteBlockedAlert = 'javascript:void(0)';
 | |
| var dataHash;
 | |
| var localDataHash;
 | |
| var os;
 | |
| var currentURL;
 | |
| 
 | |
| function start()
 | |
| {
 | |
|     changeIcon();
 | |
| 
 | |
|     /**
 | |
|     * Save OS Version
 | |
|     */
 | |
|     browser.runtime.getPlatformInfo(function(info) {
 | |
| 
 | |
|         os = info.os;
 | |
| 
 | |
| 
 | |
|         /**
 | |
|         * Initialize the JSON provider object keys.
 | |
|         *
 | |
|         * @param {JSON Object} obj
 | |
|         */
 | |
|         function getKeys(obj){
 | |
|             for(var key in obj){
 | |
|                 prvKeys.push(key);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Initialize the providers form the JSON object.
 | |
|         *
 | |
|         */
 | |
|         function createProviders()
 | |
|         {
 | |
|             data = storage.ClearURLsData;
 | |
| 
 | |
|             for(var p = 0; p < prvKeys.length; p++)
 | |
|             {
 | |
|                 //Create new provider
 | |
|                 providers.push(new Provider(prvKeys[p],data.providers[prvKeys[p]].completeProvider));
 | |
| 
 | |
|                 //Add URL Pattern
 | |
|                 providers[p].setURLPattern(data.providers[prvKeys[p]].urlPattern);
 | |
| 
 | |
|                 //Add rules to provider
 | |
|                 for(var r = 0; r < data.providers[prvKeys[p]].rules.length; r++)
 | |
|                 {
 | |
|                     providers[p].addRule(data.providers[prvKeys[p]].rules[r]);
 | |
|                 }
 | |
| 
 | |
|                 //Add exceptions to provider
 | |
|                 for(var e = 0; e < data.providers[prvKeys[p]].exceptions.length; e++)
 | |
|                 {
 | |
|                     providers[p].addException(data.providers[prvKeys[p]].exceptions[e]);
 | |
|                 }
 | |
| 
 | |
|                 //Add redirections to provider
 | |
|                 for(var re = 0; re < data.providers[prvKeys[p]].redirections.length; re++)
 | |
|                 {
 | |
|                     providers[p].addRedirection(data.providers[prvKeys[p]].redirections[re]);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Convert the external data to Objects and
 | |
|         * call the create provider function.
 | |
|         *
 | |
|         * @param  {String} retrievedText - pure data form github
 | |
|         */
 | |
|         function toObject(retrievedText) {
 | |
|             getKeys(storage.ClearURLsData.providers);
 | |
|             createProviders();
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Get the hash for the rule file on github.
 | |
|         * Check the hash with the hash form the local file.
 | |
|         * If the hash has changed, then download the new rule file.
 | |
|         * Else do nothing.
 | |
|         */
 | |
|         function getHash()
 | |
|         {
 | |
|             //Get the target hash from github
 | |
|             fetch(storage.hashURL)
 | |
|             .then(function(response){
 | |
|                 var responseTextHash = response.clone().text().then(function(responseTextHash){
 | |
|                     if(response.ok && $.trim(responseTextHash))
 | |
|                     {
 | |
|                         dataHash = responseTextHash;
 | |
| 
 | |
|                         if($.trim(dataHash) !== $.trim(localDataHash))
 | |
|                         {
 | |
|                             fetchFromURL();
 | |
|                         }
 | |
|                         else {
 | |
|                             toObject(storage.ClearURLsData);
 | |
|                             storeHashStatus(1);
 | |
|                         }
 | |
|                     }
 | |
|                     else {
 | |
|                         dataHash = false;
 | |
|                     }
 | |
|                 });
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         /*
 | |
|         * ##################################################################
 | |
|         * # Fetch Rules & Exception from URL                               #
 | |
|         * ##################################################################
 | |
|         */
 | |
|         function fetchFromURL()
 | |
|         {
 | |
|             fetch(storage.ruleURL)
 | |
|             .then(checkResponse);
 | |
| 
 | |
|             function checkResponse(response)
 | |
|             {
 | |
|                 var responseText = response.clone().text().then(function(responseText){
 | |
|                     if(response.ok && $.trim(responseText))
 | |
|                     {
 | |
|                         var downloadedFileHash = $.sha256(responseText);
 | |
| 
 | |
|                         if($.trim(downloadedFileHash) === $.trim(dataHash))
 | |
|                         {
 | |
|                             storage.ClearURLsData = responseText;
 | |
|                             storage.dataHash = downloadedFileHash;
 | |
|                             storeHashStatus(2);
 | |
|                         }
 | |
|                         else {
 | |
|                             storeHashStatus(3);
 | |
|                         }
 | |
|                         storage.ClearURLsData = JSON.parse(storage.ClearURLsData);
 | |
|                         toObject(storage.ClearURLsData);
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // ##################################################################
 | |
| 
 | |
|         /*
 | |
|         * ##################################################################
 | |
|         * # Supertyp Provider                                              #
 | |
|         * ##################################################################
 | |
|         */
 | |
|         /**
 | |
|         * Declare constructor
 | |
|         *
 | |
|         * @param {String} _name                Provider name
 | |
|         * @param {boolean} _completeProvider    Set URL Pattern as rule
 | |
|         * @param {boolean} _isActive            Is the provider active?
 | |
|         */
 | |
|         function Provider(_name, _completeProvider = false, _isActive = true){
 | |
|             var name = _name;
 | |
|             var urlPattern;
 | |
|             var rules = [];
 | |
|             var exceptions = [];
 | |
|             var canceling = _completeProvider;
 | |
|             var redirections = [];
 | |
|             var active = _isActive;
 | |
| 
 | |
|             if(_completeProvider){
 | |
|                 rules.push(".*");
 | |
|             }
 | |
| 
 | |
|             /**
 | |
|             * Returns the provider name.
 | |
|             * @return {String}
 | |
|             */
 | |
|             this.getName = function() {
 | |
|                 return name;
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Add URL pattern.
 | |
|             *
 | |
|             * @require urlPatterns as RegExp
 | |
|             */
 | |
|             this.setURLPattern = function(urlPatterns) {
 | |
|                 urlPattern = new RegExp(urlPatterns, "i");
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Return if the Provider Request is canceled
 | |
|             * @return {Boolean} isCanceled
 | |
|             */
 | |
|             this.isCaneling = function() {
 | |
|                 return canceling;
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Check the url is matching the ProviderURL.
 | |
|             *
 | |
|             * @return {boolean}    ProviderURL as RegExp
 | |
|             */
 | |
|             this.matchURL = function(url) {
 | |
|                 return !(this.matchException(url)) && urlPattern.test(url);
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Add a rule to the rule array
 | |
|             * and replace old rule with new rule.
 | |
|             *
 | |
|             * @param {String} rule        RegExp as string
 | |
|             * @param {boolean} isActive   Is this rule active?
 | |
|             */
 | |
|             this.addRule = function(rule, isActive = true) {
 | |
|                 for(var i=0; i < rules.length; i++)
 | |
|                 {
 | |
|                     if(rules[i][0] === rule)
 | |
|                     {
 | |
|                         rules[i] = [rule, isActive];
 | |
| 
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 rules.push([rule, isActive]);
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Return all active rules as an array.
 | |
|             *
 | |
|             * @return Array RegExp strings
 | |
|             */
 | |
|             this.getRules = function() {
 | |
|                 var rawRules = [];
 | |
| 
 | |
|                 rules.forEach(rule => {
 | |
|                     if(rule[1] === true)
 | |
|                     {
 | |
|                         rawRules.push(rule[0]);
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|                 return rawRules;
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Add a exception to the exceptions array
 | |
|             * and replace old with new exception.
 | |
|             *
 | |
|             * @param {String} exception   RegExp as string
 | |
|             * @param {Boolean} isActive   Is this exception acitve?
 | |
|             */
 | |
|             this.addException = function(exception, isActive = true) {
 | |
|                 for(var i=0; i < exceptions.length; i++)
 | |
|                 {
 | |
|                     if(exceptions[i][0] === exception)
 | |
|                     {
 | |
|                         exceptions[i] = [exception, isActive];
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 exceptions.push([exception, isActive]);
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Private helper method to check if the url
 | |
|             * an exception.
 | |
|             *
 | |
|             * @param  {String} url     RegExp as string
 | |
|             * @return {boolean}        if matching? true: false
 | |
|             */
 | |
|             this.matchException = function(url) {
 | |
|                 var result = false;
 | |
| 
 | |
|                 //Add the site blocked alert to every exception
 | |
|                 if(url == siteBlockedAlert) return true;
 | |
| 
 | |
|                 for (var i = 0; i < exceptions.length; i++) {
 | |
|                     if(result) { break; }
 | |
| 
 | |
|                     if(exceptions[i][1])
 | |
|                     {
 | |
|                         exception_regex = new RegExp(exceptions[i][0], "i");
 | |
|                         result = exception_regex.test(url);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return result;
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Add a redirection to the redirections array
 | |
|             * and replace old with new redirection.
 | |
|             *
 | |
|             * @param {String} redirection   RegExp as string
 | |
|             * @param {Boolean} isActive     Is this redirection active?
 | |
|             */
 | |
|             this.addRedirection = function(redirection, isActive = true) {
 | |
|                 for(var i=0; i < redirections.length; i++)
 | |
|                 {
 | |
|                     if(redirections[i][0] === redirection) {
 | |
|                         redirections[i] = [redirection, isActive];
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 redirections.push([redirection, isActive]);
 | |
|             };
 | |
| 
 | |
|             /**
 | |
|             * Return all redirection.
 | |
|             *
 | |
|             * @return url
 | |
|             */
 | |
|             this.getRedirection = function(url) {
 | |
|                 var re = null;
 | |
| 
 | |
|                 for(var i = 0; i < redirections.length; i++)
 | |
|                 {
 | |
|                     result = (url.match(new RegExp(redirections[i][0], "i")));
 | |
| 
 | |
|                     if (result && result.length > 0 && redirections[i][1])
 | |
|                     {
 | |
|                         re = (new RegExp(redirections[i][0], "i")).exec(url)[1];
 | |
| 
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return re;
 | |
|             };
 | |
|         }
 | |
|         // ##################################################################
 | |
| 
 | |
|         /**
 | |
|         * Helper function which remove the tracking fields
 | |
|         * for each provider given as parameter.
 | |
|         *
 | |
|         * @param  {Provider} provider      Provider-Object
 | |
|         * @param  {webRequest} request     webRequest-Object
 | |
|         * @return {Array}                  Array with changes and url fields
 | |
|         */
 | |
|         function removeFieldsFormURL(provider, request)
 | |
|         {
 | |
|             var url = request.url;
 | |
|             var domain = url.replace(new RegExp("\\?.*", "i"), "");
 | |
|             var fields = "";
 | |
|             var rules = provider.getRules();
 | |
|             var changes = false;
 | |
|             var cancel = false;
 | |
| 
 | |
|             /*
 | |
|             * Expand the url by provider redirections. So no tracking on
 | |
|             * url redirections form sites to sites.
 | |
|             */
 | |
|             var re = provider.getRedirection(url);
 | |
|             if(re !== null)
 | |
|             {
 | |
|                 url = decodeURIComponent(re);
 | |
|                 //Log the action
 | |
|                 pushToLog(request.url, re, translate('log_redirect'));
 | |
| 
 | |
|                 return {
 | |
|                     "redirect": true,
 | |
|                     "url": url
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             /**
 | |
|             * Only test for matches, if there are fields that can be cleaned.
 | |
|             */
 | |
|             if(existsFields(url))
 | |
|             {
 | |
|                 /**
 | |
|                 * It must be non-greedy, because by default .* will match
 | |
|                 * all ? chars. So the replace function delete everything
 | |
|                 * before the last ?. With adding a ? on the quantifier *,
 | |
|                 * we fixed this problem.
 | |
|                 */
 | |
|                 fields = url.replace(new RegExp(".*?\\?", "i"), "");
 | |
| 
 | |
|                 for (var i = 0; i < rules.length; i++) {
 | |
|                     var beforReplace = fields;
 | |
| 
 | |
|                     fields = fields.replace(new RegExp(rules[i], "i"), "");
 | |
| 
 | |
|                     if(beforReplace != fields)
 | |
|                     {
 | |
|                         //Log the action
 | |
|                         pushToLog(domain+"?"+beforReplace, domain+"?"+fields, rules[i]);
 | |
| 
 | |
|                         if(badges[tabid] == null)
 | |
|                         {
 | |
|                             badges[tabid] = 0;
 | |
|                         }
 | |
| 
 | |
|                         increaseURLCounter();
 | |
| 
 | |
|                         if(!checkOSAndroid())
 | |
|                         {
 | |
|                             if(storage.badgedStatus) {
 | |
|                                 browser.browserAction.setBadgeText({text: (++badges[tabid]).toString(), tabId: tabid});
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 browser.browserAction.setBadgeText({text: "", tabId: tabid});
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         changes = true;
 | |
|                     }
 | |
|                 }
 | |
|                 url = domain+"?"+fields;
 | |
|             }
 | |
|             else {
 | |
|                 if(domain != url)
 | |
|                 {
 | |
|                     url = domain;
 | |
|                     changes = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if(provider.isCaneling()){
 | |
|                 pushToLog(request.url, request.url, translate('log_domain_blocked'));
 | |
|                 if(badges[tabid] == null)
 | |
|                 {
 | |
|                     badges[tabid] = 0;
 | |
|                 }
 | |
| 
 | |
|                 increaseURLCounter();
 | |
| 
 | |
|                 if(!checkOSAndroid())
 | |
|                 {
 | |
|                     if(storage.badgedStatus) {
 | |
|                         browser.browserAction.setBadgeText({text: (++badges[tabid]).toString(), tabId: tabid});
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         browser.browserAction.setBadgeText({text: "", tabId: tabid});
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 cancel = true;
 | |
|             }
 | |
| 
 | |
|             return {
 | |
|                 "changes": changes,
 | |
|                 "url": url,
 | |
|                 "cancel": cancel
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Function which called from the webRequest to
 | |
|         * remove the tracking fields from the url.
 | |
|         *
 | |
|         * @param  {webRequest} request     webRequest-Object
 | |
|         * @return {Array}                  redirectUrl or none
 | |
|         */
 | |
|         function clearUrl(request)
 | |
|         {
 | |
|             var URLbeforeReplaceCount = countFields(request.url);
 | |
| 
 | |
|             //Add Fields form Request to global url counter
 | |
|             increaseGlobalURLCounter(URLbeforeReplaceCount);
 | |
| 
 | |
|             if(storage.globalStatus){
 | |
| 
 | |
|                 var result = {
 | |
|                     "changes": false,
 | |
|                     "url": "",
 | |
|                     "redirect": false,
 | |
|                     "cancel": false
 | |
|                 };
 | |
| 
 | |
|                 /*
 | |
|                 * Call for every provider the removeFieldsFormURL method.
 | |
|                 */
 | |
|                 for (var i = 0; i < providers.length; i++) {
 | |
| 
 | |
|                     if(providers[i].matchURL(request.url))
 | |
|                     {
 | |
|                         result = removeFieldsFormURL(providers[i], request);
 | |
|                     }
 | |
| 
 | |
|                     /*
 | |
|                     * Expand urls and bypass tracking.
 | |
|                     * Cancel the active request.
 | |
|                     */
 | |
|                     if(result.redirect)
 | |
|                     {
 | |
|                         browser.tabs.update(request.tabId, {url: result.url});
 | |
|                         return {cancel: true};
 | |
|                     }
 | |
| 
 | |
|                     /*
 | |
|                     * Cancel the Request and redirect to the site blocked alert page,
 | |
|                     * to inform the user about the full url blocking.
 | |
|                     */
 | |
|                     if(result.cancel){
 | |
|                         return {
 | |
|                             redirectUrl: siteBlockedAlert
 | |
|                         };
 | |
|                     }
 | |
| 
 | |
|                     /*
 | |
|                     * Ensure that the function go not into
 | |
|                     * a loop.
 | |
|                     */
 | |
|                     if(result.changes){
 | |
|                         return {
 | |
|                             redirectUrl: result.url
 | |
|                         };
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Default case
 | |
|             return {};
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Function to log all activities from ClearUrls.
 | |
|         * Only logging when activated.
 | |
|         * The log is only temporary saved in the cache and will
 | |
|         * permanently saved with the saveLogOnClose function.
 | |
|         *
 | |
|         * @param beforeProcessing  the url before the clear process
 | |
|         * @param afterProcessing   the url after the clear process
 | |
|         * @param rule              the rule that triggered the process
 | |
|         */
 | |
|         function pushToLog(beforeProcessing, afterProcessing, rule)
 | |
|         {
 | |
|             if(storage.loggingStatus)
 | |
|             {
 | |
|                 storage.log.log.push(
 | |
|                     {
 | |
|                         "before": beforeProcessing,
 | |
|                         "after": afterProcessing,
 | |
|                         "rule": rule,
 | |
|                         "timestamp": Date.now()
 | |
|                     }
 | |
|                 );
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Call loadOldDataFromStore, getHash, counter, status and log functions
 | |
|         */
 | |
| 
 | |
|         loadOldDataFromStore();
 | |
|         getHash();
 | |
|         setBadgedStatus();
 | |
| 
 | |
|         /**
 | |
|         * Call by each tab is updated.
 | |
|         * And if url has changed.
 | |
|         */
 | |
|         function handleUpdated(tabId, changeInfo, tabInfo) {
 | |
|             if(changeInfo.url)
 | |
|             {
 | |
|                 delete badges[tabId];
 | |
|             }
 | |
|             currentURL = tabInfo.url;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Call by each tab is updated.
 | |
|         */
 | |
|         browser.tabs.onUpdated.addListener(handleUpdated);
 | |
| 
 | |
|         /**
 | |
|         * Call by each tab change to set the actual tab id
 | |
|         */
 | |
|         function handleActivated(activeInfo) {
 | |
|             tabid = activeInfo.tabId;
 | |
|             browser.tabs.get(tabid).then(function (tab) {
 | |
|                 currentURL = tab.url;
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Call by each tab change.
 | |
|         */
 | |
|         browser.tabs.onActivated.addListener(handleActivated);
 | |
| 
 | |
|         /**
 | |
|         * Check the request.
 | |
|         */
 | |
|         function promise(requestDetails)
 | |
|         {
 | |
|             if(isDataURL(requestDetails))
 | |
|             {
 | |
|                 return {};
 | |
|             }
 | |
|             else {
 | |
|                 var ret = clearUrl(requestDetails);
 | |
|                 return ret;
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * To prevent long loading on data urls
 | |
|         * we will check here for data urls.
 | |
|         *
 | |
|         * @type {requestDetails}
 | |
|         * @return {boolean}
 | |
|         */
 | |
|         function isDataURL(requestDetails) {
 | |
|             var s = requestDetails.url;
 | |
| 
 | |
|             return s.substring(0,4) == "data";
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|         * Call by each Request and checking the url.
 | |
|         *
 | |
|         * @type {Array}
 | |
|         */
 | |
|         browser.webRequest.onBeforeRequest.addListener(
 | |
|             promise,
 | |
|             {urls: ["<all_urls>"], types: getData("types")},
 | |
|             ["blocking"]
 | |
|         );
 | |
|     });
 | |
| }
 |