/**
 * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
 * *
 * Copyright (C) 2011, 2012 Loic J. Duros
 *
 * This program 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.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see  <http://www.gnu.org/licenses/>.
 *
 */

// array reflex 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 intrinsecEvents = [
    "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,
    acceptedAttributes: null,
    acceptedCode: [],
    blockedCode: [],

    init: function () {	

	this.blockedScripts = $('script[type="librejs/blocked"]');
	
	this.acceptedScripts = $('script[type!="librejs/blocked"]');

	this.fetchAllNonScriptTags();

	if (this.blockedScripts.length) {

	    // display noscript tags if applicable.
	    this.displayNoScriptTags();

	    // initialize the page mod code.
	    pageModFinder.init();
	    this.fetchBlockedScripts();

	}



	if (this.acceptedScripts.length) {

	    this.fetchAcceptedScripts();

	}

	self.postMessage({event: 'scriptsFetched',
			  value: {'blocked': this.blockedCode,
				  'accepted': this.acceptedCode}});
    },

    /**
     * fetchBlockedScripts
     *
     * Gather blocked scripts.
     *
     */
    fetchBlockedScripts: function () {
	var that = this,
	singleton = '', reason;

	this.blockedScripts.each(function() {
	    singleton = '', reason = "";

	    /*if ($(this).data('librejs-reason') != '') {
		reason = $(this).data('librejs-reason') + ': ';		
	    }*/

	    if ($(this).text()) {

		if ($(this).data('singleton') === true) {
		    singleton = 'This script was removed before LibreJS 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') != "") {
		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});
	    }

	});
	
    },

    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 = intrinsecEvents.length,
	    content = "";
	
	for (; i < le; i++) {

	    if (elem.attr(intrinsecEvents[i])) {

		content += elem.attr(intrinsecEvents[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 = $('<div/>');


	    content = $(this).contents();
	    content = $("<div>").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;
    }
    
};

self.on('message', function (respData) {

    if (respData.event === 'tabProcess') {

	//console.log('this is a tab');
	scriptDetector.init();

    }

});


