//
//  Copyright (c) 2012-2013 Stefan Bolte <portix@gmx.net>
//
//  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, write to the Free Software Foundation, Inc.,
//  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
/*
 * Block requests from thirdparty domains
 *
 * Extension that blocks requests from thirdparty domains with whitelisting
 * support, either permanently or just for the session.
 * It is also possible to block requests from certain domains on all sites, they
 * have highest precedence and will also be blocked on sites where 
 * thirdparty requests are allowed in general. 
 *
 *
 * To use this extension load it with a userscript in
 * $HOME/.config/dwb/userscripts/, e.g. 
 *
 * ------------------------------------------------------------------------------
 * |#!javascript                                                                | 
 * |                                                                            | 
 * |extensions.load("requestpolicy");                                           | 
 * ------------------------------------------------------------------------------
 *
 *
 * Configuration options:
 *
 * shortcut       : Shortcut to block / allow requests, default "erp"
 *
 * unblockCurrent : Shortcut to unblock always blocked requests, shows only
 *                  domains from the current site, default "erC"
 *
 * unblockAll     : Shortcut to unblock always blocked requests, shows all
 *                  blocked domains, default "erA"
 *
 * whiteList      : A path to the whitelisting file, default is
 *                  $XDG_CONFIG_HOME/dwb/<profile>/requestpolicy.json
 *
 * autoreload     : Whether to automatically reload the website after the
 *                  persistentList has changed, default false
 *
 * notify         : Whether to notify about blocked request, default false
 *
 *
 * Example (loading config with extensions.load()) 
 *
 * ------------------------------------------------------------------------------
 * |extensions.load("requestpolicy", {                                          |
 * |    whiteList  : system.getEnv("HOME") + "/.dwb_request_policy",            |
 * |    autoreload      : true                                                  |
 * |});                                                                         |
 * ------------------------------------------------------------------------------
 *
 * Example extensionrc:
 *
 * ------------------------------------------------------------------------------
 * |return {                                                                    |
 * |   foo : { ... },                                                           |
 * |                                                                            |
 * |   requestpolicy : {                                                        |
 * |      autoreload    : true                                                  |
 * |      shortcut : "rp",                                                      |
 * |      notify : false                                                        |
 * |   },                                                                       |
 * |   bar : { ... }                                                            |
 * |}                                                                           |
 * ------------------------------------------------------------------------------
 *
 * */

/*<INFO
Block requests from thirdparty domains permanently or just for a session, whitelisting support
INFO>*/

var defaultConfig = {
//<DEFAULT_CONFIG
// path to a whitelist 
whiteList : data.configDir + "/" + data.profile + "/requestpolicy.json",

// shortcut to block/allow requests
shortcut : "erp",

// shortcut to unblock requests from current site that are blocked on all
// sites
unblockCurrent : "erC",

// shortcut to unblock requests that are blocked on all sites
unblockAll : "erA",

// reload current site after blocking / unblocking a request
autoreload : false, 

// notify about blocked requests
notify : false

//>DEFAULT_CONFIG
};
var config = {};

var persistentList = null;
var tmpList = {};

var getPrivate = function (wv)  
{
    var p = script.getPrivate(wv, "foo");
    if (!p) 
    {
        p = { domains : [], blocked : 0 };
        script.setPrivate(wv, "foo", p);
    }
    return p;
};

function listAdd(o, key, value, doWrite) 
{
    if (!o[key])
        o[key] = [];
    if (o[key].indexOf(value) == -1) 
        o[key].push(value);
    if (doWrite) 
        io.write(config.whiteList, "w", JSON.stringify(persistentList));
}
function listRemove(o, firstParty, domain, doWrite) 
{
    var idx;
    if (o[firstParty] && (idx = o[firstParty].indexOf(domain)) != -1) 
    {
        o[firstParty].splice(idx, 1);
        if (o[firstParty].length === 0) 
            delete o[firstParty];
        if (doWrite) 
            io.write(config.whiteList, "w", JSON.stringify(persistentList));
        return true;
    }
    return false;
}

