/** * GNU LibreJSXUL - A browser add-on to block nonfree nontrivial JavaScript. * * * Copyright (C) 2011, 2012, 2014 Loic J. Duros * Copyright (C) 2014, 2015 Nik Nyby * * Modified 2020 by Jesús E. * * This file is part of GNU LibreJSXUL. * * GNU LibreJSXUL is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GNU LibreJSXUL 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU LibreJSXUL. If not, see . */ // array reflects valid types as listed in // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsScriptLoader.cpp#437 // anything appended to end of strings is considered valid: var jsValidTypes = [ /^text\/javascript/i, /^text\/ecmascript/i, /^application\/javascript/i, /^application\/ecmascript/i, /^application\/x-javascript/i ]; // 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"]; /** * scriptHasInvalidType * * Checks that a script does not have a js "template" type. * Normally any script that has a type attribute other than the * few allowed ones is not interpreted. But by security, we only * discard a few of them. * * @param script obj The script element. * @return returns true if it matches a template type. * */ var scriptHasInvalidType = function (type) { var i = 0, le = jsValidTypes.length; if (type === 'librejs/blocked') { // js has already been blocked. return true; } if (!type) { // type isn't set, don't look further. return false; } for (; i < le; i++) { if (jsValidTypes[i].test(type)) { return false; } } // type is invalid and // hence cannot be executed. return true; }; /** * scriptDetector * Detects all scripts (inline, onpage, and external) * and checks whether they have been blocked or if they * are being executed. */ var scriptDetector = { contactLink: null, blockedScripts: null, acceptedScripts: null, dryRunScripts: null, acceptedAttributes: null, acceptedCode: [], blockedCode: [], dryRunCode: [], init: function() { if (typeof $ !== 'function') { return; } this.blockedScripts = $('script[type="librejs/blocked"]'); this.acceptedScripts = $('script[type!="librejs/blocked"]') .not('script[data-librejs-dryrun]'); this.dryRunScripts = $('script[data-librejs-dryrun]'); console.debug(this.dryRunScripts); this.fetchAllNonScriptTags(); if (this.blockedScripts.length) { // display noscript tags if applicable. this.displayNoScriptTags(); try { // initialize the page mod code. pageModFinder.init(); } catch (e) { // fail silently. } this.fetchBlockedScripts(); } if (this.acceptedScripts.length) { this.fetchAcceptedScripts(); } if (this.dryRunScripts.length) { this.fetchDryRunScripts(); } self.postMessage({ 'event': 'scriptsFetched', 'value': { 'blocked': this.blockedCode, 'accepted': this.acceptedCode, 'dryRun': this.dryRunCode } }); }, /** * fetchBlockedScripts * * Gather blocked scripts. * */ fetchBlockedScripts: function () { var that = this, singleton = '', reason; this.blockedScripts.each(function() { singleton = ''; reason = ""; if ($(this).data('librejs-reason') && $(this).data('librejs-reason').length > 9) { reason = $(this).data('librejs-reason') + ': '; } if ($(this).text()) { if ($(this).data('singleton') === true) { singleton = 'This script was removed before LibreJSXUL analysis: '; } that.blockedCode.push({'contents': singleton + reason + that.truncateText($(this).text()), 'inline': true}); } if ($(this).data('librejs-blocked-src')) { that.blockedCode.push({'url': $(this).data('librejs-blocked-src'), 'contents': reason, 'inline': false}); } }); }, /** * fetchAcceptedScripts * * Gather accepted scripts. * */ fetchAcceptedScripts: function () { var that = this, typeMessage = '', reason = ""; this.acceptedScripts.each(function() { reason = ""; if ($(this).data('librejs-reason') && $(this).data('librejs-reason').length > 9) { reason = $(this).data('librejs-reason') + ':'; } if ($(this).attr('type') && scriptHasInvalidType($(this).attr('type'))) { typeMessage = 'script type is not valid (js is not executed): '+ $(this).attr('type') + ' '; } if ($(this).text()) { that.acceptedCode.push({'contents': reason + typeMessage + that.truncateText($(this).text()), 'inline': true}); } if ($(this).attr('src')) { that.acceptedCode.push({'url': $(this).attr('src'), 'contents': reason + typeMessage, 'inline': false}); } }); }, /** * fetchDryRunScripts * * Gather accepted scripts. * */ fetchDryRunScripts: function () { var that = this, typeMessage = '', reason = ""; this.dryRunScripts.each(function() { reason = ""; if ($(this).data('librejs-reason') && $(this).data('librejs-reason').length > 9) { reason = $(this).data('librejs-reason') + ':'; } if ($(this).attr('type') && scriptHasInvalidType($(this).attr('type'))) { typeMessage = 'script type is not valid (js is not executed): '+ $(this).attr('type') + ' '; } if ($(this).text()) { that.dryRunCode.push({'contents': reason + typeMessage + that.truncateText($(this).text()), 'inline': true}); } if ($(this).attr('src')) { that.dryRunCode.push({'url': $(this).attr('src'), 'contents': reason + typeMessage, 'inline': false}); } }); }, fetchAllNonScriptTags: function () { var that = this; var blockedAnchors = $('*[data-librejs="rejected"]').not('script'); var acceptedAnchors = $('*[data-librejs="accepted"]').not('script'); var i = 0, le, attributes; acceptedAnchors.each(function () { var content = ""; if ($(this).attr('href')) { content = $(this).attr('href'); } else { content = that.findOnAttributeContent($(this)); } that.acceptedCode.push( {contents: 'in attribute: ' + content, inline: true} ); }); blockedAnchors.each(function () { var content = ""; if ($(this).attr('href')) { content = $(this).attr('href'); } else if ($(this).data('librejs-blocked-event')) { attributes = $(this).data('librejs-blocked-event'); le = attributes.length; for (i = 0; i < le; i++) { content += attributes[i].attribute + ":" + attributes[i].value + ";\n"; } } that.blockedCode.push( {contents: 'in attribute: ' + content, inline: true} ); }); }, findOnAttributeContent: function (elem) { var i = 0, le = intrinsicEvents.length, content = ""; for (; i < le; i++) { if (elem.attr(intrinsicEvents[i])) { content += elem.attr(intrinsicEvents[i]) + " -- "; } } return content; }, /** * displayNoScriptTags * Whenever blocked scripts are found, deep clone noscript tags * and place them in a new div. */ displayNoScriptTags: function () { var noscripts = $('body noscript'), div, content; noscripts.each(function (index) { div = $('
'); content = $(this).contents(); content = $("
").html(content).text(); div.append(content); div.children('style, script, meta').remove(); // insert noscript content right after the // original noscript tag. div.insertAfter($(this)); }); }, truncateText: function (str) { if (str.length > 1000) { str = str.slice(0, 1000) + '…'; } return str; } }; scriptDetector.init();