feat: add support for configuring system network context proxies (#41335)

* feat: add support for configuring system network context proxies

* chore: add specs

* chore: fix lint

* fix: address review feedback
This commit is contained in:
Robo 2024-02-23 02:08:25 +09:00 committed by GitHub
parent 136762b45f
commit 26131b23b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 391 additions and 113 deletions

View file

@ -1468,6 +1468,24 @@ details.
**Note:** Enable `Secure Keyboard Entry` only when it is needed and disable it when it is no longer needed. **Note:** Enable `Secure Keyboard Entry` only when it is needed and disable it when it is no longer needed.
### `app.setProxy(config)`
* `config` [ProxyConfig](structures/proxy-config.md)
Returns `Promise<void>` - Resolves when the proxy setting process is complete.
Sets the proxy settings for networks requests made without an associated [Session](session.md).
Currently this will affect requests made with [Net](net.md) in the [utility process](../glossary.md#utility-process)
and internal requests made by the runtime (ex: geolocation queries).
This method can only be called after app is ready.
#### `app.resolveProxy(url)`
* `url` URL
Returns `Promise<string>` - Resolves with the proxy information for `url` that will be used when attempting to make requests using [Net](net.md) in the [utility process](../glossary.md#utility-process).
## Properties ## Properties
### `app.accessibilitySupportEnabled` _macOS_ _Windows_ ### `app.accessibilitySupportEnabled` _macOS_ _Windows_

View file

@ -589,105 +589,15 @@ Writes any unwritten DOMStorage data to disk.
#### `ses.setProxy(config)` #### `ses.setProxy(config)`
* `config` Object * `config` [ProxyConfig](structures/proxy-config.md)
* `mode` string (optional) - The proxy mode. Should be one of `direct`,
`auto_detect`, `pac_script`, `fixed_servers` or `system`. If it's
unspecified, it will be automatically determined based on other specified
options.
* `direct`
In direct mode all connections are created directly, without any proxy involved.
* `auto_detect`
In auto_detect mode the proxy configuration is determined by a PAC script that can
be downloaded at http://wpad/wpad.dat.
* `pac_script`
In pac_script mode the proxy configuration is determined by a PAC script that is
retrieved from the URL specified in the `pacScript`. This is the default mode
if `pacScript` is specified.
* `fixed_servers`
In fixed_servers mode the proxy configuration is specified in `proxyRules`.
This is the default mode if `proxyRules` is specified.
* `system`
In system mode the proxy configuration is taken from the operating system.
Note that the system mode is different from setting no proxy configuration.
In the latter case, Electron falls back to the system settings
only if no command-line options influence the proxy configuration.
* `pacScript` string (optional) - The URL associated with the PAC file.
* `proxyRules` string (optional) - Rules indicating which proxies to use.
* `proxyBypassRules` string (optional) - Rules indicating which URLs should
bypass the proxy settings.
Returns `Promise<void>` - Resolves when the proxy setting process is complete. Returns `Promise<void>` - Resolves when the proxy setting process is complete.
Sets the proxy settings. Sets the proxy settings.
When `mode` is unspecified, `pacScript` and `proxyRules` are provided together, the `proxyRules`
option is ignored and `pacScript` configuration is applied.
You may need `ses.closeAllConnections` to close currently in flight connections to prevent You may need `ses.closeAllConnections` to close currently in flight connections to prevent
pooled sockets using previous proxy from being reused by future requests. pooled sockets using previous proxy from being reused by future requests.
The `proxyRules` has to follow the rules below:
```sh
proxyRules = schemeProxies[";"<schemeProxies>]
schemeProxies = [<urlScheme>"="]<proxyURIList>
urlScheme = "http" | "https" | "ftp" | "socks"
proxyURIList = <proxyURL>[","<proxyURIList>]
proxyURL = [<proxyScheme>"://"]<proxyHost>[":"<proxyPort>]
```
For example:
* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and
HTTP proxy `foopy2:80` for `ftp://` URLs.
* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs.
* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing
over to `bar` if `foopy:80` is unavailable, and after that using no proxy.
* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs.
* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail
over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable.
* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no
proxy if `foopy` is unavailable.
* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use
`socks4://foopy2` for all other URLs.
The `proxyBypassRules` is a comma separated list of rules described below:
* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]`
Match all hostnames that match the pattern HOSTNAME_PATTERN.
Examples:
"foobar.com", "\*foobar.com", "\*.foobar.com", "\*foobar.com:99",
"https://x.\*.y.com:99"
* `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]`
Match a particular domain suffix.
Examples:
".google.com", ".com", "http://.google.com"
* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]`
Match URLs which are IP address literals.
Examples:
"127.0.1", "\[0:0::1]", "\[::1]", "http://\[::1]:99"
* `IP_LITERAL "/" PREFIX_LENGTH_IN_BITS`
Match any URL that is to an IP literal that falls between the
given range. IP range is specified using CIDR notation.
Examples:
"192.168.1.1/16", "fefe:13::abc/33".
* `<local>`
Match local addresses. The meaning of `<local>` is whether the
host matches one of: "127.0.0.1", "::1", "localhost".
#### `ses.resolveHost(host, [options])` #### `ses.resolveHost(host, [options])`
* `host` string - Hostname to resolve. * `host` string - Hostname to resolve.

View file

@ -0,0 +1,86 @@
# ProxyConfig Object
* `mode` string (optional) - The proxy mode. Should be one of `direct`,
`auto_detect`, `pac_script`, `fixed_servers` or `system`.
Defaults to `pac_script` proxy mode if `pacScript` option is specified
otherwise defaults to `fixed_servers`.
* `direct` - In direct mode all connections are created directly, without any proxy involved.
* `auto_detect` - In auto_detect mode the proxy configuration is determined by a PAC script that can
be downloaded at http://wpad/wpad.dat.
* `pac_script` - In pac_script mode the proxy configuration is determined by a PAC script that is
retrieved from the URL specified in the `pacScript`. This is the default mode if `pacScript` is specified.
* `fixed_servers` - In fixed_servers mode the proxy configuration is specified in `proxyRules`.
This is the default mode if `proxyRules` is specified.
* `system` - In system mode the proxy configuration is taken from the operating system.
Note that the system mode is different from setting no proxy configuration.
In the latter case, Electron falls back to the system settings only if no
command-line options influence the proxy configuration.
* `pacScript` string (optional) - The URL associated with the PAC file.
* `proxyRules` string (optional) - Rules indicating which proxies to use.
* `proxyBypassRules` string (optional) - Rules indicating which URLs should
bypass the proxy settings.
When `mode` is unspecified, `pacScript` and `proxyRules` are provided together, the `proxyRules`
option is ignored and `pacScript` configuration is applied.
The `proxyRules` has to follow the rules below:
```sh
proxyRules = schemeProxies[";"<schemeProxies>]
schemeProxies = [<urlScheme>"="]<proxyURIList>
urlScheme = "http" | "https" | "ftp" | "socks"
proxyURIList = <proxyURL>[","<proxyURIList>]
proxyURL = [<proxyScheme>"://"]<proxyHost>[":"<proxyPort>]
```
For example:
* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and
HTTP proxy `foopy2:80` for `ftp://` URLs.
* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs.
* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing
over to `bar` if `foopy:80` is unavailable, and after that using no proxy.
* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs.
* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail
over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable.
* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no
proxy if `foopy` is unavailable.
* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use
`socks4://foopy2` for all other URLs.
The `proxyBypassRules` is a comma separated list of rules described below:
* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]`
Match all hostnames that match the pattern HOSTNAME_PATTERN.
Examples:
"foobar.com", "\*foobar.com", "\*.foobar.com", "\*foobar.com:99",
"https://x.\*.y.com:99"
* `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]`
Match a particular domain suffix.
Examples:
".google.com", ".com", "http://.google.com"
* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]`
Match URLs which are IP address literals.
Examples:
"127.0.1", "\[0:0::1]", "\[::1]", "http://\[::1]:99"
* `IP_LITERAL "/" PREFIX_LENGTH_IN_BITS`
Match any URL that is to an IP literal that falls between the
given range. IP range is specified using CIDR notation.
Examples:
"192.168.1.1/16", "fefe:13::abc/33".
* `<local>`
Match local addresses. The meaning of `<local>` is whether the
host matches one of: "127.0.0.1", "::1", "localhost".

