Przeglądaj źródła

Browser+LibWeb+WebContent: Implement per-URL-pattern proxies

...at least for SOCKS5.
Ali Mohammad Pur 3 lat temu
rodzic
commit
a42e03b01a

+ 2 - 0
Userland/Applications/Browser/Browser.h

@@ -14,6 +14,8 @@ namespace Browser {
 extern String g_home_url;
 extern String g_search_engine;
 extern Vector<String> g_content_filters;
+extern Vector<String> g_proxies;
+extern HashMap<String, size_t> g_proxy_mappings;
 extern bool g_content_filters_enabled;
 extern IconBag g_icon_bag;
 

+ 24 - 5
Userland/Applications/Browser/BrowserWindow.cpp

@@ -575,15 +575,34 @@ void BrowserWindow::content_filters_changed()
     });
 }
 
+void BrowserWindow::proxy_mappings_changed()
+{
+    tab_widget().for_each_child_of_type<Browser::Tab>([](auto& tab) {
+        tab.proxy_mappings_changed();
+        return IterationDecision::Continue;
+    });
+}
+
 void BrowserWindow::config_string_did_change(String const& domain, String const& group, String const& key, String const& value)
 {
-    if (domain != "Browser" || group != "Preferences")
+    if (domain != "Browser")
         return;
 
-    if (key == "SearchEngine")
-        Browser::g_search_engine = value;
-    else if (key == "Home")
-        Browser::g_home_url = value;
+    if (group == "Preferences") {
+        if (key == "SearchEngine")
+            Browser::g_search_engine = value;
+        else if (key == "Home")
+            Browser::g_home_url = value;
+    } else if (group.starts_with("Proxy:")) {
+        dbgln("Proxy mapping changed: {}/{} = {}", group, key, value);
+        auto proxy_spec = group.substring_view(6);
+        auto existing_proxy = Browser::g_proxies.find(proxy_spec);
+        if (existing_proxy.is_end())
+            Browser::g_proxies.append(proxy_spec);
+
+        Browser::g_proxy_mappings.set(key, existing_proxy.index());
+        proxy_mappings_changed();
+    }
 
     // TODO: ColorScheme
 }

+ 1 - 0
Userland/Applications/Browser/BrowserWindow.h

@@ -41,6 +41,7 @@ public:
     GUI::Action& inspect_dom_node_action() { return *m_inspect_dom_node_action; }
 
     void content_filters_changed();
+    void proxy_mappings_changed();
 
 private:
     explicit BrowserWindow(CookieJar&, URL);

+ 7 - 0
Userland/Applications/Browser/Tab.cpp

@@ -118,6 +118,8 @@ Tab::Tab(BrowserWindow& window)
     else
         m_web_content_view->set_content_filters({});
 
+    m_web_content_view->set_proxy_mappings(g_proxies, g_proxy_mappings);
+
     auto& go_back_button = toolbar.add_action(window.go_back_action());
     go_back_button.on_context_menu_request = [this](auto& context_menu_event) {
         if (!m_history.can_go_back())
@@ -516,6 +518,11 @@ void Tab::content_filters_changed()
         m_web_content_view->set_content_filters({});
 }
 
+void Tab::proxy_mappings_changed()
+{
+    m_web_content_view->set_proxy_mappings(g_proxies, g_proxy_mappings);
+}
+
 void Tab::action_entered(GUI::Action& action)
 {
     m_statusbar->set_override_text(action.status_tip());

+ 1 - 0
Userland/Applications/Browser/Tab.h

@@ -51,6 +51,7 @@ public:
     void did_become_active();
     void context_menu_requested(Gfx::IntPoint const& screen_position);
     void content_filters_changed();
+    void proxy_mappings_changed();
 
     void action_entered(GUI::Action&);
     void action_left(GUI::Action&);

+ 16 - 0
Userland/Applications/Browser/main.cpp

@@ -31,6 +31,8 @@ String g_search_engine;
 String g_home_url;
 Vector<String> g_content_filters;
 bool g_content_filters_enabled { true };
+Vector<String> g_proxies;
+HashMap<String, size_t> g_proxy_mappings;
 IconBag g_icon_bag;
 
 }
@@ -96,6 +98,20 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
 
     TRY(load_content_filters());
 
