aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--background/CORS_bypass_server.js90
-rw-r--r--test/unit/test_CORS_bypass_server.py110
-rw-r--r--test/world_wide_library.py5
3 files changed, 205 insertions, 0 deletions
diff --git a/background/CORS_bypass_server.js b/background/CORS_bypass_server.js
new file mode 100644
index 0000000..cbed945
--- /dev/null
+++ b/background/CORS_bypass_server.js
@@ -0,0 +1,90 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Allow other parts of the extension to bypass CORS by routing their
+ * request through this background script using one-off messages.
+ *
+ * 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 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.
+ */
+
+#FROM common/browser.js IMPORT browser
+
+async function get_prop(object, prop, result_object, call_prop=false) {
+ try {
+ result_object[prop] = call_prop ? (await object[prop]()) : object[prop];
+ } catch(e) {
+ result_object[`error-${prop}`] = "" + e;
+ }
+}
+
+async function perform_download(fetch_data, respond_cb) {
+ try {
+ const response = await fetch(fetch_data.url);
+ const result = {};
+
+ for (const prop of (fetch_data.to_get || []))
+ get_prop(response, prop, result);
+
+ const to_call = (fetch_data.to_call || []);
+ const promises = [];
+ for (let i = 0; i < to_call.length; i++) {
+ const response_to_use = i === to_call.length - 1 ?
+ response : response.clone();
+ promises.push(get_prop(response_to_use, to_call[i], result, true));
+ }
+
+ await Promise.all(promises);
+ return result;
+ } catch(e) {
+ return {error: "" + e};
+ }
+}
+
+function on_CORS_bypass_request([type, fetch_data], sender, respond_cb) {
+ if (type !== "CORS_bypasss")
+ return;
+
+ perform_download(fetch_data).then(respond_cb);
+
+ return true;
+}
+
+function start() {
+ browser.runtime.onMessage.addListener(on_CORS_bypass_request);
+}
+#EXPORT start
diff --git a/test/unit/test_CORS_bypass_server.py b/test/unit/test_CORS_bypass_server.py
new file mode 100644
index 0000000..35ea565
--- /dev/null
+++ b/test/unit/test_CORS_bypass_server.py
@@ -0,0 +1,110 @@
+# SPDX-License-Identifier: CC0-1.0
+
+"""
+Haketilo unit tests - routing HTTP requests through background script
+"""
+
+# 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 ..extension_crafting import ExtraHTML
+from ..script_loader import load_script
+from ..world_wide_library import some_data
+
+urls = {
+ 'resource': 'https://anotherdoma.in/resource/blocked/by/CORS.json',
+ 'nonexistent': 'https://nxdoma.in/resource.json',
+ 'invalid': 'w3csucks://invalid.url/'
+}
+
+content_script = '''\
+const urls = %s;
+
+function fetch_data(url) {
+ return {
+ url,
+ to_get: ["ok", "status"],
+ to_call: ["text", "json"]
+ };
+}
+
+async function fetch_resources() {
+ const results = {};
+ const promises = [];
+ for (const [name, url] of Object.entries(urls)) {
+ const sending = browser.runtime.sendMessage(["CORS_bypasss",
+ fetch_data(url)]);
+ promises.push(sending.then(response => results[name] = response));
+ }
+
+ await Promise.all(promises);
+
+ window.wrappedJSObject.haketilo_fetch_results = results;
+}
+
+fetch_resources();
+'''
+
+content_script = content_script % json.dumps(urls);
+
+@pytest.mark.ext_data({
+ 'content_script': content_script,
+ 'background_script':
+ lambda: load_script('background/CORS_bypass_server.js') + '; start();'
+})
+@pytest.mark.usefixtures('webextension')
+def test_CORS_bypass_server(driver, execute_in_page):
+ """
+ Test if CORS bypassing works and if errors get properly forwarded.
+ """
+ driver.get('https://gotmyowndoma.in/')
+
+ # First, verify that requests without CORS bypass measures fail.
+ results = execute_in_page(
+ '''
+ const result = {};
+ let promises = [];
+ for (const [name, url] of Object.entries(arguments[0])) {
+ const [ok_cb, err_cb] =
+ ["ok", "err"].map(status => () => result[name] = status);
+ promises.push(fetch(url).then(ok_cb, err_cb));
+ }
+ // Make the promises non-failing.
+ promises = promises.map(p => new Promise(cb => p.then(cb, cb)));
+ returnval(Promise.all(promises).then(() => result));
+ ''',
+ {**urls, 'sameorigin': './nonexistent_resource'})
+
+ assert results == dict([*[(k, 'err') for k in urls.keys()],
+ ('sameorigin', 'ok')])
+
+ done = lambda d: d.execute_script('return window.haketilo_fetch_results;')
+ results = WebDriverWait(driver, 10).until(done)
+
+ assert set(results['invalid'].keys()) == {'error'}
+
+ assert set(results['nonexistent'].keys()) == \
+ {'ok', 'status', 'text', 'error-json'}
+ assert results['nonexistent']['ok'] == False
+ assert results['nonexistent']['status'] == 404
+ assert results['nonexistent']['text'] == 'Handler for this URL not found.'
+
+ assert set(results['resource'].keys()) == {'ok', 'status', 'text', 'json'}
+ assert results['resource']['ok'] == True
+ assert results['resource']['status'] == 200
+ assert results['resource']['text'] == some_data
+ assert results['resource']['json'] == json.loads(some_data)
diff --git a/test/world_wide_library.py b/test/world_wide_library.py
index f66a6d5..bed6ec3 100644
--- a/test/world_wide_library.py
+++ b/test/world_wide_library.py
@@ -85,6 +85,8 @@ def dump_scripts(directory='./injected_scripts'):
file.write(script)
served_scripts_lock.release()
+some_data = '{"some": "data"}'
+
catalog = {
'http://gotmyowndoma.in':
(302, {'location': 'http://gotmyowndoma.in/index.html'}, None),
@@ -103,6 +105,9 @@ catalog = {
'https://gotmyowndoma.in/scripts_to_block_1.html':
(200, {}, here / 'data' / 'pages' / 'scripts_to_block_1.html'),
+ 'https://anotherdoma.in/resource/blocked/by/CORS.json':
+ lambda command, get_params, post_params: (200, {}, some_data),
+
'https://serve.scrip.ts/': serve_script,
'https://site.with.scripts.block.ed':