diff --git a/docs/api/app.md b/docs/api/app.md index d9b921e6e8712..cbd9e42e2686c 100755 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -1468,6 +1468,24 @@ details. **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` - 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` - 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 ### `app.accessibilitySupportEnabled` _macOS_ _Windows_ diff --git a/docs/api/session.md b/docs/api/session.md index 6e2f0de113567..020d5a4f81741 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -589,105 +589,15 @@ Writes any unwritten DOMStorage data to disk. #### `ses.setProxy(config)` -* `config` Object - * `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. +* `config` [ProxyConfig](structures/proxy-config.md) Returns `Promise` - Resolves when the proxy setting process is complete. 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 pooled sockets using previous proxy from being reused by future requests. -The `proxyRules` has to follow the rules below: - -```sh -proxyRules = schemeProxies[";"] -schemeProxies = ["="] -urlScheme = "http" | "https" | "ftp" | "socks" -proxyURIList = [","] -proxyURL = ["://"][":"] -``` - -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 [ ":" ]` - - 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". - -* `` - - Match local addresses. The meaning of `` is whether the - host matches one of: "127.0.0.1", "::1", "localhost". - #### `ses.resolveHost(host, [options])` * `host` string - Hostname to resolve. diff --git a/docs/api/structures/proxy-config.md b/docs/api/structures/proxy-config.md new file mode 100644 index 0000000000000..eb68d37e651ac --- /dev/null +++ b/docs/api/structures/proxy-config.md @@ -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 = ["="] +urlScheme = "http" | "https" | "ftp" | "socks" +proxyURIList = [","] +proxyURL = ["://"][":"] +``` + +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 [ ":" ]` + + 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". + +* `` + + Match local addresses. The meaning of `` is whether the + host matches one of: "127.0.0.1", "::1", "localhost". diff --git a/filenames.auto.gni b/filenames.auto.gni index 43bff50aca102..6c02bcd4de90a 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -118,6 +118,7 @@ auto_filenames = { "docs/api/structures/protocol-request.md", "docs/api/structures/protocol-response-upload-data.md", "docs/api/structures/protocol-response.md", + "docs/api/structures/proxy-config.md", "docs/api/structures/rectangle.md", "docs/api/structures/referrer.md", "docs/api/structures/render-process-gone-details.md", diff --git a/shell/browser/api/electron_api_app.cc b/shell/browser/api/electron_api_app.cc index 734c4959e608b..4b971e156e127 100644 --- a/shell/browser/api/electron_api_app.cc +++ b/shell/browser/api/electron_api_app.cc @@ -26,6 +26,9 @@ #include "chrome/browser/icon_manager.h" #include "chrome/common/chrome_features.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/gpu_data_manager_impl.h" // nogncheck #include "content/public/browser/browser_accessibility_state.h" @@ -1472,6 +1475,98 @@ void App::EnableSandbox(gin_helper::ErrorThrower thrower) { command_line->AppendSwitch(switches::kEnableSandbox); } +v8::Local App::SetProxy(gin::Arguments* args) { + v8::Isolate* isolate = args->isolate(); + gin_helper::Promise promise(isolate); + v8::Local 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(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::ResolvePromise, std::move(promise))); + + return handle; +} + +v8::Local App::ResolveProxy(gin::Arguments* args) { + v8::Isolate* isolate = args->isolate(); + gin_helper::Promise promise(isolate); + v8::Local handle = promise.GetHandle(); + + GURL url; + args->GetNext(&url); + + static_cast(g_browser_process) + ->GetResolveProxyHelper() + ->ResolveProxy( + url, base::BindOnce(gin_helper::Promise::ResolvePromise, + std::move(promise))); + + return handle; +} + void App::SetUserAgentFallback(const std::string& user_agent) { ElectronBrowserClient::Get()->SetUserAgent(user_agent); } @@ -1776,7 +1871,9 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) { .SetProperty("userAgentFallback", &App::GetUserAgentFallback, &App::SetUserAgentFallback) .SetMethod("configureHostResolver", &ConfigureHostResolver) - .SetMethod("enableSandbox", &App::EnableSandbox); + .SetMethod("enableSandbox", &App::EnableSandbox) + .SetMethod("setProxy", &App::SetProxy) + .SetMethod("resolveProxy", &App::ResolveProxy); } const char* App::GetTypeName() { diff --git a/shell/browser/api/electron_api_app.h b/shell/browser/api/electron_api_app.h index c2f5eae50b102..846b1a11e685f 100644 --- a/shell/browser/api/electron_api_app.h +++ b/shell/browser/api/electron_api_app.h @@ -222,6 +222,8 @@ class App : public ElectronBrowserClient::Delegate, void EnableSandbox(gin_helper::ErrorThrower thrower); void SetUserAgentFallback(const std::string& user_agent); std::string GetUserAgentFallback(); + v8::Local SetProxy(gin::Arguments* args); + v8::Local ResolveProxy(gin::Arguments* args); #if BUILDFLAG(IS_MAC) void SetActivationPolicy(gin_helper::ErrorThrower thrower, diff --git a/shell/browser/browser_process_impl.cc b/shell/browser/browser_process_impl.cc index f33455d992b2d..b80dd44cc8670 100644 --- a/shell/browser/browser_process_impl.cc +++ b/shell/browser/browser_process_impl.cc @@ -37,6 +37,7 @@ #include "net/proxy_resolution/proxy_config_with_annotation.h" #include "services/device/public/cpp/geolocation/geolocation_manager.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/thread_restrictions.h" @@ -100,9 +101,9 @@ void BrowserProcessImpl::PostEarlyInitialization() { OSCrypt::RegisterLocalPrefs(pref_registry.get()); #endif - auto pref_store = base::MakeRefCounted(); - ApplyProxyModeFromCommandLine(pref_store.get()); - prefs_factory.set_command_line_prefs(std::move(pref_store)); + in_memory_pref_store_ = base::MakeRefCounted(); + ApplyProxyModeFromCommandLine(in_memory_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 // is the only key that needs it @@ -316,6 +317,14 @@ const std::string& BrowserProcessImpl::GetSystemLocale() const { return system_locale_; } +electron::ResolveProxyHelper* BrowserProcessImpl::GetResolveProxyHelper() { + if (!resolve_proxy_helper_) { + resolve_proxy_helper_ = base::MakeRefCounted( + system_network_context_manager()->GetContext()); + } + return resolve_proxy_helper_.get(); +} + #if BUILDFLAG(IS_LINUX) void BrowserProcessImpl::SetLinuxStorageBackend( os_crypt::SelectedLinuxBackend selected_backend) { diff --git a/shell/browser/browser_process_impl.h b/shell/browser/browser_process_impl.h index fd34bb8f96957..e6cbb963dcb5a 100644 --- a/shell/browser/browser_process_impl.h +++ b/shell/browser/browser_process_impl.h @@ -31,6 +31,10 @@ namespace printing { class PrintJobManager; } +namespace electron { +class ResolveProxyHelper; +} + // Empty definition for std::unique_ptr, rather than a forward declaration class BackgroundModeManager {}; @@ -53,9 +57,9 @@ class BrowserProcessImpl : public BrowserProcess { void PreMainMessageLoopRun(); void PostDestroyThreads() {} void PostMainMessageLoopRun(); - void SetSystemLocale(const std::string& locale); const std::string& GetSystemLocale() const; + electron::ResolveProxyHelper* GetResolveProxyHelper(); #if BUILDFLAG(IS_LINUX) void SetLinuxStorageBackend(os_crypt::SelectedLinuxBackend selected_backend); @@ -123,6 +127,10 @@ class BrowserProcessImpl : public BrowserProcess { printing::PrintJobManager* print_job_manager() override; StartupData* startup_data() override; + ValueMapPrefStore* in_memory_pref_store() const { + return in_memory_pref_store_.get(); + } + private: void CreateNetworkQualityObserver(); void CreateOSCryptAsync(); @@ -139,6 +147,8 @@ class BrowserProcessImpl : public BrowserProcess { #endif embedder_support::OriginTrialsSettingsStorage origin_trials_settings_storage_; + scoped_refptr in_memory_pref_store_; + scoped_refptr resolve_proxy_helper_; std::unique_ptr network_quality_tracker_; std::unique_ptr< network::NetworkQualityTracker::RTTAndThroughputEstimatesObserver> diff --git a/shell/browser/electron_browser_context.cc b/shell/browser/electron_browser_context.cc index 5b1b0fe454afa..4045070de19ad 100644 --- a/shell/browser/electron_browser_context.cc +++ b/shell/browser/electron_browser_context.cc @@ -535,7 +535,8 @@ ElectronBrowserContext::GetReduceAcceptLanguageControllerDelegate() { ResolveProxyHelper* ElectronBrowserContext::GetResolveProxyHelper() { if (!resolve_proxy_helper_) { - resolve_proxy_helper_ = base::MakeRefCounted(this); + resolve_proxy_helper_ = base::MakeRefCounted( + GetDefaultStoragePartition()->GetNetworkContext()); } return resolve_proxy_helper_.get(); } diff --git a/shell/browser/net/resolve_proxy_helper.cc b/shell/browser/net/resolve_proxy_helper.cc index 106e86316974b..3f54212b5458a 100644 --- a/shell/browser/net/resolve_proxy_helper.cc +++ b/shell/browser/net/resolve_proxy_helper.cc @@ -8,19 +8,17 @@ #include "base/functional/bind.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/storage_partition.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "net/base/network_anonymization_key.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; namespace electron { -ResolveProxyHelper::ResolveProxyHelper(ElectronBrowserContext* browser_context) - : browser_context_(browser_context) {} +ResolveProxyHelper::ResolveProxyHelper( + network::mojom::NetworkContext* network_context) + : network_context_(network_context) {} ResolveProxyHelper::~ResolveProxyHelper() { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -54,11 +52,9 @@ void ResolveProxyHelper::StartPendingRequest() { receiver_.set_disconnect_handler( base::BindOnce(&ResolveProxyHelper::OnProxyLookupComplete, base::Unretained(this), net::ERR_ABORTED, std::nullopt)); - browser_context_->GetDefaultStoragePartition() - ->GetNetworkContext() - ->LookUpProxyForURL(pending_requests_.front().url, - net::NetworkAnonymizationKey(), - std::move(proxy_lookup_client)); + network_context_->LookUpProxyForURL(pending_requests_.front().url, + net::NetworkAnonymizationKey(), + std::move(proxy_lookup_client)); } void ResolveProxyHelper::OnProxyLookupComplete( diff --git a/shell/browser/net/resolve_proxy_helper.h b/shell/browser/net/resolve_proxy_helper.h index 20c998be26cfd..d12b64acc7d49 100644 --- a/shell/browser/net/resolve_proxy_helper.h +++ b/shell/browser/net/resolve_proxy_helper.h @@ -12,20 +12,19 @@ #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.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 "url/gurl.h" namespace electron { -class ElectronBrowserContext; - class ResolveProxyHelper : public base::RefCountedThreadSafe, network::mojom::ProxyLookupClient { public: using ResolveProxyCallback = base::OnceCallback; - explicit ResolveProxyHelper(ElectronBrowserContext* browser_context); + explicit ResolveProxyHelper(network::mojom::NetworkContext* network_context); void ResolveProxy(const GURL& url, ResolveProxyCallback callback); @@ -71,7 +70,7 @@ class ResolveProxyHelper mojo::Receiver receiver_{this}; // Weak Ref - raw_ptr browser_context_; + raw_ptr network_context_ = nullptr; }; } // namespace electron diff --git a/spec/api-app-spec.ts b/spec/api-app-spec.ts index bfe6d6a444c04..5194dced3c027 100644 --- a/spec/api-app-spec.ts +++ b/spec/api-app-spec.ts @@ -6,9 +6,10 @@ import * as net from 'node:net'; import * as fs from 'fs-extra'; import * as path from 'node:path'; 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 { ifdescribe, ifit, listen, waitUntil } from './lib/spec-helpers'; +import { collectStreamBody, getResponse } from './lib/net-helpers'; import { once } from 'node:events'; import split = require('split') import * as semver from 'semver'; @@ -1895,6 +1896,154 @@ describe('app module', () => { 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: '' + }; + 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', () => { diff --git a/spec/lib/spec-helpers.ts b/spec/lib/spec-helpers.ts index e814c5a45390e..9bad7f322aeaa 100644 --- a/spec/lib/spec-helpers.ts +++ b/spec/lib/spec-helpers.ts @@ -200,5 +200,5 @@ export async function listen (server: http.Server | https.Server | http2.Http2Se await new Promise(resolve => server.listen(0, hostname, () => resolve())); const { port } = server.address() as net.AddressInfo; 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 }) }; }