From 14eeee3fbc0a839d918149765d2134d05cd14601 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Tue, 18 Oct 2022 17:18:32 +0200 Subject: [proxy] upon Haketilo launch automatically open Haketilo landing page in user's default web browser * The landing page instructs user to configure browser's proxy settings. * It is now possible to choose the IP address to listen on via command line parameter. * The browser launching behavior can be switched off via command line parameter. --- src/hydrilla/locales/en_US/LC_MESSAGES/messages.po | 216 +++++++++++++-------- src/hydrilla/mitmproxy_launcher/launch.py | 11 +- src/hydrilla/proxy/addon.py | 96 +++++++-- src/hydrilla/proxy/policies/__init__.py | 2 +- src/hydrilla/proxy/policies/web_ui.py | 21 +- src/hydrilla/proxy/state.py | 25 +++ src/hydrilla/proxy/state_impl/base.py | 30 +++ src/hydrilla/proxy/state_impl/concrete_state.py | 43 +++- src/hydrilla/proxy/web_ui/__init__.py | 1 + src/hydrilla/proxy/web_ui/_app.py | 13 +- src/hydrilla/proxy/web_ui/root.py | 69 +++++-- .../proxy/web_ui/templates/base.html.jinja | 97 +-------- .../web_ui/templates/hkt_mitm_it_base.html.jinja | 116 +++++++++++ .../proxy/web_ui/templates/import.html.jinja | 2 +- .../proxy/web_ui/templates/index.html.jinja | 2 +- .../web_ui/templates/items/item_view.html.jinja | 2 +- .../web_ui/templates/items/libraries.html.jinja | 2 +- .../web_ui/templates/items/packages.html.jinja | 2 +- .../proxy/web_ui/templates/landing.html.jinja | 49 +++++ .../prompts/auto_install_error.html.jinja | 2 +- .../prompts/package_suggestion.html.jinja | 2 +- .../proxy/web_ui/templates/repos/add.html.jinja | 2 +- .../proxy/web_ui/templates/repos/index.html.jinja | 2 +- .../web_ui/templates/repos/show_single.html.jinja | 2 +- .../proxy/web_ui/templates/rules/add.html.jinja | 2 +- .../proxy/web_ui/templates/rules/index.html.jinja | 2 +- .../web_ui/templates/rules/show_single.html.jinja | 2 +- src/hydrilla/url_patterns.py | 24 ++- 28 files changed, 598 insertions(+), 241 deletions(-) create mode 100644 src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja create mode 100644 src/hydrilla/proxy/web_ui/templates/landing.html.jinja diff --git a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po index b311f8f..eac53fe 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-10 16:44+0200\n" +"POT-Creation-Date: 2022-10-18 17:03+0200\n" "PO-Revision-Date: 2022-02-12 00:00+0000\n" "Last-Translator: Wojtek Kosior \n" "Language: en_US\n" @@ -18,70 +18,70 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.9.0\n" -#: src/hydrilla/builder/build.py:84 src/hydrilla/builder/local_apt.py:123 -#: src/hydrilla/builder/local_apt.py:428 +#: src/hydrilla/builder/build.py:81 src/hydrilla/builder/local_apt.py:120 +#: src/hydrilla/builder/local_apt.py:426 msgid "couldnt_execute_{}_is_it_installed" msgstr "Could not execute '{}'. Is the tool installed and reachable via PATH?" -#: src/hydrilla/builder/build.py:88 src/hydrilla/builder/local_apt.py:127 -#: src/hydrilla/builder/local_apt.py:432 +#: src/hydrilla/builder/build.py:85 src/hydrilla/builder/local_apt.py:124 +#: src/hydrilla/builder/local_apt.py:430 msgid "command_{}_failed" msgstr "The following command finished execution with a non-zero exit status: {}" -#: src/hydrilla/builder/build.py:204 +#: src/hydrilla/builder/build.py:201 msgid "path_contains_double_dot_{}" msgstr "" "Attempt to load '{}' which includes a forbidden parent reference ('..') " "in the path." -#: src/hydrilla/builder/build.py:211 +#: src/hydrilla/builder/build.py:210 msgid "loading_{}_outside_package_dir" msgstr "Attempt to load '{}' which lies outside package source directory." -#: src/hydrilla/builder/build.py:215 +#: src/hydrilla/builder/build.py:214 msgid "loading_reserved_index_json" msgstr "Attempt to load 'index.json' which is a reserved filename." -#: src/hydrilla/builder/build.py:222 +#: src/hydrilla/builder/build.py:221 msgid "referenced_file_{}_missing" msgstr "Referenced file '{}' is missing." -#: src/hydrilla/builder/build.py:413 +#: src/hydrilla/builder/build.py:412 msgid "report_spdx_not_in_copyright_list" msgstr "" "Told to generate 'report.spdx' but 'report.spdx' is not listed among " "copyright files. Refusing to proceed." -#: src/hydrilla/builder/build.py:490 +#: src/hydrilla/builder/build.py:489 msgid "build_package_from_srcdir_to_dstdir" msgstr "" "Build Hydrilla package from `scrdir` and write the resulting files under " "`dstdir`." -#: src/hydrilla/builder/build.py:492 +#: src/hydrilla/builder/build.py:491 msgid "source_directory_to_build_from" msgstr "Source directory to build from." -#: src/hydrilla/builder/build.py:494 +#: src/hydrilla/builder/build.py:493 msgid "path_instead_of_index_json" msgstr "" "Path to file to be processed instead of index.json (if not absolute, " "resolved relative to srcdir)." -#: src/hydrilla/builder/build.py:496 +#: src/hydrilla/builder/build.py:495 msgid "path_instead_for_piggyback_files" msgstr "" "Path to a non-standard directory with foreign packages' archive files to " "use." -#: src/hydrilla/builder/build.py:498 +#: src/hydrilla/builder/build.py:497 msgid "built_package_files_destination" msgstr "Destination directory to write built package files to." -#: src/hydrilla/builder/build.py:500 -#: src/hydrilla/mitmproxy_launcher/launch.py:65 -#: src/hydrilla/server/serve.py:207 src/hydrilla/server/serve.py:225 -#: src/hydrilla/server/serve.py:265 +#: src/hydrilla/builder/build.py:499 +#: src/hydrilla/mitmproxy_launcher/launch.py:64 +#: src/hydrilla/server/serve.py:211 src/hydrilla/server/serve.py:229 +#: src/hydrilla/server/serve.py:269 #, python-format msgid "%(prog)s_%(version)s_license" msgstr "" @@ -92,79 +92,79 @@ msgstr "" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law." -#: src/hydrilla/builder/build.py:501 src/hydrilla/server/serve.py:226 -#: src/hydrilla/server/serve.py:266 +#: src/hydrilla/builder/build.py:500 src/hydrilla/server/serve.py:230 +#: src/hydrilla/server/serve.py:270 msgid "version_printing" msgstr "Print version information and exit." -#: src/hydrilla/builder/common_errors.py:61 +#: src/hydrilla/builder/common_errors.py:58 msgid "STDOUT_OUTPUT_heading" msgstr "## Command's standard output ##" -#: src/hydrilla/builder/common_errors.py:64 +#: src/hydrilla/builder/common_errors.py:61 msgid "STDERR_OUTPUT_heading" msgstr "## Command's standard error output ##" -#: src/hydrilla/builder/local_apt.py:156 +#: src/hydrilla/builder/local_apt.py:153 msgid "distro_{}_unknown" msgstr "Attempt to use an unknown software distribution '{}'." -#: src/hydrilla/builder/local_apt.py:200 +#: src/hydrilla/builder/local_apt.py:197 msgid "couldnt_import_{}_is_it_installed" msgstr "" "Could not import '{}'. Is the module installed and visible to this Python" " instance?" -#: src/hydrilla/builder/local_apt.py:208 +#: src/hydrilla/builder/local_apt.py:205 msgid "gpg_couldnt_recv_key_{}" msgstr "Could not import PGP key '{}'." -#: src/hydrilla/builder/local_apt.py:327 +#: src/hydrilla/builder/local_apt.py:325 msgid "apt_install_output_not_understood" msgstr "The output of an 'apt-get install' command was not understood." -#: src/hydrilla/builder/local_apt.py:353 +#: src/hydrilla/builder/local_apt.py:351 msgid "apt_download_gave_bad_filename_{}" msgstr "The 'apt-get download' command produced a file with unexpected name '{}'." -#: src/hydrilla/builder/piggybacking.py:110 +#: src/hydrilla/builder/piggybacking.py:109 msgid "loading_{}_outside_piggybacked_dir" msgstr "" "Attempt to load '{}' which lies outside piggybacked packages files root " "directory." -#: src/hydrilla/item_infos.py:91 +#: src/hydrilla/item_infos.py:88 msgid "err.item_info.filename_invalid_{}" msgstr "Item definition conatains an illegal path: {}" -#: src/hydrilla/item_infos.py:516 +#: src/hydrilla/item_infos.py:511 #, python-brace-format msgid "uuid_mismatch_{identifier}" msgstr "Two different uuids were specified for item '{identifier}'." -#: src/hydrilla/json_instances.py:87 +#: src/hydrilla/json_instances.py:84 msgid "bad_json_comment_line_{line_num}_char_{char_num}" msgstr "" "JSON document contains an invalid comment at line {line_num}, char " "{char_num}." -#: src/hydrilla/json_instances.py:138 +#: src/hydrilla/json_instances.py:135 msgid "unknown_schema_{}" msgstr "JSON document declares its schema as '{}' which is not a known schema." -#: src/hydrilla/json_instances.py:189 +#: src/hydrilla/json_instances.py:186 msgid "err.util.text_in_{}_not_valid_json" msgstr "Not a valid JSON file: {}" -#: src/hydrilla/json_instances.py:192 +#: src/hydrilla/json_instances.py:189 msgid "err.util.text_not_valid_json" msgstr "Not a valid JSON file." -#: src/hydrilla/json_instances.py:207 +#: src/hydrilla/json_instances.py:204 msgid "no_schema_number_in_instance" msgstr "JSON schema number is missing from a document." -#: src/hydrilla/mitmproxy_launcher/launch.py:58 +#: src/hydrilla/mitmproxy_launcher/launch.py:53 msgid "cli_help.haketilo" msgstr "" "Run Haketilo proxy.\n" @@ -172,76 +172,96 @@ msgstr "" "This command starts Haketilo as a local HTTP proxy which a web browser " "can then use." -#: src/hydrilla/mitmproxy_launcher/launch.py:60 +#: src/hydrilla/mitmproxy_launcher/launch.py:55 +msgid "cli_opt.haketilo.listen_host" +msgstr "IP address port number the proxy should listen on." + +#: src/hydrilla/mitmproxy_launcher/launch.py:57 msgid "cli_opt.haketilo.port" -msgstr "TCP port number the proxy should listen on (1-65535)." +msgstr "TCP port number the proxy should listen on." + +#: src/hydrilla/mitmproxy_launcher/launch.py:59 +msgid "cli_opt.haketilo.launch_browser" +msgstr "" +"Whether Haketilo should try to open its landing page in your default " +"browser. Defaults to yes ('-L')." -#: src/hydrilla/mitmproxy_launcher/launch.py:63 +#: src/hydrilla/mitmproxy_launcher/launch.py:62 msgid "cli_opt.haketilo.dir" msgstr "Data directory for Haketilo to use." -#: src/hydrilla/mitmproxy_launcher/launch.py:66 +#: src/hydrilla/mitmproxy_launcher/launch.py:65 msgid "cli_opt.haketilo.version" msgstr "Print version information and exit" -#: src/hydrilla/proxy/addon.py:128 -msgid "haketilo_dir_already_configured" +#: src/hydrilla/proxy/addon.py:167 +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:201 +msgid "warn.proxy.couldnt_launch_browser" msgstr "" -"Attempt was made to configure Haketilo Mitmproxy addon with data " -"directory path but it has already been configured." +"Failed to open a URL in a web browser. Do you have a default web browser " +"configured?" -#: src/hydrilla/proxy/addon.py:183 +#: src/hydrilla/proxy/addon.py:245 msgid "err.proxy.unknown_error_{}_try_again" msgstr "" "Haketilo experienced an error. Try again.\n" "\n" "{}" -#: src/hydrilla/proxy/policies/payload_resource.py:264 +#: src/hydrilla/proxy/policies/payload_resource.py:261 msgid "api.file_not_found" msgstr "Requested file could not be found." -#: src/hydrilla/proxy/policies/payload_resource.py:387 +#: src/hydrilla/proxy/policies/payload_resource.py:384 msgid "api.resource_not_enabled_for_access" msgstr "Requested resource is not enabled for access." -#: src/hydrilla/proxy/state_impl/concrete_state.py:92 +#: src/hydrilla/proxy/state_impl/concrete_state.py:89 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:96 +#: src/hydrilla/proxy/state_impl/concrete_state.py:93 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/web_ui/templates/base.html.jinja:101 +#: src/hydrilla/proxy/state_impl/concrete_state.py:218 +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/base.html.jinja:284 +#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:82 msgid "web_ui.base.nav.home" msgstr "Home" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:285 +#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:83 msgid "web_ui.base.nav.rules" msgstr "Script blocking" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:286 +#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:84 msgid "web_ui.base.nav.packages" msgstr "Packages" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:287 +#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:85 msgid "web_ui.base.nav.libraries" msgstr "Libraries" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:288 +#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:86 msgid "web_ui.base.nav.repos" msgstr "Repositories" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:289 +#: src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja:87 msgid "web_ui.base.nav.import" msgstr "Import" @@ -844,6 +864,36 @@ msgstr "Available packages" msgid "web_ui.packages.enabled_version_{}" msgstr "enabled version {}" +#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:23 +msgid "web_ui.landing.title" +msgstr "Landing page" + +#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:27 +msgid "web_ui.landing.heading.haketilo_is_running" +msgstr "Haketilo is running" + +#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:31 +msgid "web_ui.landing.web_ui.landing.what_to_do_1" +msgstr "" +"In order to access web pages through Haketilo, make sure your browser is " +"configured to use it as a proxy for both HTTP and HTTPs. Please use the " +"following values." + +#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:34 +msgid "web_ui.landing.host_label" +msgstr "Address" + +#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:40 +msgid "web_ui.landing.port_label" +msgstr "Port" + +#: src/hydrilla/proxy/web_ui/templates/landing.html.jinja:47 +msgid "web_ui.landing.html.what_to_do_2" +msgstr "" +"If you've configured your browser properly, you can visit http://hkt.mitm.it. It's Haketilo " +"configuration page that's hosted locally "inside" the proxy." + #: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:24 msgid "web_ui.prompts.auto_install_error.title" msgstr "Installation failure" @@ -1142,53 +1192,53 @@ msgstr "Actions" msgid "web_ui.rules.single.remove_button" msgstr "Remove rule" -#: src/hydrilla/server/malcontent.py:80 +#: src/hydrilla/server/malcontent.py:77 msgid "err.server.malcontent_path_not_dir_{}" msgstr "Provided 'malcontent_dir' path does not name a directory: {}" -#: src/hydrilla/server/malcontent.py:99 +#: src/hydrilla/server/malcontent.py:96 msgid "err.server.couldnt_load_item_from_{}" msgstr "Couldn't load item from {}." -#: src/hydrilla/server/malcontent.py:112 +#: src/hydrilla/server/malcontent.py:109 msgid "err.server.no_file_{required_by}_{ver}_{file}_{sha256}" msgstr "" "'{required_by}', version '{ver}' uses a file named {file} with SHA256 " "hash of {sha256}, but the file is missing." -#: src/hydrilla/server/malcontent.py:136 +#: src/hydrilla/server/malcontent.py:133 msgid "err.server.item_{item}_in_file_{file}" msgstr "Item {item} incorrectly present under {file}." -#: src/hydrilla/server/malcontent.py:142 +#: src/hydrilla/server/malcontent.py:139 msgid "item_version_{ver}_in_file_{file}" msgstr "Item version {ver} incorrectly present under {file}." -#: src/hydrilla/server/malcontent.py:169 +#: src/hydrilla/server/malcontent.py:166 msgid "err.server.no_dep_{resource}_{ver}_{dep}" msgstr "Unknown dependency '{dep}' of resource '{resource}', version '{ver}'." -#: src/hydrilla/server/malcontent.py:184 +#: src/hydrilla/server/malcontent.py:181 msgid "err.server.no_payload_{mapping}_{ver}_{payload}" msgstr "Unknown payload '{payload}' of mapping '{mapping}', version '{ver}'." -#: src/hydrilla/server/malcontent.py:199 +#: src/hydrilla/server/malcontent.py:196 msgid "err.server.no_mapping_{required_by}_{ver}_{required}" msgstr "Unknown mapping '{required}' required by '{required_by}', version '{ver}'." -#: src/hydrilla/server/malcontent.py:227 +#: src/hydrilla/server/malcontent.py:224 msgid "server.err.couldnt_register_{mapping}_{ver}_{pattern}" msgstr "" "Couldn't register mapping '{mapping}', version '{ver}' (pattern " "'{pattern}')." -#: src/hydrilla/server/serve.py:84 +#: src/hydrilla/server/serve.py:81 msgid "err.server.opt_hydrilla_parent_not_implemented" msgstr "" "Hydrilla was told to connect to a parent Hydrilla server but this feature" " is not yet implemented." -#: src/hydrilla/server/serve.py:213 +#: src/hydrilla/server/serve.py:217 msgid "serve_hydrilla_packages_explain_wsgi_considerations" msgstr "" "Serve Hydrilla packages.\n" @@ -1197,36 +1247,36 @@ msgstr "" "Hydrilla instance. For better performance, consider deployment using " "WSGI." -#: src/hydrilla/server/serve.py:216 +#: src/hydrilla/server/serve.py:220 msgid "directory_to_serve_from_overrides_config" msgstr "" "Directory to serve files from. Overrides value from the config file (if " "any)." -#: src/hydrilla/server/serve.py:218 +#: src/hydrilla/server/serve.py:222 msgid "project_url_to_display_overrides_config" msgstr "" "Project url to display on generated HTML pages. Overrides value from the " "config file (if any)." -#: src/hydrilla/server/serve.py:220 +#: src/hydrilla/server/serve.py:224 msgid "tcp_port_to_listen_on_overrides_config" msgstr "" "TCP port number to listen on (0-65535). Overrides value from the config " "file (if any)." -#: src/hydrilla/server/serve.py:223 +#: src/hydrilla/server/serve.py:227 msgid "path_to_config_file_explain_default" msgstr "" "Path to Hydrilla server configuration file (optional, by default Hydrilla" " loads its own config file, which in turn tries to load " "/etc/hydrilla/config.json)." -#: src/hydrilla/server/serve.py:255 +#: src/hydrilla/server/serve.py:259 msgid "config_option_{}_not_supplied" msgstr "Missing configuration option '{}'." -#: src/hydrilla/server/serve.py:259 +#: src/hydrilla/server/serve.py:263 msgid "serve_hydrilla_packages_wsgi_help" msgstr "" "Serve Hydrilla packages.\n" @@ -1235,39 +1285,39 @@ msgstr "" "HTTP server like Apache2 or Nginx. You can configure Hydrilla through the" " /etc/hydrilla/config.json file." -#: src/hydrilla/url_patterns.py:136 +#: src/hydrilla/url_patterns.py:139 msgid "err.url_pattern_{}.bad" msgstr "Not a valid Haketilo URL pattern: {}" -#: src/hydrilla/url_patterns.py:139 +#: src/hydrilla/url_patterns.py:142 msgid "err.url_{}.bad" msgstr "Not a valid URL: {}" -#: src/hydrilla/url_patterns.py:146 +#: src/hydrilla/url_patterns.py:149 msgid "err.url_pattern_{}.bad_scheme" msgstr "URL pattern has an unknown scheme: {}" -#: src/hydrilla/url_patterns.py:149 +#: src/hydrilla/url_patterns.py:152 msgid "err.url_{}.bad_scheme" msgstr "URL has an unknown scheme: {}" -#: src/hydrilla/url_patterns.py:154 +#: src/hydrilla/url_patterns.py:157 msgid "err.url_pattern_{}.special_scheme_port" msgstr "URL pattern has an explicit port while it shouldn't: {}" -#: src/hydrilla/url_patterns.py:166 +#: src/hydrilla/url_patterns.py:169 msgid "err.url_pattern_{}.bad_port" msgstr "URL pattern has a port outside of allowed range (1-65535): {}" -#: src/hydrilla/url_patterns.py:169 +#: src/hydrilla/url_patterns.py:172 msgid "err.url_{}.bad_port" msgstr "URL has a port outside of allowed range (1-65535): {}" -#: src/hydrilla/url_patterns.py:190 +#: src/hydrilla/url_patterns.py:193 msgid "err.url_pattern_{}.has_query" msgstr "URL pattern has a query string while it shouldn't: {}" -#: src/hydrilla/url_patterns.py:194 +#: src/hydrilla/url_patterns.py:197 msgid "err.url_pattern_{}.has_frag" msgstr "URL pattern has a fragment string while it shouldn't: {}" diff --git a/src/hydrilla/mitmproxy_launcher/launch.py b/src/hydrilla/mitmproxy_launcher/launch.py index 82766b9..4fe31db 100644 --- a/src/hydrilla/mitmproxy_launcher/launch.py +++ b/src/hydrilla/mitmproxy_launcher/launch.py @@ -29,6 +29,7 @@ import sys import os import subprocess as sp +import typing as t from pathlib import Path # The following import requires at least Python 3.8. There is no point adding @@ -54,13 +55,16 @@ addons = [HaketiloAddon()] help=_('cli_opt.haketilo.listen_host')) @click.option('-p', '--port', default=8080, type=click.IntRange(1, 65535), help=_('cli_opt.haketilo.port')) +@click.option('-L/-l', '--launch-browser/--no-launch-browser', default=True, + help=_('cli_opt.haketilo.launch_browser')) @click.option('-d', '--directory', default='~/.haketilo/', type=click.Path(file_okay=False), help=_('cli_opt.haketilo.dir')) @click.version_option(version=_version.version, prog_name='Haketilo proxy', message=_('%(prog)s_%(version)s_license'), help=_('cli_opt.haketilo.version')) -def launch(listen_host: str, port: int, directory: str): +def launch(listen_host: str, port: int, launch_browser: bool, directory: str) \ + -> t.NoReturn: directory_path = Path(os.path.expanduser(directory)).resolve() directory_path.mkdir(parents=True, exist_ok=True) @@ -69,6 +73,8 @@ def launch(listen_host: str, port: int, directory: str): script_path.write_text(addon_script_text) + launch_browser_str = 'true' if launch_browser else 'false' + sys.argv = [ 'mitmdump', '--listen-host', listen_host, @@ -77,6 +83,9 @@ def launch(listen_host: str, port: int, directory: str): '--set', 'upstream_cert=false', '--set', 'connection_strategy=lazy', '--set', f'haketilo_dir={directory_path}', + '--set', f'haketilo_listen_host={listen_host}', + '--set', f'haketilo_listen_port={port}', + '--set', f'haketilo_launch_browser={launch_browser_str}', '--scripts', str(script_path) ] diff --git a/src/hydrilla/proxy/addon.py b/src/hydrilla/proxy/addon.py index 328d4a1..cca7924 100644 --- a/src/hydrilla/proxy/addon.py +++ b/src/hydrilla/proxy/addon.py @@ -48,6 +48,7 @@ from ..exceptions import HaketiloException from ..translations import smart_gettext as _ from ..url_patterns import parse_url, ParsedUrl from .state_impl import ConcreteHaketiloState +from . import state from . import policies from . import http_messages @@ -83,27 +84,47 @@ class MitmproxyHeadersWrapper(): return self.headers.items(multi=True) +class LoggerToMitmproxy(state.Logger): + def warn(self, msg: str) -> None: + ctx.log.warn(f'Haketilo: {msg}') + + @dc.dataclass(frozen=True) class FlowHandlingData: request_url: ParsedUrl policy: policies.Policy +@dc.dataclass +class PassedOptions: + haketilo_dir: t.Optional[str] = None + haketilo_listen_host: t.Optional[str] = None + haketilo_listen_port: t.Optional[int] = None + haketilo_launch_browser: t.Optional[bool] = None + + @property + def fully_configured(self) -> bool: + return (self.haketilo_dir is not None and + self.haketilo_listen_host is not None and + self.haketilo_listen_port is not None and + self.haketilo_launch_browser is not None) + + magical_mitm_it_url_reg = re.compile(r'^http://mitm.it(/.*)?$') dummy_url = parse_url('http://dummy.replacement.url') @dc.dataclass class HaketiloAddon: - """ - ....... - """ - configured: bool = False - configured_lock: Lock = dc.field(default_factory=Lock) + initial_options: PassedOptions = PassedOptions() + configured: bool = False + configured_lock: Lock = dc.field(default_factory=Lock) flows_data: dict[int, FlowHandlingData] = dc.field(default_factory=dict) flows_data_lock: Lock = dc.field(default_factory=Lock) + logger: LoggerToMitmproxy = dc.field(default_factory=LoggerToMitmproxy) + state: t.Optional[ConcreteHaketiloState] = None def load(self, loader: addonmanager.Loader) -> None: @@ -112,29 +133,74 @@ class HaketiloAddon: name = 'haketilo_dir', typespec = str, default = '~/.haketilo/', - help = "Point to a Haketilo data directory to use", + help = "Point to a Haketilo data directory to use" + ) + loader.add_option( + name = 'haketilo_listen_host', + typespec = str, + default = '127.0.0.1', + help = "Specify the address proxy listens on" + ) + loader.add_option( + name = 'haketilo_listen_port', + typespec = int, + default = 8080, + help = "Specify the port listens on" + ) + loader.add_option( + name = 'haketilo_launch_browser', + typespec = bool, + default = True, + help = "Specify whether to attempt to open a browser window with Haketilo page displayed inside" ) def configure(self, updated: set[str]) -> None: - """....""" - if 'haketilo_dir' not in updated: - return - with self.configured_lock: - if self.configured: - ctx.log.warn(_('haketilo_dir_already_configured')) + val_names = ('dir', 'listen_host', 'listen_port', 'launch_browser') + for val_name in val_names: + key = f'haketilo_{val_name}' + + if key not in updated: + continue + + if getattr(self.initial_options, key) is not None: + fmt = _('warn.proxy.setting_already_configured_{}') + self.logger.warn(fmt.format(key)) + continue + + new_val = getattr(ctx.options, key) + setattr(self.initial_options, key, new_val) + + if self.configured or not self.initial_options.fully_configured: return try: - haketilo_dir = Path(ctx.options.haketilo_dir) - - self.state = ConcreteHaketiloState.make(haketilo_dir / 'store') + haketilo_dir = self.initial_options.haketilo_dir + listen_host = self.initial_options.haketilo_listen_host + listen_port = self.initial_options.haketilo_listen_port + + self.state = ConcreteHaketiloState.make( + store_dir = Path(t.cast(str, haketilo_dir)) / 'store', + listen_host = t.cast(str, listen_host), + listen_port = t.cast(int, listen_port), + logger = self.logger + ) except Exception as e: tb.print_exception(None, e, e.__traceback__) sys.exit(1) self.configured = True + def running(self) -> None: + with self.configured_lock: + assert self.configured + + assert self.state is not None + + if self.initial_options.haketilo_launch_browser: + if not self.state.launch_browser(): + self.logger.warn(_('warn.proxy.couldnt_launch_browser')) + def get_handling_data(self, flow: http.HTTPFlow) -> FlowHandlingData: policy: policies.Policy diff --git a/src/hydrilla/proxy/policies/__init__.py b/src/hydrilla/proxy/policies/__init__.py index 448420a..e958cbd 100644 --- a/src/hydrilla/proxy/policies/__init__.py +++ b/src/hydrilla/proxy/policies/__init__.py @@ -15,4 +15,4 @@ from .rule import RuleBlockPolicyFactory, RuleAllowPolicyFactory from .misc import FallbackAllowPolicy, FallbackBlockPolicy, ErrorBlockPolicy, \ DoNothingPolicy -from .web_ui import WebUIPolicyFactory +from .web_ui import WebUIMainPolicyFactory, WebUILandingPolicyFactory diff --git a/src/hydrilla/proxy/policies/web_ui.py b/src/hydrilla/proxy/policies/web_ui.py index 9d31696..f35b0b7 100644 --- a/src/hydrilla/proxy/policies/web_ui.py +++ b/src/hydrilla/proxy/policies/web_ui.py @@ -47,14 +47,27 @@ class WebUIPolicy(base.Policy): priority: t.ClassVar[base.PolicyPriority] = base.PolicyPriority._THREE haketilo_state: state.HaketiloState + ui_domain: web_ui.UIDomain def consume_request(self, request_info: http_messages.RequestInfo) \ -> http_messages.ProducedResponse: - return web_ui.process_request(request_info, self.haketilo_state) - + return web_ui.process_request( + request_info = request_info, + state = self.haketilo_state, + ui_domain = self.ui_domain + ) @dc.dataclass(frozen=True, unsafe_hash=True) class WebUIPolicyFactory(base.PolicyFactory): - """....""" + ui_domain: t.ClassVar[web_ui.UIDomain] + def make_policy(self, haketilo_state: state.HaketiloState) -> WebUIPolicy: - return WebUIPolicy(haketilo_state) + return WebUIPolicy(haketilo_state, self.ui_domain) + +@dc.dataclass(frozen=True, unsafe_hash=True) +class WebUIMainPolicyFactory(WebUIPolicyFactory): + ui_domain = web_ui.UIDomain.MAIN + +@dc.dataclass(frozen=True, unsafe_hash=True) +class WebUILandingPolicyFactory(WebUIPolicyFactory): + ui_domain = web_ui.UIDomain.LANDING_PAGE diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py index 5e09367..7fb7bac 100644 --- a/src/hydrilla/proxy/state.py +++ b/src/hydrilla/proxy/state.py @@ -520,6 +520,12 @@ class HaketiloGlobalSettings: repo_refresh_seconds: int +class Logger(ABC): + @abstractmethod + def warn(self, msg: str) -> None: + ... + + class MissingItemError(ValueError): """....""" pass @@ -594,3 +600,22 @@ class HaketiloState(ABC): ) -> None: """....""" ... + + @property + @abstractmethod + def listen_host(self) -> str: + ... + + @property + @abstractmethod + def listen_port(self) -> int: + ... + + @abstractmethod + def launch_browser(self) -> bool: + ... + + @property + @abstractmethod + def logger(self) -> Logger: + ... diff --git a/src/hydrilla/proxy/state_impl/base.py b/src/hydrilla/proxy/state_impl/base.py index 357ae88..df3287b 100644 --- a/src/hydrilla/proxy/state_impl/base.py +++ b/src/hydrilla/proxy/state_impl/base.py @@ -34,6 +34,7 @@ subtype. import sqlite3 import threading import secrets +import webbrowser import dataclasses as dc import typing as t @@ -137,6 +138,9 @@ PayloadsData = t.Mapping[st.PayloadRef, st.PayloadData] class HaketiloStateWithFields(st.HaketiloState): """....""" store_dir: Path + _listen_host: str + _listen_port: int + _logger: st.Logger connection: sqlite3.Connection settings: st.HaketiloGlobalSettings current_cursor: t.Optional[sqlite3.Cursor] = None @@ -251,3 +255,29 @@ class HaketiloStateWithFields(st.HaketiloState): dependencies as well as at startup. """ ... + + @property + def listen_host(self) -> str: + if self._listen_host != '0.0.0.0': + return '127.0.0.1' + + return self._listen_host + + @property + def listen_port(self) -> int: + return self._listen_port + + @property + def efective_listen_addr(self) -> str: + effective_host = self._listen_host + if self._listen_host == '0.0.0.0': + effective_host = '127.0.0.1' + + return f'http://{effective_host}:{self._listen_port}' + + def launch_browser(self) -> bool: + return webbrowser.open(self.efective_listen_addr) + + @property + def logger(self) -> st.Logger: + return self._logger diff --git a/src/hydrilla/proxy/state_impl/concrete_state.py b/src/hydrilla/proxy/state_impl/concrete_state.py index d2d6e56..83522cf 100644 --- a/src/hydrilla/proxy/state_impl/concrete_state.py +++ b/src/hydrilla/proxy/state_impl/concrete_state.py @@ -198,12 +198,29 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): def _rebuild_structures(self, cursor: sqlite3.Cursor) -> None: new_policy_tree = base.PolicyTree() - ui_factory = policies.WebUIPolicyFactory(builtin=True) - web_ui_pattern = 'http*://hkt.mitm.it/***' - for parsed_pattern in url_patterns.parse_pattern(web_ui_pattern): + web_ui_main_pattern = 'http*://hkt.mitm.it/***' + web_ui_main_factory = policies.WebUIMainPolicyFactory(builtin=True) + + for parsed_pattern in url_patterns.parse_pattern(web_ui_main_pattern): + new_policy_tree = new_policy_tree.register( + parsed_pattern = parsed_pattern, + item = web_ui_main_factory + ) + + web_ui_landing_pattern = f'{self.efective_listen_addr}/***' + web_ui_landing_factory = policies.WebUILandingPolicyFactory( + builtin = True + ) + + try: + parsed_pattern, = url_patterns.parse_pattern(web_ui_landing_pattern) + except url_patterns.HaketiloURLException: + fmt = _('warn.proxy.failed_to_register_landing_page_at_{}') + self.logger.warn(fmt.format(web_ui_landing_pattern)) + else: new_policy_tree = new_policy_tree.register( - parsed_pattern, - ui_factory + parsed_pattern = parsed_pattern, + item = web_ui_landing_factory ) # Put script blocking/allowing rules in policy tree. @@ -346,7 +363,12 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): self.settings = load_settings(cursor) @staticmethod - def make(store_dir: Path) -> 'ConcreteHaketiloState': + def make( + store_dir: Path, + listen_host: str, + listen_port: int, + logger: st.Logger + ) -> 'ConcreteHaketiloState': store_dir.mkdir(parents=True, exist_ok=True) connection = sqlite3.connect( @@ -360,7 +382,10 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): global_settings = load_settings(connection.cursor()) return ConcreteHaketiloState( - store_dir = store_dir, - connection = connection, - settings = global_settings + store_dir = store_dir, + _logger = logger, + _listen_host = listen_host, + _listen_port = listen_port, + connection = connection, + settings = global_settings ) diff --git a/src/hydrilla/proxy/web_ui/__init__.py b/src/hydrilla/proxy/web_ui/__init__.py index a5dddab..1ae5dba 100644 --- a/src/hydrilla/proxy/web_ui/__init__.py +++ b/src/hydrilla/proxy/web_ui/__init__.py @@ -4,4 +4,5 @@ # # Available under the terms of Creative Commons Zero v1.0 Universal. +from ._app import UIDomain from .root import process_request diff --git a/src/hydrilla/proxy/web_ui/_app.py b/src/hydrilla/proxy/web_ui/_app.py index ab15918..f54f72e 100644 --- a/src/hydrilla/proxy/web_ui/_app.py +++ b/src/hydrilla/proxy/web_ui/_app.py @@ -4,6 +4,8 @@ # # Available under the terms of Creative Commons Zero v1.0 Universal. +import enum +import dataclasses as dc import typing as t import flask @@ -11,8 +13,17 @@ import flask from .. import state as st +class UIDomain(enum.Enum): + MAIN = enum.auto() + LANDING_PAGE = enum.auto() + +@dc.dataclass(init=False) class WebUIApp(flask.Flask): - _haketilo_state: st.HaketiloState + _haketilo_state: st.HaketiloState + _haketilo_ui_domain: t.ClassVar[UIDomain] def get_haketilo_state() -> st.HaketiloState: return t.cast(WebUIApp, flask.current_app)._haketilo_state + +def get_haketilo_ui_domain() -> UIDomain: + return t.cast(WebUIApp, flask.current_app)._haketilo_ui_domain diff --git a/src/hydrilla/proxy/web_ui/root.py b/src/hydrilla/proxy/web_ui/root.py index 18ea18e..57dc958 100644 --- a/src/hydrilla/proxy/web_ui/root.py +++ b/src/hydrilla/proxy/web_ui/root.py @@ -29,6 +29,7 @@ ..... """ +import dataclasses as dc import typing as t from threading import Lock @@ -71,10 +72,21 @@ def get_settings() -> st.HaketiloGlobalSettings: return _app.get_haketilo_state().get_settings() +@dc.dataclass(init=False) class WebUIAppImpl(_app.WebUIApp): + # Flask app is not thread-safe and has to be accompanied by an ugly lock. + # This can cause slow requests to block other requests, so we might need a + # better workaround at some later point. + _haketilo_app_lock: Lock + + _haketilo_blueprints: t.ClassVar[t.Sequence[flask.Blueprint]] + _haketilo_ui_domain: t.ClassVar[_app.UIDomain] + def __init__(self): super().__init__(__name__) + self._haketilo_app_lock = Lock() + self.jinja_options = { **self.jinja_options, 'loader': jinja2.PackageLoader(__package__), @@ -99,19 +111,13 @@ class WebUIAppImpl(_app.WebUIApp): self.before_request(authenticate_by_referrer) - for blueprint in [ - rules.bp, repos.bp, items.bp, items_import.bp, prompts.bp - ]: - self.register_blueprint(blueprint) + for bp in self._haketilo_blueprints: + self.register_blueprint(bp) -# Flask app is not thread-safe and has to be accompanied by an ugly lock. This -# can cause slow requests to block other requests, so we might need a better -# workaround at some later point. -app = WebUIAppImpl() -app_lock = Lock() +home_bp = flask.Blueprint('home', __package__) -@app.route('/', methods=['GET']) +@home_bp.route('/', methods=['GET']) def home(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: state = _app.get_haketilo_state() @@ -122,7 +128,7 @@ def home(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: ) return flask.make_response(html, 200) -@app.route('/', methods=['POST']) +@home_bp.route('/', methods=['POST']) def home_post() -> werkzeug.Response: action = flask.request.form['action'] @@ -149,22 +155,57 @@ def home_post() -> werkzeug.Response: return flask.redirect(flask.url_for('.home'), 303) +blueprints_main = \ + (rules.bp, repos.bp, items.bp, items_import.bp, prompts.bp, home_bp) + +@dc.dataclass(init=False) +class AppMain(WebUIAppImpl): + _haketilo_blueprints = blueprints_main + _haketilo_ui_domain = _app.UIDomain.MAIN + + +landing_bp = flask.Blueprint('landing_page', __package__) + +@landing_bp.route('/', methods=['GET']) +def landing(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: + state = _app.get_haketilo_state() + + html = flask.render_template( + 'landing.html.jinja', + listen_host = state.listen_host, + listen_port = state.listen_port + ) + return flask.make_response(html, 200) + +@dc.dataclass(init=False) +class AppLandingPage(WebUIAppImpl): + _haketilo_blueprints = (landing_bp,) + _haketilo_ui_domain = _app.UIDomain.LANDING_PAGE + + +apps_seq = [AppMain(), AppLandingPage()] +apps = dict((app._haketilo_ui_domain, app) for app in apps_seq) + + def process_request( request_info: http_messages.RequestInfo, - state: st.HaketiloState + state: st.HaketiloState, + ui_domain: _app.UIDomain = _app.UIDomain.MAIN ) -> http_messages.ProducedResponse: path = '/'.join(('', *request_info.url.path_segments)) if (request_info.url.has_trailing_slash): path += '/' - with app_lock: + app = apps[ui_domain] + + with app._haketilo_app_lock: app._haketilo_state = state app.jinja_env.install_gettext_translations(make_translation()) flask_response = app.test_client().open( path = path, - base_url = f'{request_info.url.scheme}://hkt.mitm.it', + base_url = request_info.url.url_without_path, method = request_info.method, query_string = request_info.url.query, headers = [*request_info.headers.items()], diff --git a/src/hydrilla/proxy/web_ui/templates/base.html.jinja b/src/hydrilla/proxy/web_ui/templates/base.html.jinja index e4760bf..f89b39a 100644 --- a/src/hydrilla/proxy/web_ui/templates/base.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/base.html.jinja @@ -17,11 +17,9 @@ 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. --#} +#} -{% set settings = get_settings() %} - {% macro button_row(buttons_data, common_fields={}) %}
{% for classes, text, extra_fields in buttons_data %} @@ -223,96 +221,13 @@ code in a proprietary work, I am not going to enforce this in court. .hide { display: none !important; } - - ul#nav { - -moz-user-select: none; - user-select: none; - display: flex; - justify-content: stretch; - white-space: nowrap; - background-color: #e0e0e0; - margin: 0; - padding: 0; - border-bottom: 2px solid #444; - overflow-x: scroll; - } - - li.nav-entry, li.nav-separator { - list-style-type: none; - } - - li.nav-entry { - background-color: #70af70; - font-size: 115%; - cursor: pointer; - text-align: center; - flex: 1 1 0; - } - - li.nav-separator { - flex: 0 0 2px; - background-color: inherit; - } - - li.big-separator { - flex: 4 0 2px; - } - - li.nav-entry:hover { - box-shadow: 0 6px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); - } - - ul#nav > li.nav-active { - background-color: #65A065; - color: #222; - box-shadow: none; - cursor: default; - } - - ul#nav > li > :only-child { - display: block; - padding: 10px; - } - {% endblock %} + {% endblock style %} - {% endblock %} + {% endblock head %} - {% set active_endpoint = get_current_endpoint() %} - {% - set navigation_bar = [ - ('home', _('web_ui.base.nav.home'), false), - ('rules.rules', _('web_ui.base.nav.rules'), false), - ('items.packages', _('web_ui.base.nav.packages'), false), - ('items.libraries', _('web_ui.base.nav.libraries'), true), - ('repos.repos', _('web_ui.base.nav.repos'), false), - ('import.items_import', _('web_ui.base.nav.import'), false) - ] - %} - -
{% block main required %}{% endblock %}
+ {% block body %} +
{% block main required %}{% endblock %}
+ {% endblock body %} diff --git a/src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja b/src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja new file mode 100644 index 0000000..6a6de99 --- /dev/null +++ b/src/hydrilla/proxy/web_ui/templates/hkt_mitm_it_base.html.jinja @@ -0,0 +1,116 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy web UI base page template of htk.mitm.it meta-site. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior + +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" %} + +{% set settings = get_settings() %} + +{% block style %} + {{ super() }} + ul#nav { + -moz-user-select: none; + user-select: none; + display: flex; + justify-content: stretch; + white-space: nowrap; + background-color: #e0e0e0; + margin: 0; + padding: 0; + border-bottom: 2px solid #444; + overflow-x: scroll; + } + + li.nav-entry, li.nav-separator { + list-style-type: none; + } + + li.nav-entry { + background-color: #70af70; + font-size: 115%; + cursor: pointer; + text-align: center; + flex: 1 1 0; + } + + li.nav-separator { + flex: 0 0 2px; + background-color: inherit; + } + + li.big-separator { + flex: 4 0 2px; + } + + li.nav-entry:hover { + box-shadow: 0 6px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); + } + + ul#nav > li.nav-active { + background-color: #65A065; + color: #222; + box-shadow: none; + cursor: default; + } + + ul#nav > li > :only-child { + display: block; + padding: 10px; + } +{% endblock style %} + +{% block body %} + {% set active_endpoint = get_current_endpoint() %} + {% + set navigation_bar = [ + ('home.home', _('web_ui.base.nav.home'), false), + ('rules.rules', _('web_ui.base.nav.rules'), false), + ('items.packages', _('web_ui.base.nav.packages'), false), + ('items.libraries', _('web_ui.base.nav.libraries'), true), + ('repos.repos', _('web_ui.base.nav.repos'), false), + ('import.items_import', _('web_ui.base.nav.import'), false) + ] + %} + + + {{ super() }} +{% endblock body %} diff --git a/src/hydrilla/proxy/web_ui/templates/import.html.jinja b/src/hydrilla/proxy/web_ui/templates/import.html.jinja index 7636b77..6ec9947 100644 --- a/src/hydrilla/proxy/web_ui/templates/import.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/import.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.import.title') }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/index.html.jinja index e42f5e9..010c2ed 100644 --- a/src/hydrilla/proxy/web_ui/templates/index.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/index.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.home.title') }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja index e517f3b..ccfa6b9 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% macro version_with_repo(info) -%} {{ info.info.version_string }} diff --git a/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja index 0a72b64..0996b8b 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.libraries.title') }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja index bc6b5bb..b79d594 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.packages.title') }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/landing.html.jinja b/src/hydrilla/proxy/web_ui/templates/landing.html.jinja new file mode 100644 index 0000000..6314cfd --- /dev/null +++ b/src/hydrilla/proxy/web_ui/templates/landing.html.jinja @@ -0,0 +1,49 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy web UI landing page. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior + +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 title %} {{ _('web_ui.landing.title') }} {% endblock %} + +{% block main %} +