// MENU {{{
function showMenu() 
{
    var tmpWhiteListed, whiteListed;
    var isWhiteListed = false;
    var dom, i, l, domains, labels, domain;

    domain = tabs.current.mainFrame.domain;
    if (domain === null)
        return;

    domains = getPrivate(tabs.current).domains;
    labels = [];

    for (i=0, l=domains.length; i<l; ++i)
    {
        (function(dom) {
            if (persistentList._alwaysBlock && persistentList._alwaysBlock.indexOf(dom) != -1) 
                return;

            whiteListed = persistentList[domain] && persistentList[domain].indexOf(dom) != -1;
            tmpWhiteListed = tmpList[domain] && tmpList[domain].indexOf(dom) != -1;
            if (!persistentList._always || persistentList._always.indexOf(dom) == -1) 
            {
                if (!whiteListed) {
                    labels.push({ 
                            left : "[" + dom + "] allow persistently", 
                            action : function () {
                                listAdd(persistentList, domain, dom, true);
                            }
                    });
                }
                if (!tmpWhiteListed && !whiteListed) {
                    labels.push({ 
                            left : "[" + dom + "] allow temporarily", 
                            action : function() {
                                listAdd(tmpList, domain, dom, false);
                            }
                    });
                }

                else
                {
                    var right = "";
                    if (tmpWhiteListed) {
                        right += "temporarily" + (whiteListed ? "/persistently " : " ");
                    }
                    else if (whiteListed) {
                        right += "persistently ";
                    }
                    right += "allowed";
                    labels.push({ 
                            left : "[" +  dom + "] block",
                            right : right,
                            action : function() {
                                listRemove(persistentList, domain, dom, true);
                                listRemove(tmpList, domain, dom, false);
                            }
                    });
                }
            }
            isWhiteListed = isWhiteListed || whiteListed || tmpWhiteListed;
            if (!persistentList._always || persistentList._always.indexOf(dom) == -1) 
            {
                labels.push({
                        left : "[" +  dom + "] allow on all sites", 
                        action : function() {
                            listAdd(persistentList, "_always", dom, true);
                        }
                });
            }
            else 
            {
                labels.push({
                        left : "[" + dom + "] don't allow on all sites", 
                        action : function() {
                            listRemove(persistentList, "_always", dom, true);
                        }
                });
            }
            labels.push({ 
                    left : "[" + dom + "] block on all sites", 
                    action : function () {
                        listAdd(persistentList, "_alwaysBlock", dom, true);
                    }
            });
        })(domains[i]);
    }
    var allAllowed = (persistentList._all && persistentList._all.indexOf(domain) != -1) ||
                         (tmpList._all && tmpList._all.indexOf(domain) != -1);
    if (isWhiteListed || allAllowed) 
    {
        labels.unshift({
                left : "Block all requests on " + domain, 
                action : function () {
                    delete persistentList[domain];
                    listRemove(persistentList, "_all", domain, false);
                    // necessary if persistentList.domain exists
                    io.write(config.whiteList, "w", JSON.stringify(persistentList));
                    delete tmpList[domain];
                    listRemove(tmpList, "_all", domain, false);
                }
        });
    }
    if (allAllowed) 
    {
        labels.unshift({
                left : "Don't allow all requests on " + domain, 
                action : function() {
                    listRemove(tmpList, "_all", domain, false);
                    listRemove(persistentList, "_all", domain, true);
                }
        });
    }
    else 
    {
        labels.unshift({
                left : "Temporarily allow all requests on " + domain, 
                action : function() {
                    listAdd(tmpList, "_all", domain, false);
                }
        });
        labels.unshift({
                left : "Allow all requests on " + domain, 
                action : function() {
                    listAdd(persistentList, "_all", domain, true);
                }
        });
    }

    util.tabComplete("Requestpolicy:", labels, function (response) {
        var i, l, len;
        for (i=0, len = labels.length; i<len; ++i) 
        {
            l = labels[i];
            if (l.left == response) 
            {
                l.action();
                if (config.autoreload) 
                    tabs.current.reload();
            }
        }
    }, true);
}//}}}

function unblockCurrent() 
{
    if (!persistentList._alwaysBlock) 
    {
        io.notify("No domains to unblock");
        return;
    }
    var domains = getPrivate(tabs.current).domains;
    //var domains = persistentList._alwaysBlock;
    var labels = [], i, l;
    for (i=0, l = domains.length; i<l; i++) 
    {
        if (persistentList._alwaysBlock.indexOf(domains[i]) != -1) 
            labels.push({ left : domains[i] });
    }
    if (labels.length > 0) 
    {
        util.tabComplete("Unblock:", labels, function(response) {
            listRemove(persistentList, "_alwaysBlock", response, true);
            if (config.autoreload) 
                tabs.current.reload();
        }, true);
    }
    else
        io.notify("No domains to unblock");
}

