aboutsummaryrefslogtreecommitdiff
/**
 * This file is part of Haketilo.
 *
 * Function: Serving page actions to content scripts.
 *
 * Copyright (C) 2021 Wojtek Kosior
 * Redistribution terms are gathered in the `copyright' file.
 */

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

var storage;
var handler;
let policy_observable;

function send_actions(url, port)
{
    const [pattern, queried_settings] = query_best(storage, url);

    const settings = {allow: policy_observable && policy_observable.value};
    Object.assign(settings, queried_settings);
    if (settings.components)
	settings.allow = false;

    const repos = storage.get_all(TYPE_PREFIX.REPO);

    port.postMessage(["settings", [pattern, settings, repos]]);

    const components = settings.components;
    const processed_bags = new Set();

    if (components !== undefined)
	send_scripts([components], port, processed_bags);
}

// 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]);
    let url = message.url;
    console.log({url});
    send_actions(url, port);
}

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);

    policy_observable = await light_storage.observe_var("default_allow");
}

/*
 * EXPORTS_START
 * EXPORT start_page_actions_server
 * EXPORTS_END
 */