Browse Source

feat: Added support for all proxy modes (#24937)

* feat: Added support for all proxy modes

This commit extended setProxy to support all proxy modes including
direct, auto_detect, pac_script, fixed_servers and system.

* feat: New api for reload proxy configurations
LuoJinghua 4 years ago
parent
commit
201fc11b4b

+ 26 - 1
docs/api/session.md

@@ -283,6 +283,27 @@ 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
@@ -292,7 +313,7 @@ Returns `Promise<void>` - Resolves when the proxy setting process is complete.
 
 Sets the proxy settings.
 
-When `pacScript` and `proxyRules` are provided together, the `proxyRules`
+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
@@ -366,6 +387,10 @@ The `proxyBypassRules` is a comma separated list of rules described below:
 
 Returns `Promise<String>` - Resolves with the proxy information for `url`.
 
+#### `ses.forceReloadProxyConfig()`
+
+Returns `Promise<void>` - Resolves when the all internal states of proxy service is reset and the latest proxy configuration is reapplied if it's already available. The pac script will be fetched from `pacScript` again if the proxy mode is `pac_script`.
+
 #### `ses.setDownloadPath(path)`
 
 * `path` String - The download location.

+ 51 - 12
shell/browser/api/electron_api_session.cc

@@ -27,6 +27,7 @@
 #include "components/prefs/value_map_pref_store.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/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_item_utils.h"
@@ -504,26 +505,50 @@ v8::Local<v8::Promise> Session::SetProxy(gin::Arguments* args) {
     return handle;
   }
 
-  std::string proxy_rules, bypass_list, pac_url;
+  std::string mode, proxy_rules, bypass_list, pac_url;
 
   options.Get("pacScript", &pac_url);
   options.Get("proxyRules", &proxy_rules);
   options.Get("proxyBypassRules", &bypass_list);
 
-  // pacScript takes precedence over proxyRules.
-  if (!pac_url.empty()) {
-    browser_context_->in_memory_pref_store()->SetValue(
-        proxy_config::prefs::kProxy,
+  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 {
+      proxy_mode = ProxyPrefs::MODE_FIXED_SERVERS;
+    }
+  } 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;
+    }
+  }
+
+  std::unique_ptr<base::Value> proxy_config;
+  if (proxy_mode == ProxyPrefs::MODE_DIRECT) {
+    proxy_config =
+        std::make_unique<base::Value>(ProxyConfigDictionary::CreateDirect());
+  } else if (proxy_mode == ProxyPrefs::MODE_SYSTEM) {
+    proxy_config =
+        std::make_unique<base::Value>(ProxyConfigDictionary::CreateSystem());
+  } else if (proxy_mode == ProxyPrefs::MODE_AUTO_DETECT) {
+    proxy_config = std::make_unique<base::Value>(
+        ProxyConfigDictionary::CreateAutoDetect());
+  } else if (proxy_mode == ProxyPrefs::MODE_PAC_SCRIPT) {
+    proxy_config =
         std::make_unique<base::Value>(ProxyConfigDictionary::CreatePacScript(
-            pac_url, true /* pac_mandatory */)),
-        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+            pac_url, true /* pac_mandatory */));
   } else {
-    browser_context_->in_memory_pref_store()->SetValue(
-        proxy_config::prefs::kProxy,
-        std::make_unique<base::Value>(ProxyConfigDictionary::CreateFixedServers(
-            proxy_rules, bypass_list)),
-        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    proxy_config = std::make_unique<base::Value>(
+        ProxyConfigDictionary::CreateFixedServers(proxy_rules, bypass_list));
   }
+  browser_context_->in_memory_pref_store()->SetValue(
+      proxy_config::prefs::kProxy, std::move(proxy_config),
+      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(gin_helper::Promise<void>::ResolvePromise,
@@ -532,6 +557,19 @@ v8::Local<v8::Promise> Session::SetProxy(gin::Arguments* args) {
   return handle;
 }
 
+v8::Local<v8::Promise> Session::ForceReloadProxyConfig() {
+  v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
+  gin_helper::Promise<void> promise(isolate);
+  auto handle = promise.GetHandle();
+
+  content::BrowserContext::GetDefaultStoragePartition(browser_context_)
+      ->GetNetworkContext()
+      ->ForceReloadProxyConfig(base::BindOnce(
+          gin_helper::Promise<void>::ResolvePromise, std::move(promise)));
+
+  return handle;
+}
+
 void Session::SetDownloadPath(const base::FilePath& path) {
   browser_context_->prefs()->SetFilePath(prefs::kDownloadDefaultDirectory,
                                          path);
@@ -1100,6 +1138,7 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
       .SetMethod("clearStorageData", &Session::ClearStorageData)
       .SetMethod("flushStorageData", &Session::FlushStorageData)
       .SetMethod("setProxy", &Session::SetProxy)
+      .SetMethod("forceReloadProxyConfig", &Session::ForceReloadProxyConfig)
       .SetMethod("setDownloadPath", &Session::SetDownloadPath)
       .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation)
       .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation)

+ 1 - 0
shell/browser/api/electron_api_session.h

@@ -95,6 +95,7 @@ class Session : public gin::Wrappable<Session>,
   v8::Local<v8::Promise> ClearStorageData(gin::Arguments* args);
   void FlushStorageData();
   v8::Local<v8::Promise> SetProxy(gin::Arguments* args);
+  v8::Local<v8::Promise> ForceReloadProxyConfig();
   void SetDownloadPath(const base::FilePath& path);
   void EnableNetworkEmulation(const gin_helper::Dictionary& options);
   void DisableNetworkEmulation();

+ 76 - 4
spec-main/api-session-spec.ts

@@ -384,10 +384,18 @@ describe('session module', () => {
         res.end(pac);
       });
       await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
-      const config = { pacScript: `http://127.0.0.1:${(server.address() as AddressInfo).port}` };
-      await customSession.setProxy(config);
-      const proxy = await customSession.resolveProxy('https://google.com');
-      expect(proxy).to.equal('PROXY myproxy:8132');
+      {
+        const config = { pacScript: `http://127.0.0.1:${(server.address() as AddressInfo).port}` };
+        await customSession.setProxy(config);
+        const proxy = await customSession.resolveProxy('https://google.com');
+        expect(proxy).to.equal('PROXY myproxy:8132');
+      }
+      {
+        const config = { mode: 'pac_script' as any, pacScript: `http://127.0.0.1:${(server.address() as AddressInfo).port}` };
+        await customSession.setProxy(config);
+        const proxy = await customSession.resolveProxy('https://google.com');
+        expect(proxy).to.equal('PROXY myproxy:8132');
+      }
     });
 
     it('allows bypassing proxy settings', async () => {
@@ -399,6 +407,70 @@ describe('session module', () => {
       const proxy = await customSession.resolveProxy('http://example/');
       expect(proxy).to.equal('DIRECT');
     });
+
+    it('allows configuring proxy settings with mode `direct`', async () => {
+      const config = { mode: 'direct' as any, proxyRules: 'http=myproxy:80' };
+      await customSession.setProxy(config);
+      const proxy = await customSession.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 any };
+      await customSession.setProxy(config);
+    });
+
+    it('allows configuring proxy settings with mode `pac_script`', async () => {
+      const config = { mode: 'pac_script' as any };
+      await customSession.setProxy(config);
+      const proxy = await customSession.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 any, proxyRules: 'http=myproxy:80' };
+      await customSession.setProxy(config);
+      const proxy = await customSession.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 any };
+      await customSession.setProxy(config);
+    });
+
+    it('disallows configuring proxy settings with mode `invalid`', async () => {
+      const config = { mode: 'invalid' as any };
+      await expect(customSession.setProxy(config)).to.eventually.be.rejectedWith(/Invalid mode/);
+    });
+
+    it('reload proxy configuration', async () => {
+      let proxyPort = 8132;
+      server = http.createServer((req, res) => {
+        const pac = `
+          function FindProxyForURL(url, host) {
+            return "PROXY myproxy:${proxyPort}";
+          }
+        `;
+        res.writeHead(200, {
+          'Content-Type': 'application/x-ns-proxy-autoconfig'
+        });
+        res.end(pac);
+      });
+      await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
+      const config = { mode: 'pac_script' as any, pacScript: `http://127.0.0.1:${(server.address() as AddressInfo).port}` };
+      await customSession.setProxy(config);
+      {
+        const proxy = await customSession.resolveProxy('https://google.com');
+        expect(proxy).to.equal(`PROXY myproxy:${proxyPort}`);
+      }
+      {
+        proxyPort = 8133;
+        await customSession.forceReloadProxyConfig();
+        const proxy = await customSession.resolveProxy('https://google.com');
+        expect(proxy).to.equal(`PROXY myproxy:${proxyPort}`);
+      }
+    });
   });
 
   describe('ses.getBlobData()', () => {