+ {{ _('web_ui.landing.heading.haketilo_is_running') }} +

+ +

+ {{ _('web_ui.landing.web_ui.landing.what_to_do_1') }} +

+ + {{ label(_('web_ui.landing.host_label')) }} + +

+ {{ listen_host }} +

+ + {{ label(_('web_ui.landing.port_label')) }} + +

+ {{ listen_port }} +

+ + +{% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja index f4f600c..a17e61d 100644 --- a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.prompts.auto_install_error.title') }} diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja index ba06ae9..2df38b3 100644 --- a/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja @@ -19,7 +19,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.prompts.package_suggestion.title') }} diff --git a/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja index f635444..106af53 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.repos.add.title') }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja index e670b59..4f09ae6 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %}{{ _('web_ui.repos.title') }}{% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja index 04075c4..c4b7a9a 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.repos.single.title') }} {% endblock %} 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 430c5ca..6d21ccd 100644 --- a/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/rules/add.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.rules.add.title') }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja index 799eaba..57cc8ad 100644 --- a/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %}{{ _('web_ui.rules.title') }}{% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja index 9bf2c75..95e8009 100644 --- a/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja @@ -18,7 +18,7 @@ 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" %} +{% extends "hkt_mitm_it_base.html.jinja" %} {% block title %} {{ _('web_ui.rules.single.title') }} {% endblock %} diff --git a/src/hydrilla/url_patterns.py b/src/hydrilla/url_patterns.py index 115a040..cc68820 100644 --- a/src/hydrilla/url_patterns.py +++ b/src/hydrilla/url_patterns.py @@ -42,6 +42,12 @@ from immutables import Map from .translations import smart_gettext as _ from .exceptions import HaketiloException + +class HaketiloURLException(HaketiloException): + """Type used for exceptions generated when parsing a URL or URL pattern.""" + pass + + default_ports: t.Mapping[str, int] = Map(http=80, https=443, ftp=21) ParsedUrlType = t.TypeVar('ParsedUrlType', bound='ParsedUrl') @@ -131,9 +137,9 @@ def _parse_pattern_or_url( (parse_result.scheme != 'file' and not has_hostname): if is_pattern: msg = _('err.url_pattern_{}.bad').format(orig_url) - raise HaketiloException(msg) + raise HaketiloURLException(msg) else: - raise HaketiloException(_('err.url_{}.bad') .format(url)) + raise HaketiloURLException(_('err.url_{}.bad') .format(url)) # Verify the URL uses a known scheme and extract it. scheme = parse_result.scheme @@ -141,15 +147,15 @@ def _parse_pattern_or_url( if parse_result.scheme not in supported_schemes: if is_pattern: msg = _('err.url_pattern_{}.bad_scheme').format(orig_url) - raise HaketiloException(msg) + raise HaketiloURLException(msg) else: - raise HaketiloException(_('err.url_{}.bad_scheme').format(url)) + raise HaketiloURLException(_('err.url_{}.bad_scheme').format(url)) # Extract and keep information about special pattern schemas used. if is_pattern and orig_url.startswith('http*:'): if parse_result.port: fmt = _('err.url_pattern_{}.special_scheme_port') - raise HaketiloException(fmt.format(orig_url)) + raise HaketiloURLException(fmt.format(orig_url)) # Extract URL's explicit port or deduce the port based on URL's protocol. try: @@ -161,9 +167,9 @@ def _parse_pattern_or_url( if port_out_of_range: if is_pattern: msg = _('err.url_pattern_{}.bad_port').format(orig_url) - raise HaketiloException(msg) + raise HaketiloURLException(msg) else: - raise HaketiloException(_('err.url_{}.bad_port').format(url)) + raise HaketiloURLException(_('err.url_{}.bad_port').format(url)) port = explicit_port or default_ports.get(parse_result.scheme) @@ -185,11 +191,11 @@ def _parse_pattern_or_url( if is_pattern: if parse_result.query: msg = _('err.url_pattern_{}.has_query').format(orig_url) - raise HaketiloException(msg) + raise HaketiloURLException(msg) if parse_result.fragment: msg = _('err.url_pattern_{}.has_frag').format(orig_url) - raise HaketiloException(msg) + raise HaketiloURLException(msg) query = parse_result.query -- cgit v1.2.3