function unblockAll() 
{
    if (!persistentList._alwaysBlock) 
    {
        io.notify("No domains to unblock");
        return;
    }
    var i, l, labels = [];
    var domains = persistentList._alwaysBlock;
    for (i=0, l=domains.length; i<l; ++i) 
        labels.push({ left : domains[i] });

    if (labels.length > 0) 
    {
        util.tabComplete("Unblock:", labels, function(response) {
            listRemove(persistentList, "_alwaysBlock", response, true);
            if (config.autoreload) 
                tabs.current.reload();
        }, true);
    }
}

function blockRequest(wv, request, priv, domain) 
{
    request.uri = "about:blank";
    priv.blocked++;
    if (config.notify && wv == tabs.current)
        io.notify("RP: blocked " + domain);
    return true;
}
// SIGNALS {{{
var resourceCB = (function () 
{
    var regexEmpty = /^\s*$/;
    return function resourceCB(wv, frame, request, response) 
    {
        var o, message, domain, firstParty;
        if (regexEmpty.test(request.uri))
            return false;

        message = request.message;
        if (!message)
            return false;

        if (message.firstParty) 
            firstParty = net.domainFromHost(message.firstParty.host);
        else 
            firstParty = wv.mainFrame.domain;
        domain = net.domainFromHost(message.uri.host);
        if (firstParty == domain)
            return false;

        o = getPrivate(wv);
        if (o.domains.indexOf(domain) == -1) 
            o.domains.push(domain);

        // Check for requests that are always blocked
        if (persistentList._alwaysBlock && persistentList._alwaysBlock.indexOf(domain) != -1) 
            return blockRequest(wv, request, o, domain);

        // Check if domain is always allowed
        if ((persistentList._all && persistentList._all.indexOf(firstParty) != -1) || 
                (tmpList._all && tmpList._all.indexOf(firstParty) != -1)) 
            return false; 

        // Check request is always allowed
        if (persistentList._always && persistentList._always.indexOf(domain) != -1) 
            return false;

        // Check if request is whitelisted
        if (  (!persistentList[firstParty]    || persistentList[firstParty].indexOf(domain) == -1) && 
                (!tmpList[firstParty] || tmpList[firstParty].indexOf(domain) == -1)) 
            return blockRequest(wv, request, o, domain);
    };
})();
function navigationCB(wv, frame) 
{
    if (frame == wv.mainFrame) 
    {
        var o = getPrivate(wv);
        o.domains = [];
        o.blocked = 0;
    }
}

function loadFinishedCB(wv) 
{
    if (wv != tabs.current)
        return;

    var blocked = getPrivate(wv).blocked;
    if (blocked > 0) 
        io.notify("RP: blocked " + blocked + " requests");
}

var requestpolicy = {
    defaultConfig : defaultConfig, 
    init : function(c) 
    {
        config = c;
        if (system.fileTest(config.whiteList, FileTest.regular | FileTest.symlink)) 
        {
            var rawWhiteList = io.read(config.whiteList);
            try 
            {
                persistentList = JSON.parse(rawWhiteList);
            }
            catch (e) 
            {
                script.debug(e);
                return false;
            }
        }
        persistentList = persistentList || {};
        Signal.connect("resource", resourceCB);
        Signal.connect("navigation", navigationCB);
        if (config.notify)
            Signal.connect("loadFinished", loadFinishedCB);
        bind(config.shortcut, showMenu, "requestpolicy");
        bind(config.unblockCurrent, unblockCurrent, "requestpolicyUnblockCurrent");
        bind(config.unblockAll, unblockAll, "requestpolicyUnblockAll");
        return true;
    },
    end : function () 
    {
        Signal.disconnect(resourceCB);
        Signal.disconnect(navigationCB);
        Signal.disconnect(loadFinishedCB);
        unbind("requestpolicy");
        unbind("requestpolicyUnblockCurrent");
        unbind("requestpolicyUnblockAll");
    }
};
return requestpolicy;
// vim: set ft=javascript:
