diff options
author | Wojtek Kosior <koszko@koszko.org> | 2022-10-25 11:30:45 +0200 |
---|---|---|
committer | Wojtek Kosior <koszko@koszko.org> | 2022-10-25 11:30:45 +0200 |
commit | 37b3cf9fb2a56cfa980844f527d834916b38cca8 (patch) | |
tree | 288a3b3ddc7e1fe115d568480f44313173183c2d /src | |
parent | 44c09ab27ce8407f4fc5c75df9cdf309df8463eb (diff) | |
download | haketilo-hydrilla-37b3cf9fb2a56cfa980844f527d834916b38cca8.tar.gz haketilo-hydrilla-37b3cf9fb2a56cfa980844f527d834916b38cca8.zip |
[proxy] make Haketilo popup functional
* Ad hoc payload creation was additionally fixed in this commit.
* Addition on newly created script blocking/allowing rules to pattern tree was additionally fixed in this commit. It is no longer necessary to restart Haketilo for new rules to come into effect.
Diffstat (limited to 'src')
31 files changed, 819 insertions, 90 deletions
diff --git a/src/hydrilla/common_jinja_templates/base.html.jinja b/src/hydrilla/common_jinja_templates/base.html.jinja index 08178b3..0c29c0c 100644 --- a/src/hydrilla/common_jinja_templates/base.html.jinja +++ b/src/hydrilla/common_jinja_templates/base.html.jinja @@ -67,7 +67,15 @@ code in a proprietary work, I am not going to enforce this in court. </label> {% endmacro %} -{% macro form_field(field_name, required=true, sep_after=true, height=none) %} +{% + macro form_field( + field_name, + required = true, + sep_after = true, + height = none, + initial_value = none + ) +%} <div class="flex-row"> {% set attrs = { @@ -79,9 +87,11 @@ code in a proprietary work, I am not going to enforce this in court. %} {% if height is none %} + {% do attrs.update({'value': initial_value}) %} <input{{ attrs|xmlattr }}> {% else %} - <textarea{{ attrs|xmlattr }}></textarea> + {% set value = initial_value|default('', true) %} + <textarea{{ attrs|xmlattr }}>{{ value }}</textarea> {% endif %} </div> @@ -90,10 +100,17 @@ code in a proprietary work, I am not going to enforce this in court. {% endif %} {% endmacro %} +{% macro verbatim(code) %} + <code><pre>{{ code }}</pre></code> +{% endmacro %} + <html> <head> {% block head %} - <style> + {% if style_nonce is defined %} + {% set style_attrs = {'nonce': style_nonce} %} + {% endif %} + <style{{ style_attrs|default({})|xmlattr }}> {% block style %} body { color: #444; @@ -213,6 +230,13 @@ code in a proprietary work, I am not going to enforce this in court. border-radius: 0; } + code > pre { + overflow-x: auto; + background-color: #f0f0f0; + padding: 5px; + border: 1px solid #e3e3e3; + } + .hide { display: none !important; } diff --git a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po index bb41daf..43e6d18 100644 --- a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po +++ b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: hydrilla 2.0\n" "Report-Msgid-Bugs-To: koszko@koszko.org\n" -"POT-Creation-Date: 2022-10-22 11:17+0200\n" +"POT-Creation-Date: 2022-10-25 10:28+0200\n" "PO-Revision-Date: 2022-02-12 00:00+0000\n" "Last-Translator: Wojtek Kosior <koszko@koszko.org>\n" "Language: en_US\n" @@ -194,53 +194,140 @@ msgstr "Data directory for Haketilo to use. Defaults to \"{}\"." msgid "cli_opt.haketilo.version" msgstr "Print version information and exit" -#: src/hydrilla/proxy/addon.py:192 +#: src/hydrilla/proxy/addon.py:195 msgid "warn.proxy.setting_already_configured_{}" msgstr "" "Attempt was made to configure Mitmproxy addon's option '{}' which has " "already been configured." -#: src/hydrilla/proxy/addon.py:227 +#: src/hydrilla/proxy/addon.py:230 msgid "warn.proxy.couldnt_launch_browser" msgstr "" "Failed to open a URL in a web browser. Do you have a default web browser " "configured?" -#: src/hydrilla/proxy/addon.py:268 +#: src/hydrilla/proxy/addon.py:271 msgid "err.proxy.unknown_error_{}_try_again" msgstr "" "Haketilo experienced an error. Try again.\n" "\n" "{}" -#: src/hydrilla/proxy/policies/payload_resource.py:250 +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:25 +msgid "info.base.title" +msgstr "Page info" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:30 +msgid "info.base.heading.page_info" +msgstr "Haketilo page handling details" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:33 +msgid "info.base.page_url_label" +msgstr "Page URL" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:41 +msgid "info.base.page_policy_label" +msgstr "Active policy" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:53 +msgid "info.base.more_config_options_label" +msgstr "Configure" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:61 +msgid "info.base.this_site_script_blocking_button" +msgstr "JS blocking on this site" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:64 +msgid "info.base.this_site_payload_button" +msgstr "Payload for this site" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:67 +msgid "info.base.this_page_script_blocking_button" +msgstr "JS blocking on this page" + +#: src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja:70 +msgid "info.base.this_page_payload_button" +msgstr "Payload for this page" + +#: src/hydrilla/proxy/policies/info_pages_templates/js_error_blocked_info.html.jinja:13 +msgid "info.js_error_blocked.html" +msgstr "" +"Haketilo experienced an error when deciding the policy to apply on this " +"page. As a security measure, it is going to block JavaScript on pages " +"where this happens. This should not normally occur, you may consider <a " +"href=\"mailto:koszko@koszko.org\">reporting the issue</a>." + +#: src/hydrilla/proxy/policies/info_pages_templates/js_error_blocked_info.html.jinja:18 +msgid "info.js_error_blocked.stacktrace" +msgstr "Error details" + +#: src/hydrilla/proxy/policies/info_pages_templates/js_fallback_allowed_info.html.jinja:13 +msgid "info.js_fallback_allowed" +msgstr "JavaScript is allowed to execute on this page. This is the default policy." + +#: src/hydrilla/proxy/policies/info_pages_templates/js_fallback_blocked_info.html.jinja:13 +msgid "info.js_fallback_blocked" +msgstr "" +"JavaScript is blocked from executing on this page. This is the default " +"policy." + +#: src/hydrilla/proxy/policies/info_pages_templates/js_rule_allowed_info.html.jinja:13 +msgid "info.js_allowed.html.rule{url}_is_used" +msgstr "" +"JavaScript is allowed to execute on this page. A <a href=\"{url}\" " +"target=\"_blank\">script allowing rule</a> has been explicitly configured" +" by the user." + +#: src/hydrilla/proxy/policies/info_pages_templates/js_rule_blocked_info.html.jinja:13 +msgid "info.js_blocked.html.rule{url}_is_used" +msgstr "" +"JavaScript is blocked from executing on this page. A <a href=\"{url}\" " +"target=\"_blank\">script blocking rule</a> has been explicitly configured" +" by the user." + +#: src/hydrilla/proxy/policies/info_pages_templates/js_rule_info.html.jinja:32 +msgid "info.rule.matched_pattern_label" +msgstr "Matched rule pattern" + +#: src/hydrilla/proxy/policies/info_pages_templates/payload_info.html.jinja:36 +msgid "info.payload.html.package_{identifier}{url}_is_used" +msgstr "" +"This page is handled by package with the name '<a href=\"{url}\" " +"target=\"_blank\">{identifier}</a>'. The package has been explicitly " +"configured by the user and can make changes to the page." + +#: src/hydrilla/proxy/policies/info_pages_templates/payload_info.html.jinja:43 +msgid "info.payload.matched_pattern_label" +msgstr "Matched package pattern" + +#: src/hydrilla/proxy/policies/info_pages_templates/special_page_info.html.jinja:13 +msgid "info.special_page" +msgstr "This is a special page. It is exempt from the usual Haketilo policies." + +#: src/hydrilla/proxy/policies/payload_resource.py:249 msgid "api.file_not_found" msgstr "Requested file could not be found." -#: src/hydrilla/proxy/policies/payload_resource.py:368 +#: src/hydrilla/proxy/policies/payload_resource.py:365 msgid "api.resource_not_enabled_for_access" msgstr "Requested resource is not enabled for access." -#: src/hydrilla/proxy/state_impl/concrete_state.py:89 +#: src/hydrilla/proxy/state_impl/concrete_state.py:111 msgid "err.proxy.unknown_db_schema" msgstr "" "Haketilo's data files have been altered, possibly by a newer version of " "Haketilo." -#: src/hydrilla/proxy/state_impl/concrete_state.py:93 +#: src/hydrilla/proxy/state_impl/concrete_state.py:133 msgid "err.proxy.no_sqlite_foreign_keys" msgstr "" "This installation of Haketilo uses an SQLite version which does not " "support foreign key constraints." -#: src/hydrilla/proxy/state_impl/concrete_state.py:227 +#: src/hydrilla/proxy/state_impl/concrete_state.py:283 msgid "warn.proxy.failed_to_register_landing_page_at_{}" msgstr "Failed to register landing page at \"{}\"." -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:99 -msgid "web_ui.base.title.haketilo_proxy" -msgstr "Haketilo" - #: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:82 msgid "web_ui.base.nav.home" msgstr "Home" @@ -558,6 +645,13 @@ msgstr "" "Haketilo currently does not make it possible to open its popup window on " "pages where payload is used." +#: src/hydrilla/proxy/web_ui/templates/index.html.jinja:304 +msgid "web_ui.home.popup_can_be_opened_by" +msgstr "" +"When enabled on given page, popup dialog can be opened by typing big " +"letters \"HKT\". It can be subsequently closed by clicking anywhere on " +"the dark area around it." + #: src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja:44 #: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:30 #: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:35 @@ -1258,6 +1352,10 @@ msgstr "Actions" msgid "web_ui.rules.single.remove_button" msgstr "Remove rule" +#: src/hydrilla/proxy/web_ui/templates/web_ui_base.html.jinja:20 +msgid "web_ui.base.title.haketilo_proxy" +msgstr "Haketilo" + #: src/hydrilla/server/malcontent.py:77 msgid "err.server.malcontent_path_not_dir_{}" msgstr "Provided 'malcontent_dir' path does not name a directory: {}" diff --git a/src/hydrilla/proxy/addon.py b/src/hydrilla/proxy/addon.py index 68b3cd5..98894e7 100644 --- a/src/hydrilla/proxy/addon.py +++ b/src/hydrilla/proxy/addon.py @@ -32,11 +32,12 @@ from addon script. import sys import re +import threading +import secrets import typing as t import dataclasses as dc import traceback as tb -from threading import Lock from pathlib import Path from contextlib import contextmanager from urllib.parse import urlparse @@ -139,6 +140,8 @@ class PassedOptions: self.haketilo_launch_browser is not None) +Lock = threading.Lock + @dc.dataclass class HaketiloAddon: initial_options: PassedOptions = PassedOptions() @@ -343,7 +346,15 @@ class HaketiloAddon: with self.http_safe_event_handling(flow): handling = self.get_flow_handling(flow) - result = handling.policy.consume_response(handling.full_http_info) + new_nonce = secrets.token_urlsafe(8) + setattr(policies.response_work_data, 'nonce', new_nonce) + + try: + http_info = handling.full_http_info + result = handling.policy.consume_response(http_info) + finally: + delattr(policies.response_work_data, 'nonce') + if result is not None: headers_bin = result.headers.items_bin() diff --git a/src/hydrilla/proxy/http_messages.py b/src/hydrilla/proxy/http_messages.py index 718022f..74f1f02 100644 --- a/src/hydrilla/proxy/http_messages.py +++ b/src/hydrilla/proxy/http_messages.py @@ -30,6 +30,7 @@ """ import re +import cgi import dataclasses as dc import typing as t import sys @@ -120,42 +121,18 @@ def make_parsed_url(url: t.Union[str, url_patterns.ParsedUrl]) \ return url_patterns.parse_url(url) if isinstance(url, str) else url -# For details of 'Content-Type' header's structure, see: -# https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.1 -content_type_reg = re.compile(r''' -^ -(?P<mime>[\w-]+/[\w-]+) -\s* -(?: - ; - (?:[^;]*;)* # match possible parameter other than "charset" -) -\s* -charset= # no whitespace allowed in parameter as per RFC -(?P<encoding> - [\w-]+ - | - "[\w-]+" # quotes are optional per RFC -) -(?:;[^;]+)* # match possible parameter other than "charset" -$ # forbid possible dangling characters after closing '"' -''', re.VERBOSE | re.IGNORECASE) - @dc.dataclass(frozen=True) class HasHeadersMixin: headers: IHeaders def deduce_content_type(self) -> tuple[t.Optional[str], t.Optional[str]]: - content_type = self.headers.get('content-type') - if content_type is None: - return (None, None) - - match = content_type_reg.match(content_type) - if match is None: + content_type_header = self.headers.get('content-type') + if content_type_header is None: return (None, None) - mime, encoding = match.group('mime'), match.group('encoding') + mime, options = cgi.parse_header(content_type_header) + encoding = options.get('charset') if encoding is not None: encoding = encoding.lower() diff --git a/src/hydrilla/proxy/policies/__init__.py b/src/hydrilla/proxy/policies/__init__.py index 2276177..93c3d4f 100644 --- a/src/hydrilla/proxy/policies/__init__.py +++ b/src/hydrilla/proxy/policies/__init__.py @@ -4,7 +4,7 @@ # # Available under the terms of Creative Commons Zero v1.0 Universal. -from .base import * +from .base import PolicyPriority, Policy, PolicyFactory, response_work_data from .payload import PayloadPolicyFactory diff --git a/src/hydrilla/proxy/policies/base.py b/src/hydrilla/proxy/policies/base.py index 7ce5105..1626b5c 100644 --- a/src/hydrilla/proxy/policies/base.py +++ b/src/hydrilla/proxy/policies/base.py @@ -31,10 +31,10 @@ import enum import re +import threading import dataclasses as dc import typing as t -from threading import Lock from abc import ABC, abstractmethod from hashlib import sha256 from base64 import b64encode @@ -43,24 +43,60 @@ import jinja2 from immutables import Map -from ... url_patterns import ParsedUrl +from ...translations import translation as make_translation +from ... import url_patterns +from ... import common_jinja_templates from .. import state from .. import http_messages from .. import csp -loader = jinja2.PackageLoader(__package__, package_path='injectable_scripts') -jinja_env = jinja2.Environment( - loader = loader, +_info_loader = jinja2.PackageLoader( + __package__, + package_path = 'info_pages_templates' +) +_combined_loader = common_jinja_templates.combine_with_loaders([_info_loader]) +_jinja_info_env = jinja2.Environment( + loader = _combined_loader, + autoescape = jinja2.select_autoescape(['html.jinja']), + lstrip_blocks = True, + extensions = ['jinja2.ext.i18n', 'jinja2.ext.do'] +) +_jinja_info_env.install_gettext_translations(make_translation()) # type: ignore +_jinja_info_env.globals['url_patterns'] = url_patterns +_jinja_info_lock = threading.Lock() + +def get_info_template(template_file_name: str) -> jinja2.Template: + with _jinja_info_lock: + return _jinja_info_env.get_template(template_file_name) + + +_jinja_script_loader = jinja2.PackageLoader( + __package__, + package_path = 'injectable_scripts' +) +_jinja_script_env = jinja2.Environment( + loader = _jinja_script_loader, + autoescape = False, lstrip_blocks = True, - autoescape = False + extensions = ['jinja2.ext.do'] ) -jinja_lock = Lock() +_jinja_script_lock = threading.Lock() +def get_script_template(template_file_name: str) -> jinja2.Template: + with _jinja_script_lock: + return _jinja_script_env.get_template(template_file_name) -popup_script = jinja_env.get_template('popup.js.jinja').render() -popup_script_sha256_bytes = sha256(popup_script.encode()).digest() -popup_script_sha256_b64 = b64encode(popup_script_sha256_bytes).decode() + +response_work_data = threading.local() + +def response_nonce() -> str: + """ + When called multiple times during consume_response(), each time returns the + same unpredictable string unique to this response. The string is used as a + nonce for script elements. + """ + return response_work_data.nonce class PolicyPriority(int, enum.Enum): @@ -140,7 +176,9 @@ class Policy(ABC): -> t.Mapping[str, t.Sequence[str]]: if (self.current_popup_settings.popup_enabled and http_info.is_likely_a_page): - return {'script-src': [f"'sha256-{popup_script_sha256_b64}'"]} + nonce_source = f"'nonce-{response_nonce()}'" + directives = ('script-src', 'style-src', 'frame-src') + return dict((directive, [nonce_source]) for directive in directives) else: return Map() @@ -167,8 +205,26 @@ class Policy(ABC): ) -> t.Union[str, bytes]: popup_settings = self.current_popup_settings - if (popup_settings.popup_enabled and - http_info.is_likely_a_page): + if popup_settings.popup_enabled: + nonce = response_nonce() + + popup_page = self.make_info_page(http_info) + if popup_page is None: + template = get_info_template('special_page_info.html.jinja') + popup_page = template.render( + url = http_info.request_info.url.orig_url + ) + + template = get_script_template('popup.js.jinja') + popup_script = template.render( + popup_page_b64 = b64encode(popup_page.encode()).decode(), + nonce_b64 = b64encode(nonce.encode()).decode(), + # TODO: add an option to configure popup style in the web UI. + # Then start passing the real style value. + #popup_style = popup_settings.style.value + popup_style = 'D' + ) + if encoding is None: encoding = 'utf-8' @@ -180,16 +236,15 @@ class Policy(ABC): dotype_decl = body[0:doctype_decl_len] doc_rest = body[doctype_decl_len:] - return f'{dotype_decl}<script>{popup_script}</script>{doc_rest}' + script_tag = f'<script nonce="{nonce}">{popup_script}</script>' + + return dotype_decl + script_tag + doc_rest else: return http_info.response_info.body def _modify_response_body(self, http_info: http_messages.FullHTTPInfo) \ -> bytes: - if not http_messages.is_likely_a_page( - request_info = http_info.request_info, - response_info = http_info.response_info - ): + if not http_info.is_likely_a_page: return http_info.response_info.body data = http_info.response_info.body @@ -252,6 +307,10 @@ class Policy(ABC): body = new_body ) + def make_info_page(self, http_info: http_messages.FullHTTPInfo) \ + -> t.Optional[str]: + return None + @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class PolicyFactory(ABC): diff --git a/src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja new file mode 100644 index 0000000..0785039 --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/info_base.html.jinja @@ -0,0 +1,89 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy info page with information about other page - base template. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> + +Dual licensed under +* GNU General Public License v3.0 or later and +* Creative Commons Attribution Share Alike 4.0 International. + +You can choose to use either of these licenses or both. + + +I, Wojtek Kosior, thereby promise not to sue for violation of this +file's licenses. Although I request that you do not make use of this +code in a proprietary work, I am not going to enforce this in court. +#} +{% extends "base.html.jinja" %} + +{% block style %} + {{ super() }} + + #main { + padding: 0 10px; + } +{% endblock %} + +{% block head %} + {{ super() }} + + <title>{{ _('info.base.title') }}</title> +{% endblock head %} + +{% block main %} + <h3> + {{ _('info.base.heading.page_info') }} + </h3> + + {{ label(_('info.base.page_url_label')) }} + + <p> + {{ url }} + </p> + + <div class="horizontal-separator"></div> + + {{ label(_('info.base.page_policy_label')) }} + + <p class="has-colored-links"> + {% block site_policy required %}{% endblock %} + </p> + + {% block main_rest %} + {% endblock %} + + {% block options %} + <div class="horizontal-separator"></div> + + {{ label(_('info.base.more_config_options_label')) }} + + {% set site_pattern = url_patterns.pattern_for_domain(url)|urlencode %} + {% set page_pattern = url_patterns.normalize_pattern(url)|urlencode %} + + {% + for pattern, hkt_url_fmt, but_text in [ + (site_pattern, 'https://hkt.mitm.it/rules/viewbypattern?pattern={}', + _('info.base.this_site_script_blocking_button')), + + (site_pattern, 'https://hkt.mitm.it/import?pattern={}', + _('info.base.this_site_payload_button')), + + (page_pattern, 'https://hkt.mitm.it/rules/viewbypattern?pattern={}', + _('info.base.this_page_script_blocking_button')), + + (page_pattern, 'https://hkt.mitm.it/import?pattern={}', + _('info.base.this_page_payload_button')) + ] + %} + {% set hkt_url = hkt_url_fmt.format(pattern) %} + {% set classes = "green-button block-with-bottom-margin" %} + <a class="{{classes}}" href="{{ hkt_url }}" target="_blank"> + {{ but_text }} + </a> + {% endfor %} + {% endblock options %} +{% endblock main %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/js_error_blocked_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/js_error_blocked_info.html.jinja new file mode 100644 index 0000000..c76d42b --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/js_error_blocked_info.html.jinja @@ -0,0 +1,22 @@ +{# +SPDX-License-Identifier: CC0-1.0 + +Proxy info page with information about page with JS blocked after an error. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +#} +{% extends "info_base.html.jinja" %} + +{% block site_policy %} + {{ _('info.js_error_blocked.html')|safe }} +{% endblock %} + +{% block main_rest %} + {% if settings.advanced_user %} + {{ label(_('info.js_error_blocked.stacktrace')) }} + + {{ verbatim(traceback) }} + {% endif %} +{% endblock %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/js_fallback_allowed_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/js_fallback_allowed_info.html.jinja new file mode 100644 index 0000000..71f3151 --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/js_fallback_allowed_info.html.jinja @@ -0,0 +1,14 @@ +{# +SPDX-License-Identifier: CC0-1.0 + +Proxy info page with information about page with JS allowed by default policy. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +#} +{% extends "info_base.html.jinja" %} + +{% block site_policy %} + {{ _('info.js_fallback_allowed') }} +{% endblock %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/js_fallback_blocked_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/js_fallback_blocked_info.html.jinja new file mode 100644 index 0000000..3e8719a --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/js_fallback_blocked_info.html.jinja @@ -0,0 +1,14 @@ +{# +SPDX-License-Identifier: CC0-1.0 + +Proxy info page with information about page with JS blocked by default policy. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +#} +{% extends "info_base.html.jinja" %} + +{% block site_policy %} + {{ _('info.js_fallback_blocked') }} +{% endblock %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/js_rule_allowed_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/js_rule_allowed_info.html.jinja new file mode 100644 index 0000000..fe74602 --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/js_rule_allowed_info.html.jinja @@ -0,0 +1,14 @@ +{# +SPDX-License-Identifier: CC0-1.0 + +Proxy info page with information about page with JS allowed by a rule. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +#} +{% extends "js_rule_info.html.jinja" %} + +{% block site_policy %} + {{ format_html_with_rule_url(_('info.js_allowed.html.rule{url}_is_used')) }} +{% endblock %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/js_rule_blocked_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/js_rule_blocked_info.html.jinja new file mode 100644 index 0000000..e84d371 --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/js_rule_blocked_info.html.jinja @@ -0,0 +1,14 @@ +{# +SPDX-License-Identifier: CC0-1.0 + +Proxy info page with information about page with JS blocked by a rule. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +#} +{% extends "js_rule_info.html.jinja" %} + +{% block site_policy %} + {{ format_html_with_rule_url(_('info.js_blocked.html.rule{url}_is_used')) }} +{% endblock %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/js_rule_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/js_rule_info.html.jinja new file mode 100644 index 0000000..b808827 --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/js_rule_info.html.jinja @@ -0,0 +1,37 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy info page with information about page with JS blocked or allowed by a +rule - template for firther extending. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> + +Dual licensed under +* GNU General Public License v3.0 or later and +* Creative Commons Attribution Share Alike 4.0 International. + +You can choose to use either of these licenses or both. + + +I, Wojtek Kosior, thereby promise not to sue for violation of this +file's licenses. Although I request that you do not make use of this +code in a proprietary work, I am not going to enforce this in court. +#} +{% extends "info_base.html.jinja" %} + +{% macro format_html_with_rule_url(msg_fmt) %} + {% set url_fmt = 'https://hkt.mitm.it/rules/viewbypattern?pattern={pattern}' %} + {{ msg_fmt.format(url=url_fmt.format(pattern=pattern)|e)|safe }} +{% endmacro %} + +{% block main_rest %} + <div class="horizontal-separator"></div> + + {{ label(_('info.rule.matched_pattern_label')) }} + + <p> + {{ pattern }} + </p> +{% endblock %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/payload_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/payload_info.html.jinja new file mode 100644 index 0000000..a71ca25 --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/payload_info.html.jinja @@ -0,0 +1,48 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy info page with information about page with payload. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> + +Dual licensed under +* GNU General Public License v3.0 or later and +* Creative Commons Attribution Share Alike 4.0 International. + +You can choose to use either of these licenses or both. + + +I, Wojtek Kosior, thereby promise not to sue for violation of this +file's licenses. Although I request that you do not make use of this +code in a proprietary work, I am not going to enforce this in court. +#} +{% extends "info_base.html.jinja" %} + +{% macro format_html_with_package_identifier_and_url(msg_fmt) %} + {% set package_identifier = payload_data.mapping_identifier|e %} + {% set url_fmt = 'https://hkt.mitm.it/package/viewbypayload/{payload_id}/{package_identifier}' %} + {% + set url = url_fmt.format( + payload_id = payload_data.ref.id, + package_identifier = package_identifier + ) + %} + {{ msg_fmt.format(identifier=package_identifier, url=url|e)|safe }} +{% endmacro %} + +{% block site_policy %} + {% set fmt = _('info.payload.html.package_{identifier}{url}_is_used') %} + {{ format_html_with_package_identifier_and_url(fmt) }} +{% endblock %} + +{% block main_rest %} + <div class="horizontal-separator"></div> + + {{ label(_('info.payload.matched_pattern_label')) }} + + <p> + {{ payload_data.pattern }} + </p> +{% endblock %} diff --git a/src/hydrilla/proxy/policies/info_pages_templates/special_page_info.html.jinja b/src/hydrilla/proxy/policies/info_pages_templates/special_page_info.html.jinja new file mode 100644 index 0000000..2f7a9d3 --- /dev/null +++ b/src/hydrilla/proxy/policies/info_pages_templates/special_page_info.html.jinja @@ -0,0 +1,17 @@ +{# +SPDX-License-Identifier: CC0-1.0 + +Proxy info page with information about page handled by special policy. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +#} +{% extends "info_base.html.jinja" %} + +{% block site_policy %} + {{ _('info.special_page') }} +{% endblock %} + +{% block options %} +{% endblock %} diff --git a/src/hydrilla/proxy/policies/injectable_scripts/popup.js.jinja b/src/hydrilla/proxy/policies/injectable_scripts/popup.js.jinja index 653b7df..593673b 100644 --- a/src/hydrilla/proxy/policies/injectable_scripts/popup.js.jinja +++ b/src/hydrilla/proxy/policies/injectable_scripts/popup.js.jinja @@ -45,6 +45,177 @@ code in a proprietary program, I am not going to enforce this in court. #} (function(){ - console.log('TODO: make Haketilo able to actually display a popup') document.currentScript.remove(); + + /* + * To slightly decrease the chance of accidental popup breakage we snapshot + * methods that other code might redefine. + */ + function get_setter(obj, name) { + return Object.getOwnPropertyDescriptor(obj, name).set; + } + + const ElementPrototype = [0, 0, 0] + .reduce(n => Object.getPrototypeOf(n), document.documentElement); + + const prepend_fun = ElementPrototype.prepend; + const setattr_fun = ElementPrototype.setAttribute; + const remove_fun = ElementPrototype.remove; + const setinner_fun = get_setter(ElementPrototype, "innerHTML"); + const open_fun = window.open; + + const shortcut = "HKT"; + const nonce = atob("{{nonce_b64}}"); + const popup_style = "{{popup_style}}"; + const popup_html = atob("{{popup_page_b64}}"); + const popup_container = document.createElement("div"); + const popup_frame = document.createElement("iframe"); + + function make_style(styles_obj) { + return Object.entries(styles_obj) + .map(([key, val]) => `${key}: ${val} !important`) + .join(';'); + } + + const frame_style = make_style({ + "position": "absolute", + "left": "50%", + "top": "50%", + "transform": "translate(-50%, -50%)", + "display": "block", + "visibility": "visible", + "min-width": "initial", + "width": "600px", + "max-width": "calc(100vw - 20px)", + "min-height": "initial", + "height": "700px", + "max-height": "calc(100vh - 20px)", + "background-color": "#fff", + "opacity": "100%", + "margin": 0, + "padding": 0, + "border": "none", + "border-radius": "5px" + }); + + const container_style = make_style({ + "position": "fixed", + "left": "0", + "top": "0", + "transform": "initial", + "z-index": 2147483647, + "display": "block", + "visibility": "visible", + "min-width": "100vw", + "max-width": "100vw", + "min-height": "100vh", + "max-height": "100vh", + "background-color": "#0008", + "opacity": "100%", + "margin": 0, + "padding": 0, + "border": "none", + "border-radius": 0 + }); + + const popup_blob_opts = {type: "text/html;charset=UTF-8"}; + const popup_blob = new Blob([popup_html], popup_blob_opts); + const popup_url = URL.createObjectURL(popup_blob); + + function show_popup_dialog() { + setattr_fun.call(popup_frame, "srcdoc", popup_html); + setattr_fun.call(popup_frame, "nonce", nonce); + setattr_fun.call(popup_frame, "style", frame_style); + + setattr_fun.call(popup_container, "style", container_style); + setinner_fun.call(popup_container, ""); + prepend_fun.call(popup_container, popup_frame); + + prepend_fun.call(document.body, popup_container); + } + + let popup_newtab_wanted = false; + + function show_popup_newtab() { + /* + * We cannot open popup directly here because browsers block window + * creation attempts from "keypress" event handlers. Instead, we set a + * flag to have "click" event handler open the popup. + */ + popup_newtab_wanted = true; + console.info(`You typed "${shortcut}". Please click anywhere on the page to show Haketilo page information.`); + } + + function show_popup() { + if (popup_style === "T") { + show_popup_newtab(); + } else { + /* popup_syle === "D" */ + show_popup_dialog(); + } + } + + function hide_popup_dialog() { + remove_fun.call(popup_container); + } + + let letters_matched = 0; + + function matches_previous(letter) { + return letters_matched > 0 && letter === shortcut[letters_matched - 1]; + } + + function match_letter(letter) { + if (letter !== shortcut[letters_matched] && !matches_previous(letter)) + letters_matched = 0; + + if (letter === shortcut[letters_matched]) { + if (++letters_matched === shortcut.length) { + letters_matched = 0; + return true; + } + } + + return false; + } + + function consume_keypress(event) { + if (!event.isTrusted) + return; + + if (match_letter(event.key)) + show_popup(); + } + + function cancel_event(event) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + + function consume_click(event) { + if (!event.isTrusted) + return; + + if (popup_style === "T") { + if (popup_newtab_wanted) { + popup_newtab_wanted = false; + cancel_event(event); + window.open( + popup_url, + "_blank", + "popup,width=600px,height=700px" + ); + } + } else { + /* popup_syle === "D" */ + if (event.target === popup_container) { + hide_popup_dialog(); + cancel_event(event); + } + } + } + + document.addEventListener("keypress", consume_keypress, {capture: true}); + document.addEventListener("click", consume_click, {capture: true}); })(); diff --git a/src/hydrilla/proxy/policies/misc.py b/src/hydrilla/proxy/policies/misc.py index 350f3dc..0ff4596 100644 --- a/src/hydrilla/proxy/policies/misc.py +++ b/src/hydrilla/proxy/policies/misc.py @@ -29,9 +29,10 @@ ..... """ +import enum +import traceback as tb import dataclasses as dc import typing as t -import enum from abc import ABC, abstractmethod @@ -44,16 +45,39 @@ from .rule import AllowPolicy, BlockPolicy class FallbackAllowPolicy(AllowPolicy): priority = base.PolicyPriority._ONE + def make_info_page(self, http_info: http_messages.FullHTTPInfo) \ + -> t.Optional[str]: + template = base.get_info_template('js_fallback_allowed_info.html.jinja') + return template.render(url=http_info.request_info.url.orig_url) + class FallbackBlockPolicy(BlockPolicy): priority = base.PolicyPriority._ONE + def make_info_page(self, http_info: http_messages.FullHTTPInfo) \ + -> t.Optional[str]: + template = base.get_info_template('js_fallback_blocked_info.html.jinja') + return template.render(url=http_info.request_info.url.orig_url) + @dc.dataclass(frozen=True) class ErrorBlockPolicy(BlockPolicy): - """....""" error: Exception + @property + def traceback(self) -> str: + lines = tb.format_exception(None, self.error, self.error.__traceback__) + return ''.join(lines) + + def make_info_page(self, http_info: http_messages.FullHTTPInfo) \ + -> t.Optional[str]: + template = base.get_info_template('js_error_blocked_info.html.jinja') + return template.render( + url = http_info.request_info.url.orig_url, + settings = self.haketilo_settings, + traceback = self.traceback + ) + class MitmItPagePolicy(base.Policy): """ diff --git a/src/hydrilla/proxy/policies/payload.py b/src/hydrilla/proxy/policies/payload.py index 3252c6a..55851cc 100644 --- a/src/hydrilla/proxy/policies/payload.py +++ b/src/hydrilla/proxy/policies/payload.py @@ -173,6 +173,13 @@ class PayloadInjectPolicy(PayloadAwarePolicy): return soup.decode() + def make_info_page(self, http_info: http_messages.FullHTTPInfo) \ + -> t.Optional[str]: + return base.get_info_template('payload_info.html.jinja').render( + url = http_info.request_info.url.orig_url, + payload_data = self.payload_data + ) + class _PayloadHasProblemsError(HaketiloException): pass diff --git a/src/hydrilla/proxy/policies/payload_resource.py b/src/hydrilla/proxy/policies/payload_resource.py index 38cfd21..0d73242 100644 --- a/src/hydrilla/proxy/policies/payload_resource.py +++ b/src/hydrilla/proxy/policies/payload_resource.py @@ -261,18 +261,16 @@ class PayloadResourcePolicy(PayloadAwarePolicy): request_info: http_messages.RequestInfo ) -> MessageInfo: if path[0] == 'page_init_script.js': - with base.jinja_lock: - template = base.jinja_env.get_template( - 'page_init_script.js.jinja' - ) - token = self.payload_data.unique_token - base_url = self._assets_base_url(request_info.url) - ver_str = json.dumps(haketilo_version) - js = template.render( - unique_token_encoded = encode_string_for_js(token), - assets_base_url_encoded = encode_string_for_js(base_url), - haketilo_version = encode_string_for_js(ver_str) - ) + template = base.get_script_template('page_init_script.js.jinja') + + token = self.payload_data.unique_token + base_url = self._assets_base_url(request_info.url) + ver_str = json.dumps(haketilo_version) + js = template.render( + unique_token_encoded = encode_string_for_js(token), + assets_base_url_encoded = encode_string_for_js(base_url), + haketilo_version = encode_string_for_js(ver_str) + ) return http_messages.ResponseInfo.make( status_code = 200, diff --git a/src/hydrilla/proxy/policies/rule.py b/src/hydrilla/proxy/policies/rule.py index 8c5e69b..1f39295 100644 --- a/src/hydrilla/proxy/policies/rule.py +++ b/src/hydrilla/proxy/policies/rule.py @@ -67,11 +67,25 @@ class BlockPolicy(base.Policy): class RuleAllowPolicy(AllowPolicy): pattern: ParsedPattern + def make_info_page(self, http_info: http_messages.FullHTTPInfo) \ + -> t.Optional[str]: + return base.get_info_template('js_rule_allowed_info.html.jinja').render( + url = http_info.request_info.url.orig_url, + pattern = self.pattern.orig_url + ) + @dc.dataclass(frozen=True) class RuleBlockPolicy(BlockPolicy): pattern: ParsedPattern + def make_info_page(self, http_info: http_messages.FullHTTPInfo) \ + -> t.Optional[str]: + return base.get_info_template('js_rule_blocked_info.html.jinja').render( + url = http_info.request_info.url.orig_url, + pattern = self.pattern.orig_url + ) + @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class RulePolicyFactory(base.PolicyFactory): diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py index d21a392..72eaaa0 100644 --- a/src/hydrilla/proxy/state.py +++ b/src/hydrilla/proxy/state.py @@ -171,6 +171,10 @@ class RuleStore(Store[RuleRef]): def add(self, pattern: str, allow: bool) -> RuleRef: ... + @abstractmethod + def get_by_pattern(self, pattern: str) -> RuleRef: + ... + class RepoNameInvalid(HaketiloException): pass @@ -445,6 +449,8 @@ class PayloadData: explicitly_enabled: bool unique_token: str + mapping_identifier: str + pattern: str pattern_path_segments: tuple[str, ...] eval_allowed: bool cors_bypass_allowed: bool diff --git a/src/hydrilla/proxy/state_impl/concrete_state.py b/src/hydrilla/proxy/state_impl/concrete_state.py index 5df7c34..a5c547f 100644 --- a/src/hydrilla/proxy/state_impl/concrete_state.py +++ b/src/hydrilla/proxy/state_impl/concrete_state.py @@ -367,6 +367,8 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): ref = payload_ref, explicitly_enabled = enabled_status == 'E', unique_token = token, + mapping_identifier = identifier, + pattern = pattern, pattern_path_segments = pattern_path_segments, eval_allowed = eval_allowed, cors_bypass_allowed = cors_bypass_allowed, diff --git a/src/hydrilla/proxy/state_impl/rules.py b/src/hydrilla/proxy/state_impl/rules.py index 2fed2c1..1761b04 100644 --- a/src/hydrilla/proxy/state_impl/rules.py +++ b/src/hydrilla/proxy/state_impl/rules.py @@ -148,6 +148,8 @@ class ConcreteRuleStore(st.RuleStore): (rule_id,), = cursor.fetchall() + self.state.rebuild_structures(payloads=False) + return ConcreteRuleRef(str(rule_id), self.state) def get_display_infos(self, allow: t.Optional[bool] = None) \ @@ -176,3 +178,19 @@ class ConcreteRuleStore(st.RuleStore): result.append(st.RuleDisplayInfo(ref, pattern, allow_scripts)) return result + + def get_by_pattern(self, pattern: str) -> st.RuleRef: + with self.state.cursor() as cursor: + cursor.execute( + 'SELECT rule_id FROM rules WHERE pattern = ?;', + (url_patterns.normalize_pattern(pattern),) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (rule_id,), = rows + + return ConcreteRuleRef(str(rule_id), self.state) diff --git a/src/hydrilla/proxy/web_ui/items.py b/src/hydrilla/proxy/web_ui/items.py index 808fb6d..d0f0f2e 100644 --- a/src/hydrilla/proxy/web_ui/items.py +++ b/src/hydrilla/proxy/web_ui/items.py @@ -390,8 +390,8 @@ def show_required_mapping( return flask.redirect(url) -@bp.route('/package/viewpayload/<string:item_version_id>/<string:pattern>/<string:lib_identifier>') -def show_payload(item_version_id: str, pattern: str, lib_identifier: str) \ +@bp.route('/package/viewlibrary/<string:item_version_id>/<string:pattern>/<string:lib_identifier>') +def show_package_library(item_version_id: str, pattern: str, lib_identifier: str) \ -> werkzeug.Response: state = _app.get_haketilo_state() @@ -406,10 +406,35 @@ def show_payload(item_version_id: str, pattern: str, lib_identifier: str) \ item_version_id = resource_ver_ref.id ) except st.MissingItemError: - resource_ref = \ - state.resource_store().get_by_identifier(lib_identifier) + resource_ref = state.resource_store().get_by_identifier( + lib_identifier + ) url = flask.url_for('.show_library', item_id=resource_ref.id) except st.MissingItemError: flask.abort(404) return flask.redirect(url) + +@bp.route('/package/viewbypayload/<string:payload_id>/<string:package_identifier>') +def show_payload_package(payload_id: str, package_identifier: str) \ + -> werkzeug.Response: + state = _app.get_haketilo_state() + + try: + ref = state.payload_store().get(payload_id) + + try: + mapping_ver_ref = ref.get_display_info().mapping_info.ref + url = flask.url_for( + '.show_package_version', + item_version_id = mapping_ver_ref.id + ) + except st.MissingItemError: + mapping_ref = state.mapping_store().get_by_identifier( + package_identifier + ) + url = flask.url_for('.show_package', item_id=mapping_ref.id) + except st.MissingItemError: + flask.abort(404) + + return flask.redirect(url) diff --git a/src/hydrilla/proxy/web_ui/items_import.py b/src/hydrilla/proxy/web_ui/items_import.py index a5b5f18..f94768f 100644 --- a/src/hydrilla/proxy/web_ui/items_import.py +++ b/src/hydrilla/proxy/web_ui/items_import.py @@ -51,7 +51,13 @@ bp = flask.Blueprint('import', __package__) @bp.route('/import', methods=['GET']) def items_import(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: - html = flask.render_template('import.html.jinja', **errors) + pattern = flask.request.args.get('pattern') + if pattern is None: + extra_args = {} + else: + extra_args = {'pattern': normalize_pattern(pattern)} + + html = flask.render_template('import.html.jinja', **errors, **extra_args) return flask.make_response(html, 200) def items_import_from_file() -> werkzeug.Response: @@ -172,7 +178,6 @@ def item_import_ad_hoc() -> werkzeug.Response: try: builder_args = ['-s', str(source_dir), '-d', str(malcontent_dir)] build.perform(builder_args, standalone_mode=False) - build.perform(['-s', str(source_dir), '-d', '/tmp/haketilodebug'], standalone_mode=False) _app.get_haketilo_state().import_items(malcontent_dir) except: import traceback diff --git a/src/hydrilla/proxy/web_ui/rules.py b/src/hydrilla/proxy/web_ui/rules.py index 56753a3..606d33f 100644 --- a/src/hydrilla/proxy/web_ui/rules.py +++ b/src/hydrilla/proxy/web_ui/rules.py @@ -107,3 +107,16 @@ def alter_rule(rule_id: str) -> werkzeug.Response: flask.abort(404) return flask.redirect(flask.url_for('.show_rule', rule_id=rule_id)) + +@bp.route('/rules/viewbypattern') +def show_pattern_rule() -> werkzeug.Response: + pattern = flask.request.args['pattern'] + + try: + store = _app.get_haketilo_state().rule_store() + rule_ref = store.get_by_pattern(pattern) + except st.MissingItemError: + html = flask.render_template('rules/add.html.jinja', pattern=pattern) + return flask.make_response(html, 200) + + return flask.redirect(flask.url_for('.show_rule', rule_id=rule_ref.id)) diff --git a/src/hydrilla/proxy/web_ui/templates/import.html.jinja b/src/hydrilla/proxy/web_ui/templates/import.html.jinja index 6ec9947..7f3be50 100644 --- a/src/hydrilla/proxy/web_ui/templates/import.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/import.html.jinja @@ -103,7 +103,7 @@ code in a proprietary work, I am not going to enforce this in court. {% if invalid_ad_hoc_patterns is defined %} {{ error_note(_('web_ui.err.invalid_ad_hoc_patterns')) }} {% endif %} - {{ form_field('patterns', height=3) }} + {{ form_field('patterns', height=3, initial_value=pattern|default(none)) }} {{ label(_('web_ui.import.script_text_field_label'), 'script_text') }} {{ form_field('script_text', required=false, height=15) }} diff --git a/src/hydrilla/proxy/web_ui/templates/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/index.html.jinja index 2b49361..ff74369 100644 --- a/src/hydrilla/proxy/web_ui/templates/index.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/index.html.jinja @@ -299,4 +299,8 @@ code in a proprietary work, I am not going to enforce this in court. {{ _('web_ui.home.payloadon_popup_no') }} {% endif %} {% endcall %} + + <p> + {{ _('web_ui.home.popup_can_be_opened_by') }} + </p> {% endblock main %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja index fe816ab..386c0c8 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja @@ -75,7 +75,7 @@ code in a proprietary work, I am not going to enforce this in court. {% set encoded = patterns[0]|urlencode|replace('/', '%2F') %} {% set url = url_for( - '.show_payload', + '.show_package_library', item_version_id = version_display_info.ref.id, pattern = encoded, lib_identifier = lib_identifier diff --git a/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja index 6d21ccd..9e4b869 100644 --- a/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja @@ -24,14 +24,14 @@ code in a proprietary work, I am not going to enforce this in court. {% block main %} <h3>{{ _('web_ui.rules.add.heading') }}</h3> - <form method="POST"> + <form method="POST" action="{{ url_for('.add_rule') }}"> {{ label(_('web_ui.rules.add.pattern_field_label'), 'pattern') }} {% if rule_pattern_invalid is defined %} {{ error_note(_('web_ui.err.rule_pattern_invalid')) }} {% endif %} - {{ form_field('pattern') }} + {{ form_field('pattern', initial_value=pattern|default(none)) }} {{ label(_('web_ui.rules.add.block_or_allow_label'), 'allow') }} diff --git a/src/hydrilla/url_patterns.py b/src/hydrilla/url_patterns.py index 5e62a28..860d456 100644 --- a/src/hydrilla/url_patterns.py +++ b/src/hydrilla/url_patterns.py @@ -242,4 +242,8 @@ def normalize_pattern(url_pattern: str) -> str: return reconstructed +def pattern_for_domain(url: str) -> str: + return normalize_pattern(f'http*://{up.urlparse(url).netloc}/***') + + dummy_url = parse_url('http://dummy.replacement.url') |