aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2022-01-18 18:55:05 +0100
committerWojtek Kosior <koszko@koszko.org>2022-01-18 19:28:11 +0100
commit046b8a7b3e7259bf451926732e6221076b1d4153 (patch)
tree3746d157139ba88f7c78a7d17cd97a6e5cf48a6a
parent17614206a6e23900e0ddd91c4e4e40ec08eaec99 (diff)
downloadbrowser-extension-046b8a7b3e7259bf451926732e6221076b1d4153.tar.gz
browser-extension-046b8a7b3e7259bf451926732e6221076b1d4153.zip
facilitate caching repository responses in content scripts
-rw-r--r--background/CORS_bypass_server.js2
-rw-r--r--content/repo_query_cacher.js84
-rw-r--r--html/item_preview.html4
-rw-r--r--test/unit/test_CORS_bypass_server.py3
-rw-r--r--test/unit/test_repo_query_cacher.py114
-rw-r--r--test/world_wide_library.py15
6 files changed, 217 insertions, 5 deletions
diff --git a/background/CORS_bypass_server.js b/background/CORS_bypass_server.js
index cbed945..664100b 100644
--- a/background/CORS_bypass_server.js
+++ b/background/CORS_bypass_server.js
@@ -76,7 +76,7 @@ async function perform_download(fetch_data, respond_cb) {
}
function on_CORS_bypass_request([type, fetch_data], sender, respond_cb) {
- if (type !== "CORS_bypasss")
+ if (type !== "CORS_bypass")
return;
perform_download(fetch_data).then(respond_cb);
diff --git a/content/repo_query_cacher.js b/content/repo_query_cacher.js
new file mode 100644
index 0000000..bdba189
--- /dev/null
+++ b/content/repo_query_cacher.js
@@ -0,0 +1,84 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Caching responses from remote repositories and serving them to
+ * popup pages.
+ *
+ * Copyright (C) 2022 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 of this code in a
+ * proprietary program, I am not going to enforce this in court.
+ */
+
+/*
+ * Map URLs to objects containing parsed responses, error info or promises
+ * resolving to those.
+ */
+const cache = new Map();
+
+async function perform_download(url) {
+ if (cache.has(url))
+ return cache.get(url);
+
+ let resolve_cb;
+ cache.set(url, new Promise(cb => resolve_cb = cb));
+
+ try {
+ const opts = {url, to_get: ["ok", "status"], to_call: ["json"]};
+ var result = await browser.runtime.sendMessage(["CORS_bypass", opts]);
+ if (result === undefined)
+ result = {error: "Couldn't communicate with background script."};
+ } catch(e) {
+ var result = {error: e + ""};
+ }
+
+ cache.set(url, result);
+ resolve_cb(result);
+
+ return result;
+}
+
+function on_repo_query_request([type, url], sender, respond_cb) {
+ if (type !== "repo_query")
+ return;
+
+ perform_download(url).then(respond_cb);
+
+ return true;
+}
+
+function start() {
+ browser.runtime.onMessage.addListener(on_repo_query_request);
+}
diff --git a/html/item_preview.html b/html/item_preview.html
index c631f37..34ce6e1 100644
--- a/html/item_preview.html
+++ b/html/item_preview.html
@@ -38,13 +38,13 @@
#LOADCSS html/base.css
#LOADCSS html/grid.css
<style>
- .preview_main_div {
+ .item_preview_main_div {
margin: 1.3em 0.8em 1em 0.8em;
}
</style>
<template>
<div id="resource_preview" data-template="main_div"
- class="grid_2 grid_form preview_main_div">
+ class="grid_2 grid_form item_preview_main_div">
<h3 class="grid_col_both">resource preview</h3>
<label class="grid_col_1">identifier:</label>
<span data-template="identifier" class="grid_col_2">...</span>
diff --git a/test/unit/test_CORS_bypass_server.py b/test/unit/test_CORS_bypass_server.py
index 35ea565..8b845b9 100644
--- a/test/unit/test_CORS_bypass_server.py
+++ b/test/unit/test_CORS_bypass_server.py
@@ -21,7 +21,6 @@ import pytest
import json
from selenium.webdriver.support.ui import WebDriverWait
-from ..extension_crafting import ExtraHTML
from ..script_loader import load_script
from ..world_wide_library import some_data
@@ -46,7 +45,7 @@ async function fetch_resources() {
const results = {};
const promises = [];
for (const [name, url] of Object.entries(urls)) {
- const sending = browser.runtime.sendMessage(["CORS_bypasss",
+ const sending = browser.runtime.sendMessage(["CORS_bypass",
fetch_data(url)]);
promises.push(sending.then(response => results[name] = response));
}
diff --git a/test/unit/test_repo_query_cacher.py b/test/unit/test_repo_query_cacher.py
new file mode 100644
index 0000000..d5c7396
--- /dev/null
+++ b/test/unit/test_repo_query_cacher.py
@@ -0,0 +1,114 @@
+# SPDX-License-Identifier: CC0-1.0
+
+"""
+Haketilo unit tests - caching responses from remote repositories
+"""
+
+# This file is part of Haketilo
+#
+# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the CC0 1.0 Universal License as published by
+# the Creative Commons Corporation.
+#
+# 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
+# CC0 1.0 Universal License for more details.
+
+import pytest
+import json
+from selenium.webdriver.support.ui import WebDriverWait
+
+from ..script_loader import load_script
+
+tab_id_responder = '''
+function tell_tab_id(msg, sender, respond_cb) {
+ if (msg[0] === "learn_tab_id")
+ respond_cb(sender.tab.id);
+}
+browser.runtime.onMessage.addListener(tell_tab_id);
+'''
+
+def content_script():
+ return load_script('content/repo_query_cacher.js') + ''';
+ start();
+ browser.runtime.sendMessage(["learn_tab_id"])
+ .then(tid => window.wrappedJSObject.haketilo_tab = tid);
+ '''
+
+def bypass_js():
+ return load_script('background/CORS_bypass_server.js') + '; start();'
+
+def run_content_script_in_new_window(driver, url):
+ initial_handle = driver.current_window_handle
+ handles = driver.window_handles
+ driver.execute_script('window.open(arguments[0], "_blank");', url)
+ WebDriverWait(driver, 10).until(lambda d: d.window_handles is not handles)
+ new_handle = [h for h in driver.window_handles if h not in handles][0]
+
+ driver.switch_to.window(new_handle)
+
+ get_tab_id = lambda d: d.execute_script('return window.haketilo_tab;')
+ tab_id = WebDriverWait(driver, 10).until(get_tab_id)
+
+ driver.switch_to.window(initial_handle)
+ return tab_id
+
+def fetch_through_cache(driver, tab_id, url):
+ return driver.execute_script(
+ '''
+ return browser.tabs.sendMessage(arguments[0],
+ ["repo_query", arguments[1]]);
+ ''',
+ tab_id, url)
+
+@pytest.mark.ext_data({
+ 'content_script': content_script,
+ 'background_script': lambda: bypass_js() + ';' + tab_id_responder
+})
+@pytest.mark.usefixtures('webextension')
+def test_repo_query_cacher_normal_use(driver, execute_in_page):
+ """
+ Test if HTTP requests made through our cacher return correct results.
+ """
+ tab_id = run_content_script_in_new_window(driver, 'https://gotmyowndoma.in')
+
+ result = fetch_through_cache(driver, tab_id, 'https://counterdoma.in/')
+ assert set(result.keys()) == {'ok', 'status', 'json'}
+ counter_initial = result['json']['counter']
+ assert type(counter_initial) is int
+
+ for i in range(2):
+ result = fetch_through_cache(driver, tab_id, 'https://counterdoma.in/')
+ assert result['json']['counter'] == counter_initial
+
+ tab_id = run_content_script_in_new_window(driver, 'https://gotmyowndoma.in')
+ result = fetch_through_cache(driver, tab_id, 'https://counterdoma.in/')
+ assert result['json']['counter'] == counter_initial + 1
+
+ for i in range(2):
+ result = fetch_through_cache(driver, tab_id, 'https://nxdoma.in/')
+ assert set(result.keys()) == {'ok', 'status', 'error-json'}
+ assert result['ok'] == False
+ assert result['status'] == 404
+
+ for i in range(2):
+ result = fetch_through_cache(driver, tab_id, 'bad://url')
+ assert set(result.keys()) == {'error'}
+
+@pytest.mark.ext_data({
+ 'content_script': content_script,
+ 'background_script': tab_id_responder
+})
+@pytest.mark.usefixtures('webextension')
+def test_repo_query_cacher_bgscript_error(driver):
+ """
+ Test if our cacher properly reports errors in communication with the
+ background script.
+ """
+ tab_id = run_content_script_in_new_window(driver, 'https://gotmyowndoma.in')
+
+ result = fetch_through_cache(driver, tab_id, 'https://counterdoma.in/')
+ assert set(result.keys()) == {'error'}
diff --git a/test/world_wide_library.py b/test/world_wide_library.py
index bed6ec3..14e3d2f 100644
--- a/test/world_wide_library.py
+++ b/test/world_wide_library.py
@@ -31,6 +31,7 @@ from hashlib import sha256
from pathlib import Path
from shutil import rmtree
from threading import Lock
+import json
from .misc_constants import here
@@ -87,6 +88,18 @@ def dump_scripts(directory='./injected_scripts'):
some_data = '{"some": "data"}'
+# used by handler function of https://counterdoma.in
+request_counter = 0
+
+def serve_counter(command, get_params, post_params):
+ global request_counter
+ request_counter += 1
+ return (
+ 200,
+ {'Cache-Control': 'private, max-age=0, no-store'},
+ json.dumps({'counter': request_counter})
+ )
+
catalog = {
'http://gotmyowndoma.in':
(302, {'location': 'http://gotmyowndoma.in/index.html'}, None),
@@ -108,6 +121,8 @@ catalog = {
'https://anotherdoma.in/resource/blocked/by/CORS.json':
lambda command, get_params, post_params: (200, {}, some_data),
+ 'https://counterdoma.in/': serve_counter,
+
'https://serve.scrip.ts/': serve_script,
'https://site.with.scripts.block.ed':