diff options
-rwxr-xr-x | configure | 3 | ||||
-rw-r--r-- | content/policy_enforcing.js | 40 | ||||
-rw-r--r-- | pytest.ini | 1 | ||||
-rw-r--r-- | test/conftest.py | 26 | ||||
-rw-r--r-- | test/unit/test_popup.py | 6 |
5 files changed, 66 insertions, 10 deletions
@@ -91,6 +91,9 @@ if [ "x$BROWSER_BINARY" = x ]; then elif [ "x$TARGET" = xlibrewolf ]; then # Debian's path to Librewolf BROWSER_BINARY=/usr/share/librewolf/librewolf + elif [ "x$TARGET" = xiceweasel ]; then + # Parabola's path to Iceweasel + BROWSER_BINARY=/usr/lib/iceweasel/iceweasel elif [ "x$TARGET" = xicecat ]; then # Parabola's path to IceCat BROWSER_BINARY=/usr/lib/icecat/icecat diff --git a/content/policy_enforcing.js b/content/policy_enforcing.js index 320b6d0..45529ea 100644 --- a/content/policy_enforcing.js +++ b/content/policy_enforcing.js @@ -159,6 +159,12 @@ function desanitize_script(script) { delete script.haketilo_blocked_type; } +/* + * Blocking certain attributes that might allow 'javascript:' URLs. Some of + * these are: <iframe>'s 'src' attributes (would normally execute js in URL upon + * frame's load), <object>'s 'data' attribute (would also execute upon load) and + * <a>'s 'href' attribute (would execute upon link click). + */ const bad_url_reg = /^data:([^,;]*ml|unknown-content-type)|^javascript:/i; function sanitize_element_urls(element) { if (element.haketilo_sanitized_urls) @@ -166,13 +172,37 @@ function sanitize_element_urls(element) { element.haketilo_sanitized_urls = true; + let some_attr_blocked = false; + for (const attr of [...element.attributes || []] .filter(attr => /^(href|src|data)$/i.test(attr.localName)) .filter(attr => bad_url_reg.test(attr.value))) { + /* + * Under some browsers (Mozilla) removing attributes doesn't stop their + * javascript from executing, but replacing them does. For 'src' and + * 'data' I chose to replace the attribute with a 'data:' URL and have + * it replace bad <iframe>'s/<object>'s contents with a "blocked" + * string. For 'href' (which appears on <a>'s) I chose to use a + * 'javascript:' URL to avoid having the page reloaded upon a link + * click. + */ const replacement_value = /^href$/i.test(attr.localName) ? - "javascript:void('blocked');" : "data:text/plain,blocked"; + "javascript:void('blocked');" : "data:text/plain,blocked"; + some_attr_blocked = true; block_attribute(element, attr.localName, attr.namespaceURI, - replacement_value); + replacement_value); + } + + /* + * Trial and error shows that under certain browsers additional element + * removal and re-addition might be necessary to prevent execution of a + * 'javascript:' URL (Parabola's Iceweasel 75 requires it for 'src' URL of + * an <iframe>). + */ + if (some_attr_blocked) { + const replacement_elem = document.createElement("a"); + element.replaceWith(replacement_elem); + replacement_elem.replaceWith(element); } } @@ -189,8 +219,10 @@ function sanitize_element_onevent(element) { continue; /* - * Guard against redefined getter on DOM object property. This should - * not be an issue */ + * Guard against redefined getter on DOM object property. This is a + * supplemental security measure since page's own scripts should be + * blocked and unable to redefine properties, anyway. + */ if (Object.getOwnPropertyDescriptor(element.wrappedJSObject, attr)) { console.error("Redefined property on a DOM object! The page might have bypassed our script blocking measures!"); continue; @@ -17,3 +17,4 @@ markers = ext_data: define a custom testing extension for `webextension` fixture. get_page: define a url the `driver` fixture should navigate the browser to. + second_driver: tell `driver` fixture to spawn a separate browser instance fr this test. diff --git a/test/conftest.py b/test/conftest.py index b9c46b0..dec6d91 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -59,10 +59,20 @@ def close_all_but_one_window(driver): @pytest.fixture() def driver(_driver, request): nav_target = request.node.get_closest_marker('get_page') - close_all_but_one_window(_driver) - _driver.get(nav_target.args[0] if nav_target else 'about:blank') - _driver.implicitly_wait(0) - yield _driver + nav_target = nav_target.args[0] if nav_target else 'about:blank' + + second_driver = request.node.get_closest_marker('second_driver') + + if second_driver: + with firefox_safe_mode() as _driver: + _driver.get(nav_target) + yield _driver + _driver.quit() + else: + close_all_but_one_window(_driver) + _driver.get(nav_target) + _driver.implicitly_wait(0) + yield _driver @pytest.fixture() def webextension(driver, request): @@ -87,8 +97,12 @@ def webextension(driver, request): yield - close_all_but_one_window(driver) - driver.get('https://gotmyowndoma.in/') + # Unloading an extension might cause its windows to vanish. Make sure + # there's at least one window navigated to some other page before + # uninstalling the addon. Otherwise, we could be left with a windowless + # browser :c + driver.switch_to.window(driver.window_handles[-1]) + driver.get('about:blank') driver.uninstall_addon(addon_id) ext_path.unlink() diff --git a/test/unit/test_popup.py b/test/unit/test_popup.py index 3163adb..1fc262c 100644 --- a/test/unit/test_popup.py +++ b/test/unit/test_popup.py @@ -235,6 +235,12 @@ def test_popup_repo_query(driver, execute_in_page): @pytest.mark.ext_data(popup_ext_data) @pytest.mark.usefixtures('webextension') +# Under Parabola's Iceweasel 75 the settings page's window opened during this +# test is impossible to close using driver.close() - it raises an exception with +# message 'closeTab() not supported in iceweasel'. To avoid such error during +# test cleanup, we use the mark below to tell our driver fixture to span a +# separate browser instance for this test. +@pytest.mark.second_driver() def test_popup_settings_opening(driver, execute_in_page): """ Test opening the settings page from popup through button click. |