diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4b9411..44672141 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Deprecated old classes/properties/methods to make them eventually compatible with other Platforms and WebView engines. - Added Web support +- Added `ProxyController` for Android - Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState`, `isInFullscreen`, `getCameraCaptureState`, `setCameraCaptureState`, `getMicrophoneCaptureState`, `setMicrophoneCaptureState` WebView controller methods - Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS` WebView settings - Added support for `onPermissionRequest` event on iOS 15.0+ diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java index ee5b2cb6..d9442e68 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java @@ -12,6 +12,7 @@ import com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeSafariB import com.pichillilorenzo.flutter_inappwebview.credential_database.CredentialDatabaseHandler; import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserManager; import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebViewManager; +import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -35,6 +36,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { public MyWebStorage myWebStorage; public ServiceWorkerManager serviceWorkerManager; public WebViewFeatureManager webViewFeatureManager; + public ProxyManager proxyManager; public FlutterWebViewFactory flutterWebViewFactory; public static ValueCallback filePathCallbackLegacy; public static ValueCallback filePathCallback; @@ -97,6 +99,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { credentialDatabaseHandler = new CredentialDatabaseHandler(this); } webViewFeatureManager = new WebViewFeatureManager(this); + proxyManager = new ProxyManager(this); } @Override @@ -141,6 +144,10 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { webViewFeatureManager.dispose(); webViewFeatureManager = null; } + if (proxyManager != null) { + proxyManager.dispose(); + proxyManager = null; + } filePathCallbackLegacy = null; filePathCallback = null; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxyManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxyManager.java new file mode 100755 index 00000000..5a5556c2 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxyManager.java @@ -0,0 +1,138 @@ +package com.pichillilorenzo.flutter_inappwebview.proxy; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.webkit.ProxyConfig; +import androidx.webkit.ProxyController; +import androidx.webkit.WebViewFeature; + +import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview.types.ProxyRuleExt; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +public class ProxyManager implements MethodChannel.MethodCallHandler { + + static final String LOG_TAG = "ProxyManager"; + + public MethodChannel channel; + @Nullable + public static ProxyController proxyController; + @Nullable + public InAppWebViewFlutterPlugin plugin; + + public ProxyManager(final InAppWebViewFlutterPlugin plugin) { + this.plugin = plugin; + channel = new MethodChannel(plugin.messenger, "com.pichillilorenzo/flutter_inappwebview_proxycontroller"); + channel.setMethodCallHandler(this); + if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) { + proxyController = ProxyController.getInstance(); + } else { + proxyController = null; + } + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + switch (call.method) { + case "setProxyOverride": + if (proxyController != null) { + HashMap settingsMap = (HashMap) call.argument("settings"); + ProxySettings settings = new ProxySettings(); + if (settingsMap != null) { + settings.parse(settingsMap); + } + setProxyOverride(settings, result); + } else { + result.success(false); + } + break; + case "clearProxyOverride": + if (proxyController != null ) { + clearProxyOverride(result); + } else { + result.success(false); + } + break; + default: + result.notImplemented(); + } + } + + private void setProxyOverride(ProxySettings settings, final MethodChannel.Result result) { + if (proxyController != null) { + ProxyConfig.Builder proxyConfigBuilder = new ProxyConfig.Builder(); + for (String bypassRule : settings.bypassRules) { + proxyConfigBuilder.addBypassRule(bypassRule); + } + for (String direct : settings.directs) { + proxyConfigBuilder.addDirect(direct); + } + for (ProxyRuleExt proxyRule : settings.proxyRules) { + if (proxyRule.getSchemeFilter() != null) { + proxyConfigBuilder.addProxyRule(proxyRule.getUrl(), proxyRule.getSchemeFilter()); + } else { + proxyConfigBuilder.addProxyRule(proxyRule.getUrl()); + } + } + if (settings.bypassSimpleHostnames != null && settings.bypassSimpleHostnames) { + proxyConfigBuilder.bypassSimpleHostnames(); + } + if (settings.removeImplicitRules != null && settings.removeImplicitRules) { + proxyConfigBuilder.removeImplicitRules(); + } + proxyController.setProxyOverride(proxyConfigBuilder.build(), new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }, new Runnable() { + @Override + public void run() { + result.success(true); + } + }); + } + } + + private void clearProxyOverride(final MethodChannel.Result result) { + if (proxyController != null) { + proxyController.clearProxyOverride(new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }, new Runnable() { + @Override + public void run() { + result.success(true); + } + }); + } + } + + public void dispose() { + channel.setMethodCallHandler(null); + if (proxyController != null) { + // Clears the proxy settings + proxyController.clearProxyOverride(new Executor() { + @Override + public void execute(Runnable command) { + + } + }, new Runnable() { + @Override + public void run() { + + } + }); + proxyController = null; + } + plugin = null; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxySettings.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxySettings.java new file mode 100644 index 00000000..503d7592 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxySettings.java @@ -0,0 +1,88 @@ +package com.pichillilorenzo.flutter_inappwebview.proxy; + +import androidx.webkit.ProxyConfig; + +import com.pichillilorenzo.flutter_inappwebview.ISettings; +import com.pichillilorenzo.flutter_inappwebview.types.ProxyRuleExt; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ProxySettings implements ISettings { + List bypassRules = new ArrayList<>(); + List directs = new ArrayList<>(); + List proxyRules = new ArrayList<>(); + Boolean bypassSimpleHostnames = null; + Boolean removeImplicitRules = null; + + @Override + public ProxySettings parse(Map settings) { + for (Map.Entry pair : settings.entrySet()) { + String key = pair.getKey(); + Object value = pair.getValue(); + if (value == null) { + continue; + } + + switch (key) { + case "bypassRules": + bypassRules = (List) value; + break; + case "directs": + directs = (List) value; + break; + case "proxyRules": + proxyRules = new ArrayList<>(); + List> proxyRuleMapList = (List>) value; + for (Map proxyRuleMap : proxyRuleMapList) { + ProxyRuleExt proxyRuleExt = ProxyRuleExt.fromMap(proxyRuleMap); + if (proxyRuleExt != null) { + proxyRules.add(proxyRuleExt); + } + } + break; + case "bypassSimpleHostnames": + bypassSimpleHostnames = (Boolean) value; + break; + case "removeImplicitRules": + removeImplicitRules = (Boolean) value; + break; + } + } + + return this; + } + + @Override + public Map toMap() { + List> proxyRuleMapList = new ArrayList<>(); + for (ProxyRuleExt proxyRuleExt : proxyRules) { + proxyRuleMapList.add(proxyRuleExt.toMap()); + } + Map settings = new HashMap<>(); + settings.put("bypassRules", bypassRules); + settings.put("directs", directs); + settings.put("proxyRules", proxyRuleMapList); + settings.put("bypassSimpleHostnames", bypassSimpleHostnames); + settings.put("removeImplicitRules", removeImplicitRules); + return settings; + } + + @Override + public Map getRealSettings(ProxyConfig proxyConfig) { + Map realSettings = toMap(); + List> proxyRuleMapList = new ArrayList<>(); + List proxyRules = proxyConfig.getProxyRules(); + for (ProxyConfig.ProxyRule proxyRule : proxyRules) { + Map proxyRuleMap = new HashMap<>(); + proxyRuleMap.put("url", proxyRule.getUrl()); + proxyRuleMap.put("schemeFilter", proxyRule.getSchemeFilter()); + proxyRuleMapList.add(proxyRuleMap); + } + realSettings.put("bypassRules", proxyConfig.getBypassRules()); + realSettings.put("proxyRules", proxyRuleMapList); + return realSettings; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/ProxyRuleExt.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/ProxyRuleExt.java new file mode 100644 index 00000000..f6b12b8f --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/ProxyRuleExt.java @@ -0,0 +1,81 @@ +package com.pichillilorenzo.flutter_inappwebview.types; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class ProxyRuleExt { + @Nullable + private String schemeFilter; + @NonNull + private String url; + + public ProxyRuleExt(@Nullable String schemeFilter, @NonNull String url) { + this.schemeFilter = schemeFilter; + this.url = url; + } + + @Nullable + public static ProxyRuleExt fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + String url = (String) map.get("url"); + String schemeFilter = (String) map.get("schemeFilter"); + return new ProxyRuleExt(schemeFilter, url); + } + + public Map toMap() { + Map proxyRuleMap = new HashMap<>(); + proxyRuleMap.put("url", url); + proxyRuleMap.put("schemeFilter", schemeFilter); + return proxyRuleMap; + } + + @Nullable + public String getSchemeFilter() { + return schemeFilter; + } + + public void setSchemeFilter(@Nullable String schemeFilter) { + this.schemeFilter = schemeFilter; + } + + @NonNull + public String getUrl() { + return url; + } + + public void setUrl(@NonNull String url) { + this.url = url; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ProxyRuleExt that = (ProxyRuleExt) o; + + if (schemeFilter != null ? !schemeFilter.equals(that.schemeFilter) : that.schemeFilter != null) + return false; + return url.equals(that.url); + } + + @Override + public int hashCode() { + int result = schemeFilter != null ? schemeFilter.hashCode() : 0; + result = 31 * result + url.hashCode(); + return result; + } + + @Override + public String toString() { + return "ProxyRuleExt{" + + "schemeFilter='" + schemeFilter + '\'' + + ", url='" + url + '\'' + + '}'; + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 5da8fb6a..39f8452e 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -40,6 +40,16 @@ Future main() async { }, )); } + + // var proxyAvailable = await WebViewFeature.isFeatureSupported( + // WebViewFeature.PROXY_OVERRIDE); + // if (proxyAvailable) { + // ProxyController proxyController = ProxyController.instance(); + // await proxyController.clearProxyOverride(); + // await proxyController.setProxyOverride(settings: ProxySettings( + // proxyRules: [ProxyRule(url: "https://192.168.1.102:4433")], + // )); + // } } runApp(MyApp()); diff --git a/lib/src/android/main.dart b/lib/src/android/main.dart index 2ae39c51..fce751da 100644 --- a/lib/src/android/main.dart +++ b/lib/src/android/main.dart @@ -1,2 +1,3 @@ export 'service_worker_controller.dart'; export 'webview_feature.dart'; +export 'proxy_controller.dart'; diff --git a/lib/src/android/proxy_controller.dart b/lib/src/android/proxy_controller.dart new file mode 100644 index 00000000..834adfa6 --- /dev/null +++ b/lib/src/android/proxy_controller.dart @@ -0,0 +1,152 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'webview_feature.dart'; +import '../in_app_webview/webview.dart'; +import '../types/main.dart'; + +///Manages setting and clearing a process-specific override for the Android system-wide proxy settings that govern network requests made by [WebView]. +/// +///[WebView] may make network requests in order to fetch content that is not otherwise read from the file system or provided directly by application code. +///In this case by default the system-wide Android network proxy settings are used to redirect requests to appropriate proxy servers. +/// +///In the rare case that it is necessary for an application to explicitly specify its proxy configuration, +///this API may be used to explicitly specify the proxy rules that govern WebView initiated network requests. +/// +///**Supported Platforms/Implementations**: +///- Android native WebView ([Official API - ProxyController](https://developer.android.com/reference/androidx/webkit/ProxyController)) +class ProxyController { + static ProxyController? _instance; + static const MethodChannel _channel = const MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_proxycontroller'); + + ///Gets the [ProxyController] shared instance. + /// + ///This method should only be called if [WebViewFeature.isFeatureSupported] returns `true` for [WebViewFeature.PROXY_OVERRIDE]. + static ProxyController instance() { + return (_instance != null) ? _instance! : _init(); + } + + static ProxyController _init() { + _channel.setMethodCallHandler(_handleMethod); + _instance = ProxyController(); + return _instance!; + } + + static Future _handleMethod(MethodCall call) async { + // ProxyController controller = ProxyController.instance(); + switch (call.method) { + default: + throw UnimplementedError("Unimplemented ${call.method} method"); + } + // return null; + } + + ///Sets [ProxySettings] which will be used by all [WebView]s in the app. + ///URLs that match patterns in the bypass list will not be directed to any proxy. + ///Instead, the request will be made directly to the origin specified by the URL. + ///Network connections are not guaranteed to immediately use the new proxy setting; wait for the method to return before loading a page. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - ProxyController.setProxyOverride](https://developer.android.com/reference/androidx/webkit/ProxyController#setProxyOverride(androidx.webkit.ProxyConfig,%20java.util.concurrent.Executor,%20java.lang.Runnable))) + Future setProxyOverride({required ProxySettings settings}) async { + Map args = {}; + args.putIfAbsent("settings", () => settings.toMap()); + return await _channel.invokeMethod('setProxyOverride', args); + } + + ///Clears the proxy settings. + ///Network connections are not guaranteed to immediately use the new proxy setting; wait for the method to return before loading a page. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - ProxyController.clearProxyOverride](https://developer.android.com/reference/androidx/webkit/ProxyController#clearProxyOverride(java.util.concurrent.Executor,%20java.lang.Runnable))) + Future clearProxyOverride() async { + Map args = {}; + return await _channel.invokeMethod('clearProxyOverride', args); + } +} + +///Class that represents the settings used to configure the [ProxyController]. +/// +///**Supported Platforms/Implementations**: +///- Android native WebView ([Official API - ProxyConfig](https://developer.android.com/reference/androidx/webkit/ProxyConfig)) +class ProxySettings { + ///List of bypass rules. + /// + ///A bypass rule describes URLs that should skip proxy override settings and make a direct connection instead. These can be URLs or IP addresses. Wildcards are accepted. + ///For instance, the rule "*example.com" would mean that requests to "http://example.com" and "www.example.com" would not be directed to any proxy, + ///instead, would be made directly to the origin specified by the URL. + List bypassRules; + + ///List of scheme filters. + /// + ///URLs that match these scheme filters are connected to directly instead of using a proxy server. + List directs; + + ///List of proxy rules to be used for all URLs. This method can be called multiple times to add multiple rules. Additional rules have decreasing precedence. + /// + ///Proxy is a string in the format `[scheme://]host[:port]`. + ///Scheme is optional, if present must be `HTTP`, `HTTPS` or [SOCKS](https://tools.ietf.org/html/rfc1928) and defaults to `HTTP`. + ///Host is one of an IPv6 literal with brackets, an IPv4 literal or one or more labels separated by a period. + ///Port number is optional and defaults to `80` for `HTTP`, `443` for `HTTPS` and `1080` for `SOCKS`. + /// + ///The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). + List proxyRules; + + ///Hostnames without a period in them (and that are not IP literals) will skip proxy settings and be connected to directly instead. Examples: `"abc"`, `"local"`, `"some-domain"`. + /// + ///Hostnames with a trailing dot are not considered simple by this definition. + bool? bypassSimpleHostnames; + + ///By default, certain hostnames implicitly bypass the proxy if they are link-local IPs, or localhost addresses. + ///For instance hostnames matching any of (non-exhaustive list): + ///- localhost + ///- *.localhost + ///- [::1] + ///- 127.0.0.1/8 + ///- 169.254/16 + ///- [FE80::]/10 + ///Set this to `true` to override the default behavior and force localhost and link-local URLs to be sent through the proxy. + bool? removeImplicitRules; + + ProxySettings( + {this.bypassRules = const [], + this.directs = const [], + this.proxyRules = const [], + this.bypassSimpleHostnames, + this.removeImplicitRules}); + + Map toMap() { + return { + "bypassRules": bypassRules, + "directs": directs, + "proxyRules": proxyRules.map((e) => e.toMap()).toList(), + "bypassSimpleHostnames": bypassSimpleHostnames, + "removeImplicitRules": removeImplicitRules + }; + } + + static ProxySettings fromMap(Map map) { + var settings = ProxySettings(); + settings.bypassRules = map["bypassRules"]; + settings.directs = map["directs"]; + settings.proxyRules = (map["proxyRules"].cast>() + as List>) + .map((e) => ProxyRule.fromMap(e)) as List; + settings.bypassSimpleHostnames = map["bypassSimpleHostnames"]; + settings.removeImplicitRules = map["removeImplicitRules"]; + return settings; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } + + ProxySettings copy() { + return ProxySettings.fromMap(this.toMap()); + } +} diff --git a/lib/src/android/service_worker_controller.dart b/lib/src/android/service_worker_controller.dart index b31d15ab..e9527a15 100644 --- a/lib/src/android/service_worker_controller.dart +++ b/lib/src/android/service_worker_controller.dart @@ -15,6 +15,8 @@ class ServiceWorkerController { 'com.pichillilorenzo/flutter_inappwebview_serviceworkercontroller'); ///Gets the [ServiceWorkerController] shared instance. + /// + ///This method should only be called if [WebViewFeature.isFeatureSupported] returns `true` for [WebViewFeature.SERVICE_WORKER_BASIC_USAGE]. static ServiceWorkerController instance() { return (_instance != null) ? _instance! : _init(); } @@ -23,7 +25,8 @@ class ServiceWorkerController { ServiceWorkerClient? get serviceWorkerClient => _serviceWorkerClient; - ///Sets the service worker client. + ///Sets the client to capture service worker related callbacks. + ///A [ServiceWorkerClient] should be set before any service workers are active, e.g. a safe place is before any WebView instances are created or pages loaded. /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - ServiceWorkerControllerCompat.setServiceWorkerClient](https://developer.android.com/reference/androidx/webkit/ServiceWorkerControllerCompat#setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat))) diff --git a/lib/src/android/webview_feature.dart b/lib/src/android/webview_feature.dart index b51cd5b9..e6dac690 100644 --- a/lib/src/android/webview_feature.dart +++ b/lib/src/android/webview_feature.dart @@ -1,5 +1,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; +import '../in_app_webview/in_app_webview_controller.dart'; +import '../in_app_webview/in_app_webview_settings.dart'; ///Class that represents an Android-specific utility class for checking which WebView Support Library features are supported on the device. class WebViewFeature { @@ -70,15 +72,15 @@ class WebViewFeature { @override String toString() => _value; - /// + ///This feature covers [InAppWebViewController.createWebMessageChannel]. static const CREATE_WEB_MESSAGE_CHANNEL = const WebViewFeature._internal("CREATE_WEB_MESSAGE_CHANNEL"); - /// + ///This feature covers [InAppWebViewSettings.disabledActionModeMenuItems]. static const DISABLED_ACTION_MODE_MENU_ITEMS = const WebViewFeature._internal("DISABLED_ACTION_MODE_MENU_ITEMS"); - /// + ///This feature covers [InAppWebViewSettings.forceDark]. static const FORCE_DARK = const WebViewFeature._internal("FORCE_DARK"); /// diff --git a/lib/src/in_app_browser/in_app_browser_settings.dart b/lib/src/in_app_browser/in_app_browser_settings.dart index b3c7b408..6ad76fbe 100755 --- a/lib/src/in_app_browser/in_app_browser_settings.dart +++ b/lib/src/in_app_browser/in_app_browser_settings.dart @@ -60,11 +60,6 @@ class InAppBrowserClassSettings { return options; } - static Map instanceToMap( - InAppBrowserClassSettings settings) { - return settings.toMap(); - } - Map toJson() { return this.toMap(); } diff --git a/lib/src/types/main.dart b/lib/src/types/main.dart index 847ef700..f399a2d8 100644 --- a/lib/src/types/main.dart +++ b/lib/src/types/main.dart @@ -144,4 +144,6 @@ export 'webview_render_process_action.dart'; export 'window_features.dart'; export 'web_resource_error.dart'; export 'web_resource_error_type.dart'; -export 'media_capture_state.dart'; \ No newline at end of file +export 'media_capture_state.dart'; +export 'proxy_rule.dart'; +export 'proxy_scheme_filter.dart'; \ No newline at end of file diff --git a/lib/src/types/proxy_rule.dart b/lib/src/types/proxy_rule.dart new file mode 100644 index 00000000..40a31f6e --- /dev/null +++ b/lib/src/types/proxy_rule.dart @@ -0,0 +1,36 @@ +import 'proxy_scheme_filter.dart'; + +///Class that holds a scheme filter and a proxy URL. +class ProxyRule { + ///Represents the scheme filter. + ProxySchemeFilter? schemeFilter; + + ///Represents the proxy URL. + String url; + + ProxyRule({required this.url, this.schemeFilter}); + + ///Gets a possible [ProxyRule] instance from a [Map] value. + static ProxyRule? fromMap(Map? map) { + return map != null + ? ProxyRule( + url: map["url"], + schemeFilter: ProxySchemeFilter.fromValue(map["schemeFilter"])) + : null; + } + + ///Converts instance to a map. + Map toMap() { + return {"url": url, "schemeFilter": schemeFilter?.toValue()}; + } + + ///Converts instance to a map. + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/src/types/proxy_scheme_filter.dart b/lib/src/types/proxy_scheme_filter.dart new file mode 100644 index 00000000..7ebec59b --- /dev/null +++ b/lib/src/types/proxy_scheme_filter.dart @@ -0,0 +1,49 @@ +import '../android/proxy_controller.dart'; + +///Class that represent scheme filters used by [ProxyController]. +class ProxySchemeFilter { + final String _value; + + const ProxySchemeFilter._internal(this._value); + + ///Set of all values of [ProxySchemeFilter]. + static final Set values = [ + ProxySchemeFilter.MATCH_ALL_SCHEMES, + ProxySchemeFilter.MATCH_HTTP, + ProxySchemeFilter.MATCH_HTTPS, + ].toSet(); + + ///Gets a possible [ProxySchemeFilter] instance from a [String] value. + static ProxySchemeFilter? fromValue(String? value) { + if (value != null) { + try { + return ProxySchemeFilter.values + .firstWhere((element) => element.toValue() == value); + } catch (e) { + return null; + } + } + return null; + } + + ///Gets [String] value. + String toValue() => _value; + + @override + String toString() => _value; + + ///Matches all schemes. + static const MATCH_ALL_SCHEMES = const ProxySchemeFilter._internal("*"); + + ///HTTP scheme. + static const MATCH_HTTP = const ProxySchemeFilter._internal("http"); + + ///HTTPS scheme. + static const MATCH_HTTPS = + const ProxySchemeFilter._internal("https"); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} \ No newline at end of file diff --git a/nodejs_server_test_auth_basic_and_ssl/index.js b/nodejs_server_test_auth_basic_and_ssl/index.js index f6bcdb16..6aa0a022 100755 --- a/nodejs_server_test_auth_basic_and_ssl/index.js +++ b/nodejs_server_test_auth_basic_and_ssl/index.js @@ -1,12 +1,14 @@ // Example of the server https is taken from here: https://engineering.circle.com/https-authorized-certs-with-node-js-315e548354a2 // Conversion of client1-crt.pem to certificate.pfx: https://stackoverflow.com/a/38408666/4637638 -const express = require('express') -const https = require('https') -const cors = require('cors') -const auth = require('basic-auth') -const app = express() -const appHttps = express() -const appAuthBasic = express() +const express = require('express'); +const proxy = require('express-http-proxy'); +const https = require('https'); +const cors = require('cors'); +const auth = require('basic-auth'); +const app = express(); +const appHttps = express(); +const appAuthBasic = express(); +const appProxy = express(); const fs = require('fs') const path = require('path') const bodyParser = require('body-parser'); @@ -20,6 +22,7 @@ var options = { rejectUnauthorized: false }; +appHttps.use('/', proxy('www.google.com')); appHttps.get('/', (req, res) => { console.log(JSON.stringify(req.headers)) const cert = req.connection.getPeerCertificate() @@ -197,3 +200,7 @@ app.get("/test-download-file", (req, res) => { }) app.listen(8082) + +appProxy.use(cors()); +appProxy.use('/', proxy('www.google.com')); +appProxy.listen(8083); \ No newline at end of file diff --git a/nodejs_server_test_auth_basic_and_ssl/package-lock.json b/nodejs_server_test_auth_basic_and_ssl/package-lock.json index 04f0a5dc..8006b6af 100755 --- a/nodejs_server_test_auth_basic_and_ssl/package-lock.json +++ b/nodejs_server_test_auth_basic_and_ssl/package-lock.json @@ -108,6 +108,11 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -155,6 +160,31 @@ "vary": "~1.1.2" } }, + "express-http-proxy": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-1.6.3.tgz", + "integrity": "sha512-/l77JHcOUrDUX8V67E287VEUQT0lbm71gdGVoodnlWBziarYKgMcpqT7xvh/HM8Jv52phw8Bd8tY+a7QjOr7Yg==", + "requires": { + "debug": "^3.0.1", + "es6-promise": "^4.1.1", + "raw-body": "^2.3.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", diff --git a/nodejs_server_test_auth_basic_and_ssl/package.json b/nodejs_server_test_auth_basic_and_ssl/package.json index 6feac6cc..d273f3e3 100755 --- a/nodejs_server_test_auth_basic_and_ssl/package.json +++ b/nodejs_server_test_auth_basic_and_ssl/package.json @@ -13,6 +13,7 @@ "body-parser": "^1.19.0", "cors": "^2.8.5", "express": "latest", + "express-http-proxy": "^1.6.3", "https": "latest", "multiparty": "^4.2.2" }