View file

@ -118,6 +118,7 @@ auto_filenames = {
"docs/api/structures/protocol-request.md", "docs/api/structures/protocol-request.md",
"docs/api/structures/protocol-response-upload-data.md", "docs/api/structures/protocol-response-upload-data.md",
"docs/api/structures/protocol-response.md", "docs/api/structures/protocol-response.md",
"docs/api/structures/proxy-config.md",
"docs/api/structures/rectangle.md", "docs/api/structures/rectangle.md",
"docs/api/structures/referrer.md", "docs/api/structures/referrer.md",
"docs/api/structures/render-process-gone-details.md", "docs/api/structures/render-process-gone-details.md",

View file

@ -26,6 +26,9 @@
#include "chrome/browser/icon_manager.h" #include "chrome/browser/icon_manager.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "components/proxy_config/proxy_config_dictionary.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "components/proxy_config/proxy_prefs.h"
#include "content/browser/gpu/compositor_util.h" // nogncheck #include "content/browser/gpu/compositor_util.h" // nogncheck
#include "content/browser/gpu/gpu_data_manager_impl.h" // nogncheck #include "content/browser/gpu/gpu_data_manager_impl.h" // nogncheck
#include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_accessibility_state.h"
@ -1472,6 +1475,98 @@ void App::EnableSandbox(gin_helper::ErrorThrower thrower) {
command_line->AppendSwitch(switches::kEnableSandbox); command_line->AppendSwitch(switches::kEnableSandbox);
} }
v8::Local<v8::Promise> App::SetProxy(gin::Arguments* args) {
v8::Isolate* isolate = args->isolate();
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
gin_helper::Dictionary options;
args->GetNext(&options);
if (!Browser::Get()->is_ready()) {
promise.RejectWithErrorMessage(
"app.setProxy() can only be called after app is ready.");
return handle;
}
if (!g_browser_process->local_state()) {
promise.RejectWithErrorMessage(
"app.setProxy() failed due to internal error.");
return handle;
}
std::string mode, proxy_rules, bypass_list, pac_url;
options.Get("pacScript", &pac_url);
options.Get("proxyRules", &proxy_rules);
options.Get("proxyBypassRules", &bypass_list);
ProxyPrefs::ProxyMode proxy_mode = ProxyPrefs::MODE_FIXED_SERVERS;
if (!options.Get("mode", &mode)) {
// pacScript takes precedence over proxyRules.
if (!pac_url.empty()) {
proxy_mode = ProxyPrefs::MODE_PAC_SCRIPT;
}
} else if (!ProxyPrefs::StringToProxyMode(mode, &proxy_mode)) {
promise.RejectWithErrorMessage(
"Invalid mode, must be one of direct, auto_detect, pac_script, "
"fixed_servers or system");
return handle;
}
base::Value::Dict proxy_config;
switch (proxy_mode) {
case ProxyPrefs::MODE_DIRECT:
proxy_config = ProxyConfigDictionary::CreateDirect();
break;
case ProxyPrefs::MODE_SYSTEM:
proxy_config = ProxyConfigDictionary::CreateSystem();
break;
case ProxyPrefs::MODE_AUTO_DETECT:
proxy_config = ProxyConfigDictionary::CreateAutoDetect();
break;
case ProxyPrefs::MODE_PAC_SCRIPT:
proxy_config = ProxyConfigDictionary::CreatePacScript(pac_url, true);
break;
case ProxyPrefs::MODE_FIXED_SERVERS:
proxy_config =
ProxyConfigDictionary::CreateFixedServers(proxy_rules, bypass_list);
break;
default:
NOTIMPLEMENTED();
}
static_cast<BrowserProcessImpl*>(g_browser_process)
->in_memory_pref_store()
->SetValue(proxy_config::prefs::kProxy,
base::Value{std::move(proxy_config)},
WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
g_browser_process->system_network_context_manager()
->GetContext()
->ForceReloadProxyConfig(base::BindOnce(
gin_helper::Promise<void>::ResolvePromise, std::move(promise)));
return handle;
}
v8::Local<v8::Promise> App::ResolveProxy(gin::Arguments* args) {
v8::Isolate* isolate = args->isolate();
gin_helper::Promise<std::string> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
GURL url;
args->GetNext(&url);
static_cast<BrowserProcessImpl*>(g_browser_process)
->GetResolveProxyHelper()
->ResolveProxy(
url, base::BindOnce(gin_helper::Promise<std::string>::ResolvePromise,
std::move(promise)));
return handle;
}
void App::SetUserAgentFallback(const std::string& user_agent) { void App::SetUserAgentFallback(const std::string& user_agent) {
ElectronBrowserClient::Get()->SetUserAgent(user_agent); ElectronBrowserClient::Get()->SetUserAgent(user_agent);
} }
@ -1776,7 +1871,9 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
.SetProperty("userAgentFallback", &App::GetUserAgentFallback, .SetProperty("userAgentFallback", &App::GetUserAgentFallback,
&App::SetUserAgentFallback) &App::SetUserAgentFallback)
.SetMethod("configureHostResolver", &ConfigureHostResolver) .SetMethod("configureHostResolver", &ConfigureHostResolver)
.SetMethod("enableSandbox", &App::EnableSandbox); .SetMethod("enableSandbox", &App::EnableSandbox)
.SetMethod("setProxy", &App::SetProxy)
.SetMethod("resolveProxy", &App::ResolveProxy);
} }
const char* App::GetTypeName() { const char* App::GetTypeName() {

View file

@ -222,6 +222,8 @@ class App : public ElectronBrowserClient::Delegate,
void EnableSandbox(gin_helper::ErrorThrower thrower); void EnableSandbox(gin_helper::ErrorThrower thrower);
void SetUserAgentFallback(const std::string& user_agent); void SetUserAgentFallback(const std::string& user_agent);
std::string GetUserAgentFallback(); std::string GetUserAgentFallback();
v8::Local<v8::Promise> SetProxy(gin::Arguments* args);
v8::Local<v8::Promise> ResolveProxy(gin::Arguments* args);
#if BUILDFLAG(IS_MAC) #if BUILDFLAG(IS_MAC)
void SetActivationPolicy(gin_helper::ErrorThrower thrower, void SetActivationPolicy(gin_helper::ErrorThrower thrower,

View file

@ -37,6 +37,7 @@
#include "net/proxy_resolution/proxy_config_with_annotation.h" #include "net/proxy_resolution/proxy_config_with_annotation.h"
#include "services/device/public/cpp/geolocation/geolocation_manager.h" #include "services/device/public/cpp/geolocation/geolocation_manager.h"
#include "services/network/public/cpp/network_switches.h" #include "services/network/public/cpp/network_switches.h"
#include "shell/browser/net/resolve_proxy_helper.h"
#include "shell/common/electron_paths.h" #include "shell/common/electron_paths.h"
#include "shell/common/thread_restrictions.h" #include "shell/common/thread_restrictions.h"
@ -100,9 +101,9 @@ void BrowserProcessImpl::PostEarlyInitialization() {
OSCrypt::RegisterLocalPrefs(pref_registry.get()); OSCrypt::RegisterLocalPrefs(pref_registry.get());
#endif #endif
auto pref_store = base::MakeRefCounted<ValueMapPrefStore>(); in_memory_pref_store_ = base::MakeRefCounted<ValueMapPrefStore>();
ApplyProxyModeFromCommandLine(pref_store.get()); ApplyProxyModeFromCommandLine(in_memory_pref_store());
prefs_factory.set_command_line_prefs(std::move(pref_store)); prefs_factory.set_command_line_prefs(in_memory_pref_store());
// Only use a persistent prefs store when cookie encryption is enabled as that // Only use a persistent prefs store when cookie encryption is enabled as that
// is the only key that needs it // is the only key that needs it
@ -316,6 +317,14 @@ const std::string& BrowserProcessImpl::GetSystemLocale() const {
return system_locale_; return system_locale_;
} }
electron::ResolveProxyHelper* BrowserProcessImpl::GetResolveProxyHelper() {
if (!resolve_proxy_helper_) {
resolve_proxy_helper_ = base::MakeRefCounted<electron::ResolveProxyHelper>(
system_network_context_manager()->GetContext());
}
return resolve_proxy_helper_.get();
}
#if BUILDFLAG(IS_LINUX) #if BUILDFLAG(IS_LINUX)
void BrowserProcessImpl::SetLinuxStorageBackend( void BrowserProcessImpl::SetLinuxStorageBackend(
os_crypt::SelectedLinuxBackend selected_backend) { os_crypt::SelectedLinuxBackend selected_backend) {

View file

@ -31,6 +31,10 @@ namespace printing {
class PrintJobManager; class PrintJobManager;
} }
namespace electron {
class ResolveProxyHelper;
}
// Empty definition for std::unique_ptr, rather than a forward declaration // Empty definition for std::unique_ptr, rather than a forward declaration
class BackgroundModeManager {}; class BackgroundModeManager {};
@ -53,9 +57,9 @@ class BrowserProcessImpl : public BrowserProcess {
void PreMainMessageLoopRun(); void PreMainMessageLoopRun();
void PostDestroyThreads() {} void PostDestroyThreads() {}
void PostMainMessageLoopRun(); void PostMainMessageLoopRun();
void SetSystemLocale(const std::string& locale); void SetSystemLocale(const std::string& locale);
const std::string& GetSystemLocale() const; const std::string& GetSystemLocale() const;
electron::ResolveProxyHelper* GetResolveProxyHelper();
#if BUILDFLAG(IS_LINUX) #if BUILDFLAG(IS_LINUX)
void SetLinuxStorageBackend(os_crypt::SelectedLinuxBackend selected_backend); void SetLinuxStorageBackend(os_crypt::SelectedLinuxBackend selected_backend);
@ -123,6 +127,10 @@ class BrowserProcessImpl : public BrowserProcess {
printing::PrintJobManager* print_job_manager() override; printing::PrintJobManager* print_job_manager() override;
StartupData* startup_data() override; StartupData* startup_data() override;
ValueMapPrefStore* in_memory_pref_store() const {
return in_memory_pref_store_.get();
}
private: private:
void CreateNetworkQualityObserver(); void CreateNetworkQualityObserver();
void CreateOSCryptAsync(); void CreateOSCryptAsync();
@ -139,6 +147,8 @@ class BrowserProcessImpl : public BrowserProcess {
#endif #endif
embedder_support::OriginTrialsSettingsStorage origin_trials_settings_storage_; embedder_support::OriginTrialsSettingsStorage origin_trials_settings_storage_;
scoped_refptr<ValueMapPrefStore> in_memory_pref_store_;
scoped_refptr<electron::ResolveProxyHelper> resolve_proxy_helper_;
std::unique_ptr<network::NetworkQualityTracker> network_quality_tracker_; std::unique_ptr<network::NetworkQualityTracker> network_quality_tracker_;
std::unique_ptr< std::unique_ptr<
network::NetworkQualityTracker::RTTAndThroughputEstimatesObserver> network::NetworkQualityTracker::RTTAndThroughputEstimatesObserver>

View file

@ -535,7 +535,8 @@ ElectronBrowserContext::GetReduceAcceptLanguageControllerDelegate() {
ResolveProxyHelper* ElectronBrowserContext::GetResolveProxyHelper() { ResolveProxyHelper* ElectronBrowserContext::GetResolveProxyHelper() {
if (!resolve_proxy_helper_) { if (!resolve_proxy_helper_) {
resolve_proxy_helper_ = base::MakeRefCounted<ResolveProxyHelper>(this); resolve_proxy_helper_ = base::MakeRefCounted<ResolveProxyHelper>(
GetDefaultStoragePartition()->GetNetworkContext());
} }
return resolve_proxy_helper_.get(); return resolve_proxy_helper_.get();
} }

View file

@ -8,19 +8,17 @@
#include "base/functional/bind.h" #include "base/functional/bind.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/network_anonymization_key.h" #include "net/base/network_anonymization_key.h"
#include "net/proxy_resolution/proxy_info.h" #include "net/proxy_resolution/proxy_info.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "shell/browser/electron_browser_context.h"
using content::BrowserThread; using content::BrowserThread;
namespace electron { namespace electron {
ResolveProxyHelper::ResolveProxyHelper(ElectronBrowserContext* browser_context) ResolveProxyHelper::ResolveProxyHelper(
: browser_context_(browser_context) {} network::mojom::NetworkContext* network_context)
: network_context_(network_context) {}
ResolveProxyHelper::~ResolveProxyHelper() { ResolveProxyHelper::~ResolveProxyHelper() {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
@ -54,11 +52,9 @@ void ResolveProxyHelper::StartPendingRequest() {
receiver_.set_disconnect_handler( receiver_.set_disconnect_handler(
base::BindOnce(&ResolveProxyHelper::OnProxyLookupComplete, base::BindOnce(&ResolveProxyHelper::OnProxyLookupComplete,
base::Unretained(this), net::ERR_ABORTED, std::nullopt)); base::Unretained(this), net::ERR_ABORTED, std::nullopt));
browser_context_->GetDefaultStoragePartition() network_context_->LookUpProxyForURL(pending_requests_.front().url,
->GetNetworkContext() net::NetworkAnonymizationKey(),
->LookUpProxyForURL(pending_requests_.front().url, std::move(proxy_lookup_client));
net::NetworkAnonymizationKey(),
std::move(proxy_lookup_client));
} }
void ResolveProxyHelper::OnProxyLookupComplete( void ResolveProxyHelper::OnProxyLookupComplete(

View file

@ -12,20 +12,19 @@
#include "base/memory/raw_ptr.h" #include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/receiver.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/proxy_lookup_client.mojom.h" #include "services/network/public/mojom/proxy_lookup_client.mojom.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace electron { namespace electron {
class ElectronBrowserContext;
class ResolveProxyHelper class ResolveProxyHelper
: public base::RefCountedThreadSafe<ResolveProxyHelper>, : public base::RefCountedThreadSafe<ResolveProxyHelper>,
network::mojom::ProxyLookupClient { network::mojom::ProxyLookupClient {
public: public:
using ResolveProxyCallback = base::OnceCallback<void(std::string)>; using ResolveProxyCallback = base::OnceCallback<void(std::string)>;
explicit ResolveProxyHelper(ElectronBrowserContext* browser_context); explicit ResolveProxyHelper(network::mojom::NetworkContext* network_context);
void ResolveProxy(const GURL& url, ResolveProxyCallback callback); void ResolveProxy(const GURL& url, ResolveProxyCallback callback);
@ -71,7 +70,7 @@ class ResolveProxyHelper
mojo::Receiver<network::mojom::ProxyLookupClient> receiver_{this}; mojo::Receiver<network::mojom::ProxyLookupClient> receiver_{this};
// Weak Ref // Weak Ref
raw_ptr<ElectronBrowserContext> browser_context_; raw_ptr<network::mojom::NetworkContext> network_context_ = nullptr;
}; };
} // namespace electron } // namespace electron

View file

@ -6,9 +6,10 @@ import * as net from 'node:net';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import * as path from 'node:path'; import * as path from 'node:path';
import { promisify } from 'node:util'; import { promisify } from 'node:util';
import { app, BrowserWindow, Menu, session, net as electronNet, WebContents } from 'electron/main'; import { app, BrowserWindow, Menu, session, net as electronNet, WebContents, utilityProcess } from 'electron/main';
import { closeWindow, closeAllWindows } from './lib/window-helpers'; import { closeWindow, closeAllWindows } from './lib/window-helpers';
import { ifdescribe, ifit, listen, waitUntil } from './lib/spec-helpers'; import { ifdescribe, ifit, listen, waitUntil } from './lib/spec-helpers';
import { collectStreamBody, getResponse } from './lib/net-helpers';
import { once } from 'node:events'; import { once } from 'node:events';
import split = require('split') import split = require('split')
import * as semver from 'semver'; import * as semver from 'semver';
@ -1895,6 +1896,154 @@ describe('app module', () => {
app.showAboutPanel(); app.showAboutPanel();
}); });
}); });
describe('app.setProxy(options)', () => {
let server: http.Server;
afterEach(async () => {
if (server) {
server.close();
}
await app.setProxy({ mode: 'direct' as const });
});
it('allows configuring proxy settings', async () => {
const config = { proxyRules: 'http=myproxy:80' };
await app.setProxy(config);
const proxy = await app.resolveProxy('http://example.com/');
expect(proxy).to.equal('PROXY myproxy:80');
});
it('allows removing the implicit bypass rules for localhost', async () => {
const config = {
proxyRules: 'http=myproxy:80',
proxyBypassRules: '<-loopback>'
};
await app.setProxy(config);
const proxy = await app.resolveProxy('http://localhost');
expect(proxy).to.equal('PROXY myproxy:80');
});
it('allows configuring proxy settings with pacScript', async () => {
server = http.createServer((req, res) => {
const pac = `
function FindProxyForURL(url, host) {
return "PROXY myproxy:8132";
}
`;
res.writeHead(200, {
'Content-Type': 'application/x-ns-proxy-autoconfig'
});
res.end(pac);
});
const { url } = await listen(server);
{
const config = { pacScript: url };
await app.setProxy(config);
const proxy = await app.resolveProxy('https://google.com');
expect(proxy).to.equal('PROXY myproxy:8132');
}
{
const config = { mode: 'pac_script' as any, pacScript: url };
await app.setProxy(config);
const proxy = await app.resolveProxy('https://google.com');
expect(proxy).to.equal('PROXY myproxy:8132');
}
});
it('allows bypassing proxy settings', async () => {
const config = {
proxyRules: 'http=myproxy:80',
proxyBypassRules: '<local>'
};
await app.setProxy(config);
const proxy = await app.resolveProxy('http://example/');
expect(proxy).to.equal('DIRECT');
});
it('allows configuring proxy settings with mode `direct`', async () => {
const config = { mode: 'direct' as const, proxyRules: 'http=myproxy:80' };
await app.setProxy(config);
const proxy = await app.resolveProxy('http://example.com/');
expect(proxy).to.equal('DIRECT');
});
it('allows configuring proxy settings with mode `auto_detect`', async () => {
const config = { mode: 'auto_detect' as const };
await app.setProxy(config);
});
it('allows configuring proxy settings with mode `pac_script`', async () => {
const config = { mode: 'pac_script' as const };
await app.setProxy(config);
const proxy = await app.resolveProxy('http://example.com/');
expect(proxy).to.equal('DIRECT');
});
it('allows configuring proxy settings with mode `fixed_servers`', async () => {
const config = { mode: 'fixed_servers' as const, proxyRules: 'http=myproxy:80' };
await app.setProxy(config);
const proxy = await app.resolveProxy('http://example.com/');
expect(proxy).to.equal('PROXY myproxy:80');
});
it('allows configuring proxy settings with mode `system`', async () => {
const config = { mode: 'system' as const };
await app.setProxy(config);
});
it('disallows configuring proxy settings with mode `invalid`', async () => {
const config = { mode: 'invalid' as any };
await expect(app.setProxy(config)).to.eventually.be.rejectedWith(/Invalid mode/);
});
it('impacts proxy for requests made from utility process', async () => {
const utilityFixturePath = path.resolve(__dirname, 'fixtures', 'api', 'utility-process', 'api-net-spec.js');
const fn = async () => {
const urlRequest = electronNet.request('http://example.com/');
const response = await getResponse(urlRequest);
expect(response.statusCode).to.equal(200);
const message = await collectStreamBody(response);
expect(message).to.equal('ok from proxy\n');
};
server = http.createServer((req, res) => {
res.writeHead(200);
res.end('ok from proxy\n');
});
const { port, hostname } = await listen(server);
const config = { mode: 'fixed_servers' as const, proxyRules: `http=${hostname}:${port}` };
await app.setProxy(config);
const proxy = await app.resolveProxy('http://example.com/');
expect(proxy).to.equal(`PROXY ${hostname}:${port}`);
const child = utilityProcess.fork(utilityFixturePath, [], {
execArgv: ['--expose-gc']
});
child.postMessage({ fn: `(${fn})()` });
const [data] = await once(child, 'message');
expect(data.ok).to.be.true(data.message);
// Cleanup.
const [code] = await once(child, 'exit');
expect(code).to.equal(0);
});
it('does not impact proxy for requests made from main process', async () => {
server = http.createServer((req, res) => {
res.writeHead(200);
res.end('ok from server\n');
});
const { url } = await listen(server);
const config = { mode: 'fixed_servers' as const, proxyRules: 'http=myproxy:80' };
await app.setProxy(config);
const proxy = await app.resolveProxy('http://example.com/');
expect(proxy).to.equal('PROXY myproxy:80');
const urlRequest = electronNet.request(url);
const response = await getResponse(urlRequest);
expect(response.statusCode).to.equal(200);
const message = await collectStreamBody(response);
expect(message).to.equal('ok from server\n');
});
});
}); });
describe('default behavior', () => { describe('default behavior', () => {

View file

@ -200,5 +200,5 @@ export async function listen (server: http.Server | https.Server | http2.Http2Se
await new Promise<void>(resolve => server.listen(0, hostname, () => resolve())); await new Promise<void>(resolve => server.listen(0, hostname, () => resolve()));
const { port } = server.address() as net.AddressInfo; const { port } = server.address() as net.AddressInfo;
const protocol = (server instanceof http.Server) ? 'http' : 'https'; const protocol = (server instanceof http.Server) ? 'http' : 'https';
return { port, url: url.format({ protocol, hostname, port }) }; return { port, hostname, url: url.format({ protocol, hostname, port }) };
} }