+    for (auto& group : Config::list_groups("Browser")) {
+        if (!group.starts_with("Proxy:"))
+            continue;
+
+        for (auto& key : Config::list_keys("Browser", group)) {
+            auto proxy_spec = group.substring_view(6);
+            auto existing_proxy = Browser::g_proxies.find(proxy_spec);
+            if (existing_proxy.is_end())
+                Browser::g_proxies.append(proxy_spec);
+
+            Browser::g_proxy_mappings.set(key, existing_proxy.index());
+        }
+    }
+
     URL first_url = Browser::url_from_user_input(Browser::g_home_url);
     if (specified_url) {
         if (Core::File::exists(specified_url)) {

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -280,6 +280,7 @@ set(SOURCES
     Loader/ImageLoader.cpp
     Loader/ImageResource.cpp
     Loader/LoadRequest.cpp
+    Loader/ProxyMappings.cpp
     Loader/Resource.cpp
     Loader/ResourceLoader.cpp
     MimeSniff/MimeType.cpp

+ 41 - 0
Userland/Libraries/LibWeb/Loader/ProxyMappings.cpp

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "ProxyMappings.h"
+
+Web::ProxyMappings& Web::ProxyMappings::the()
+{
+    static ProxyMappings instance {};
+    return instance;
+}
+
+Core::ProxyData Web::ProxyMappings::proxy_for_url(AK::URL const& url) const
+{
+    auto url_string = url.to_string();
+    for (auto& it : m_mappings) {
+        dbgln("Checking {} against {}...", url, it.key);
+        if (url_string.matches(it.key)) {
+            dbgln("Matched!");
+            auto result = Core::ProxyData::parse_url(m_proxies[it.value]);
+            if (result.is_error()) {
+                dbgln("Failed to parse proxy URL: {}", m_proxies[it.value]);
+                continue;
+            }
+            return result.release_value();
+        }
+    }
+
+    dbgln("No luck!");
+    return {};
+}
+
+void Web::ProxyMappings::set_mappings(Vector<String> proxies, OrderedHashMap<String, size_t> mappings)
+{
+    m_proxies = move(proxies);
+    m_mappings = move(mappings);
+
+    dbgln("Proxy mappings updated: proxies: {}", m_proxies);
+}

+ 31 - 0
Userland/Libraries/LibWeb/Loader/ProxyMappings.h

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/URL.h>
+#include <AK/Vector.h>
+#include <LibCore/Proxy.h>
+
+namespace Web {
+
+class ProxyMappings {
+public:
+    static ProxyMappings& the();
+
+    Core::ProxyData proxy_for_url(AK::URL const&) const;
+    void set_mappings(Vector<String> proxies, OrderedHashMap<String, size_t> mappings);
+
+private:
+    ProxyMappings() = default;
+    ~ProxyMappings() = default;
+
+    Vector<String> m_proxies;
+    OrderedHashMap<String, size_t> m_mappings;
+};
+
+}

+ 5 - 1
Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp

@@ -14,6 +14,7 @@
 #include <LibProtocol/RequestClient.h>
 #include <LibWeb/Loader/ContentFilter.h>
 #include <LibWeb/Loader/LoadRequest.h>
+#include <LibWeb/Loader/ProxyMappings.h>
 #include <LibWeb/Loader/Resource.h>
 #include <LibWeb/Loader/ResourceLoader.h>
 #include <serenity.h>
@@ -213,6 +214,9 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has
     }
 
     if (url.protocol() == "http" || url.protocol() == "https" || url.protocol() == "gemini") {
+        auto proxy = ProxyMappings::the().proxy_for_url(url);
+        dbgln("Proxy for {} is {}", url, proxy.type == decltype(proxy.type)::SOCKS5 ? IPv4Address(proxy.host_ipv4).to_string() : "(direct)");
+
         HashMap<String, String> headers;
         headers.set("User-Agent", m_user_agent);
         headers.set("Accept-Encoding", "gzip, deflate");
@@ -221,7 +225,7 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has
             headers.set(it.key, it.value);
         }
 
-        auto protocol_request = protocol_client().start_request(request.method(), url, headers, request.body());
+        auto protocol_request = protocol_client().start_request(request.method(), url, headers, request.body(), proxy);
         if (!protocol_request) {
             auto start_request_failure_msg = "Failed to initiate load"sv;
             log_failure(request, start_request_failure_msg);

+ 5 - 0
Userland/Libraries/LibWeb/OutOfProcessWebView.cpp

@@ -500,6 +500,11 @@ void OutOfProcessWebView::set_content_filters(Vector<String> filters)
     client().async_set_content_filters(filters);
 }
 
+void OutOfProcessWebView::set_proxy_mappings(Vector<String> proxies, HashMap<String, size_t> mappings)
+{
+    client().async_set_proxy_mappings(move(proxies), move(mappings));
+}
+
 void OutOfProcessWebView::set_preferred_color_scheme(Web::CSS::PreferredColorScheme color_scheme)
 {
     client().async_set_preferred_color_scheme(color_scheme);

+ 1 - 0
Userland/Libraries/LibWeb/OutOfProcessWebView.h

@@ -55,6 +55,7 @@ public:
     OrderedHashMap<String, String> get_local_storage_entries();
 
     void set_content_filters(Vector<String>);
+    void set_proxy_mappings(Vector<String> proxies, HashMap<String, size_t> mappings);
     void set_preferred_color_scheme(Web::CSS::PreferredColorScheme);
 
     Function<void(Gfx::IntPoint const& screen_position)> on_context_menu_request;

+ 18 - 0
Userland/Services/WebContent/ConnectionFromClient.cpp

@@ -7,6 +7,7 @@
 
 #include <AK/Debug.h>
 #include <AK/JsonObject.h>
+#include <AK/QuickSort.h>
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/FontDatabase.h>
 #include <LibGfx/SystemTheme.h>
@@ -24,6 +25,7 @@
 #include <LibWeb/HTML/Window.h>
 #include <LibWeb/Layout/InitialContainingBlock.h>
 #include <LibWeb/Loader/ContentFilter.h>
+#include <LibWeb/Loader/ProxyMappings.h>
 #include <LibWeb/Loader/ResourceLoader.h>
 #include <LibWeb/Painting/PaintableBox.h>
 #include <LibWeb/Painting/StackingContext.h>
@@ -454,6 +456,22 @@ void ConnectionFromClient::set_content_filters(Vector<String> const& filters)
         Web::ContentFilter::the().add_pattern(filter);
 }
 
+void ConnectionFromClient::set_proxy_mappings(Vector<String> const& proxies, HashMap<String, size_t> const& mappings)
+{
+    auto keys = mappings.keys();
+    quick_sort(keys, [&](auto& a, auto& b) { return a.length() < b.length(); });
+
+    OrderedHashMap<String, size_t> sorted_mappings;
+    for (auto& key : keys) {
+        auto value = *mappings.get(key);
+        if (value >= proxies.size())
+            continue;
+        sorted_mappings.set(key, value);
+    }
+
+    Web::ProxyMappings::the().set_mappings(proxies, move(sorted_mappings));
+}
+
 void ConnectionFromClient::set_preferred_color_scheme(Web::CSS::PreferredColorScheme const& color_scheme)
 {
     m_page_host->set_preferred_color_scheme(color_scheme);

+ 1 - 0
Userland/Services/WebContent/ConnectionFromClient.h

@@ -59,6 +59,7 @@ private:
     virtual Messages::WebContentServer::GetHoveredNodeIdResponse get_hovered_node_id() override;
     virtual Messages::WebContentServer::DumpLayoutTreeResponse dump_layout_tree() override;
     virtual void set_content_filters(Vector<String> const&) override;
+    virtual void set_proxy_mappings(Vector<String> const&, HashMap<String, size_t> const&) override;
     virtual void set_preferred_color_scheme(Web::CSS::PreferredColorScheme const&) override;
     virtual void set_has_focus(bool) override;
     virtual void set_is_scripting_enabled(bool) override;

+ 1 - 0
Userland/Services/WebContent/WebContentServer.ipc

@@ -43,6 +43,7 @@ endpoint WebContentServer
     select_all() =|
 
     set_content_filters(Vector<String> filters) =|
+    set_proxy_mappings(Vector<String> proxies, HashMap<String,size_t> mappings) =|
     set_preferred_color_scheme(Web::CSS::PreferredColorScheme color_scheme) =|
     set_has_focus(bool has_focus) =|
     set_is_scripting_enabled(bool is_scripting_enabled) =|