aboutsummaryrefslogtreecommitdiff
/**
 * This file is part of Haketilo.
 *
 * Function: Serving page actions to content scripts.
 *
 * Copyright (C) 2021 Wojtek Kosior
 *
 * 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.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute forms of that code without the copy of the GNU
 * GPL normally required by section 4, provided you include this
 * license notice and, in case of non-source distribution, a URL
 * through which recipients can access the Corresponding Source.
 * If you modify file(s) with this exception, you may extend this
 * exception to your version of the file(s), but you are not
 * obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * As a special exception to the GPL, any HTML file which merely
 * makes function calls to this code, and for that purpose
 * includes it by reference shall be deemed a separate work for
 * copyright law purposes. If you modify this code, you may extend
 * this exception to your version of the code, but you are not
 * obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
 * license. Although I request that you do not make use this code in a
 * proprietary program, I am not going to enforce this in court.
 */

/*
 * IMPORTS_START
 * IMPORT get_storage
 * IMPORT light_storage
 * IMPORT TYPE_PREFIX
 * IMPORT CONNECTION_TYPE
 * IMPORT browser
 * IMPORT listen_for_connection
 * IMPORT sha256
 * IMPORT make_ajax_request
 * IMPORTS_END
 */

var storage;
var handler;

// TODO: parallelize script fetching
async function send_scripts(components, port, processed_bags)
{
    for (let [prefix, name] of components) {
	if (prefix === TYPE_PREFIX.BAG) {
	    if (processed_bags.has(name)) {
		console.log(`preventing recursive inclusion of bag ${name}`);
		continue;
	    }

	    var bag = storage.get(TYPE_PREFIX.BAG, name);

	    if (bag === undefined) {
		console.log(`no bag in storage for key ${name}`);
		continue;
	    }

	    processed_bags.add(name);
	    await send_scripts(bag, port, processed_bags);

	    processed_bags.delete(name);
	} else {
	    let script_text = await get_script_text(name);
	    if (script_text === undefined)
		continue;

	    port.postMessage(["inject", [script_text]]);
	}
    }
}

async function get_script_text(script_name)
{
    try {
	let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name);
	if (script_data === undefined) {
	    console.log(`missing data for ${script_name}`);
	    return;
	}
	let script_text = script_data.text;
	if (!script_text)
	    script_text = await fetch_remote_script(script_data);
	return script_text;
    } catch (e) {
	console.log(e);
    }
}

async function fetch_remote_script(script_data)
{
    try {
	let xhttp = await make_ajax_request("GET", script_data.url);
	if (xhttp.status === 200) {
	    let computed_hash = sha256(xhttp.responseText);
	    if (computed_hash !== script_data.hash) {
		console.log(`Bad hash for ${script_data.url}\n    got ${computed_hash} instead of ${script_data.hash}`);
		return;
	    }
	    return xhttp.responseText;
	} else {
	    console.log("script not fetched: " + script_data.url);
	    return;
	}
    } catch (e) {
	console.log(e);
    }
}

function handle_message(port, message, handler)
{
    port.onMessage.removeListener(handler[0]);
    console.debug(`Loading payload '${message.payload}'.`);

    const processed_bags = new Set();

    send_scripts([message.payload], port, processed_bags);
}

function new_connection(port)
{
    console.log("new page actions connection!");
    let handler = [];
    handler.push(m => handle_message(port, m, handler));
    port.onMessage.addListener(handler[0]);
}

async function start_page_actions_server()
{
    storage = await get_storage();

    listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection);
}

/*
 * EXPORTS_START
 * EXPORT start_page_actions_server
 * EXPORTS_END
 */