From de9d081af2ebea0dcfb3180255335c29e4cfd8e5 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Fri, 12 Feb 2021 17:14:13 +0100 Subject: [PATCH] fix #679, updated X509Certificate classes, removed androidOnRequestFocus event because it is never called --- .idea/libraries/Dart_Packages.xml | 772 ------------------ .idea/libraries/Flutter_Plugins.xml | 5 +- CHANGELOG.md | 5 +- README.md | 4 +- .../InAppWebView/InAppWebView.java | 30 +- .../InAppWebViewChromeClient.java | 10 +- .../WebViewFeatureManager.java | 1 + example/.flutter-plugins-dependencies | 2 +- .../webview_flutter_test.dart | 124 ++- .../ios/Flutter/flutter_export_environment.sh | 4 +- example/ios/Runner/AppDelegate.swift | 10 +- example/lib/in_app_webiew_example.screen.dart | 6 +- example/lib/main.dart | 1 + example/pubspec.yaml | 2 +- flutter_inappwebview.iml | 1 - .../InAppBrowser/InAppBrowserManager.swift | 4 +- .../InAppBrowserWebViewController.swift | 101 +-- .../FlutterWebViewController.swift | 27 +- ios/Classes/InAppWebView/InAppWebView.swift | 30 +- .../InAppWebView/InAppWebViewOptions.swift | 1 + ios/Classes/InAppWebViewMethodHandler.swift | 7 +- lib/src/X509Certificate/asn1_decoder.dart | 14 +- lib/src/X509Certificate/asn1_der_encoder.dart | 27 + .../asn1_distinguished_names.dart | 38 + lib/src/X509Certificate/asn1_identifier.dart | 5 +- lib/src/X509Certificate/asn1_object.dart | 20 +- lib/src/X509Certificate/main.dart | 3 + lib/src/X509Certificate/oid.dart | 19 +- lib/src/X509Certificate/x509_certificate.dart | 136 +-- lib/src/X509Certificate/x509_extension.dart | 415 ++++++++++ lib/src/X509Certificate/x509_public_key.dart | 10 + lib/src/headless_in_app_webview.dart | 4 - lib/src/in_app_browser.dart | 8 - lib/src/in_app_webview.dart | 4 - lib/src/in_app_webview_controller.dart | 20 +- lib/src/webview.dart | 9 - lib/src/webview_options.dart | 13 +- 37 files changed, 862 insertions(+), 1030 deletions(-) delete mode 100644 .idea/libraries/Dart_Packages.xml create mode 100644 lib/src/X509Certificate/asn1_der_encoder.dart diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml deleted file mode 100644 index 787c9c8a..00000000 --- a/.idea/libraries/Dart_Packages.xml +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 31799730..c241dc8c 100755 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,6 +1,9 @@ - + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index e55a5083..54c5ddc6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,14 @@ - Added `initialUserScripts` WebView option - Added `addUserScript`, `addUserScripts`, `removeUserScript`, `removeUserScripts`, `removeAllUserScripts`, `callAsyncJavaScript` WebView methods - Added `contentWorld` argument to `evaluateJavascript` WebView method -- Added `isDirectionalLockEnabled`, `mediaType`, `pageZoom`, `limitsNavigationsToAppBoundDomains`, `useOnNavigationResponse`, `applePayAPIEnabled` iOS-specific WebView options +- Added `isDirectionalLockEnabled`, `mediaType`, `pageZoom`, `limitsNavigationsToAppBoundDomains`, `useOnNavigationResponse`, `applePayAPIEnabled`, `allowingReadAccessTo` iOS-specific WebView options - Added `handlesURLScheme`, `createPdf`, `createWebArchiveData` iOS-specific WebView methods - Added `iosOnNavigationResponse` and `iosShouldAllowDeprecatedTLS` iOS-specific WebView events - Added `iosAnimated` optional argument to `zoomBy` WebView method - Added `screenshotConfiguration` optional argument to `takeScreenshot` WebView method - Added `scriptHtmlTagAttributes` optional argument to `injectJavascriptFileFromUrl` WebView method - Added `cssLinkHtmlTagAttributes` optional argument to `injectCSSFileFromUrl` WebView method +- Added `iosAllowingReadAccessTo` iOS-specific optional argument to `loadUrl` WebView method - Added new iOS-specific attributes to `ShouldOverrideUrlLoadingRequest` and `CreateWindowRequest` classes - Updated integration tests - Merge "Upgraded appcompat to 1.2.0-rc-02" [#465](https://github.com/pichillilorenzo/flutter_inappwebview/pull/465) (thanks to [andreidiaconu](https://github.com/andreidiaconu)) @@ -52,6 +53,8 @@ - Changed `zoomBy` WebView method signature - Moved `saveWebArchive` WebView method from Android-specific to cross-platform - Renamed `HttpAuthChallenge` to `URLAuthenticationChallenge` +- Deleted `androidOnRequestFocus` event because it is never called +- Updated `basicConstraints`, `subjectKeyIdentifier`, `authorityKeyIdentifier`, `certificatePolicies`, `cRLDistributionPoints`, `authorityInfoAccess` attributes type of `X509Certificate` ## 4.0.0+4 diff --git a/README.md b/README.md index b3d0599c..1df4dd1e 100755 --- a/README.md +++ b/README.md @@ -437,7 +437,7 @@ Screenshots: * `isLoading`: Check if the WebView instance is in a loading state. * `loadData({required String data, String mimeType = "text/html", String encoding = "utf8", String baseUrl = "about:blank", String androidHistoryUrl = "about:blank"})`: Loads the given data into this WebView. * `loadFile({required String assetFilePath, Map headers = const {}})`: Loads the given `assetFilePath` with optional headers specified as a map from name to value. -* `loadUrl({required String url, Map headers = const {}})`: Loads the given url with optional headers specified as a map from name to value. +* `loadUrl({required String url, Map headers = const {}, String? iosAllowingReadAccessTo})`: Loads the given url with optional headers specified as a map from name to value. * `pauseTimers`: On Android, it pauses all layout, parsing, and JavaScript timers for all WebViews. This is a global requests, not restricted to just this WebView. This can be useful if the application has been paused. On iOS, it is restricted to just this WebView. * `postUrl({required String url, required Uint8List postData})`: Loads the given url with postData using `POST` method into this WebView. * `printCurrentPage`: Prints the current page. @@ -636,6 +636,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: ##### `InAppWebView` iOS-specific options * `accessibilityIgnoresInvertColors`: A Boolean value indicating whether the view ignores an accessibility request to invert its colors. The default value is `false`. +* `allowingReadAccessTo`: Used in combination with `WebView.initialUrl` (with `file://` scheme), it represents the URL from which to read the web content. This URL must be a file-based URL (with `file://` scheme). * `allowsAirPlayForMediaPlayback`: Set to `true` to allow AirPlay. The default value is `true`. * `allowsBackForwardNavigationGestures`: Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`. * `allowsInlineMediaPlayback`: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. @@ -713,7 +714,6 @@ Event names that starts with `android` or `ios` are events platform-specific. * `androidOnRenderProcessUnresponsive`: Event called when the renderer currently associated with the WebView becomes unresponsive as a result of a long running blocking task such as the execution of JavaScript (available only on Android). * `androidOnFormResubmission`: As the host application if the browser should resend data as the requested page was a result of a POST. The default is to not resend the data (available only on Android). * `androidOnScaleChanged`: Event fired when the scale applied to the WebView has changed (available only on Android). -* `androidOnRequestFocus`: Event fired when there is a request to display and focus for this WebView (available only on Android). * `androidOnReceivedIcon`: Event fired when there is new favicon for the current page (available only on Android). * `androidOnReceivedTouchIconUrl`: Event fired when there is an url for an apple-touch-icon (available only on Android). * `androidOnJsBeforeUnload`: Event fired when the client should display a dialog to confirm navigation away from the current page. This is the result of the `onbeforeunload` javascript event (available only on Android). diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java index 73df5c24..c491728c 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java @@ -666,21 +666,21 @@ final public class InAppWebView extends InputAwareWebView { "})();"; static final String onWindowBlurEventJS = "(function(){" + - " window.addEventListener('blur', function(e) {" + - " window." + JavaScriptBridgeInterface.name + ".callHandler('onWindowBlur');" + - " });" + - "})();"; + " window.addEventListener('blur', function(e) {" + + " window." + JavaScriptBridgeInterface.name + ".callHandler('onWindowBlur');" + + " });" + + "})();"; static final String callAsyncJavaScriptWrapperJS = "(function(obj) {" + - " (async function($FUNCTION_ARGUMENT_NAMES) {" + - " $FUNCTION_BODY" + - " })($FUNCTION_ARGUMENT_VALUES).then(function(value) {" + - " window." + JavaScriptBridgeInterface.name + ".callHandler('callAsyncJavaScript', {'value': value, 'error': null, 'resultUuid': '$RESULT_UUID'});" + - " }).catch(function(error) {" + - " window." + JavaScriptBridgeInterface.name + ".callHandler('callAsyncJavaScript', {'value': null, 'error': error, 'resultUuid': '$RESULT_UUID'});" + - " });" + - " return null;" + - "})($FUNCTION_ARGUMENTS_OBJ);"; + " (async function($FUNCTION_ARGUMENT_NAMES) {" + + " $FUNCTION_BODY" + + " })($FUNCTION_ARGUMENT_VALUES).then(function(value) {" + + " window." + JavaScriptBridgeInterface.name + ".callHandler('callAsyncJavaScript', {'value': value, 'error': null, 'resultUuid': '$RESULT_UUID'});" + + " }).catch(function(error) {" + + " window." + JavaScriptBridgeInterface.name + ".callHandler('callAsyncJavaScript', {'value': null, 'error': error, 'resultUuid': '$RESULT_UUID'});" + + " });" + + " return null;" + + "})($FUNCTION_ARGUMENTS_OBJ);"; public InAppWebView(Context context) { super(context); @@ -1864,8 +1864,8 @@ final public class InAppWebView extends InputAwareWebView { final ActionMode.Callback callback ) { // fix Android 10 clipboard not working properly https://github.com/pichillilorenzo/flutter_inappwebview/issues/678 - if (!options.useHybridComposition) { - onWindowFocusChanged(isFocused()); + if (!options.useHybridComposition && containerView != null) { + onWindowFocusChanged(containerView.isFocused()); } boolean hasBeenRemovedAndRebuilt = false; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java index 231a0d72..b7078ad0 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java @@ -569,7 +569,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.show(); - } + } @Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, final Message resultMsg) { @@ -635,14 +635,6 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR super.onCloseWindow(window); } - @Override - public void onRequestFocus(WebView view) { - final Map obj = new HashMap<>(); - channel.invokeMethod("onRequestFocus", obj); - - super.onCloseWindow(view); - } - @Override public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) { Map obj = new HashMap<>(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/WebViewFeatureManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/WebViewFeatureManager.java index 17637d80..1564bcff 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/WebViewFeatureManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/WebViewFeatureManager.java @@ -23,6 +23,7 @@ public class WebViewFeatureManager implements MethodChannel.MethodCallHandler { case "isFeatureSupported": String feature = (String) call.argument("feature"); result.success(WebViewFeature.isFeatureSupported(feature)); + break; default: result.notImplemented(); } diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index fabe3962..9a0e80e6 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-02-11 17:35:59.644389","version":"1.26.0-18.0.pre.257"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-02-12 16:50:12.665299","version":"1.27.0-2.0.pre.43"} \ No newline at end of file diff --git a/example/integration_test/webview_flutter_test.dart b/example/integration_test/webview_flutter_test.dart index bc7d18ba..1ff4f17a 100644 --- a/example/integration_test/webview_flutter_test.dart +++ b/example/integration_test/webview_flutter_test.dart @@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:path_provider/path_provider.dart'; import '.env.dart'; @@ -119,6 +120,127 @@ void main() { expect(content.contains('flutter_test_header'), isTrue); }); + group("iOS loadFileURL", () { + late Directory appSupportDir; + late File fileHtml; + late File fileJs; + + setUpAll(() async { + appSupportDir = (await getApplicationSupportDirectory())!; + + final Directory htmlFolder = Directory('${appSupportDir.path}/html/'); + if(!await htmlFolder.exists()){ + await htmlFolder.create(recursive: true); + } + + final Directory jsFolder = Directory('${appSupportDir.path}/js/'); + if(!await jsFolder.exists()){ + await jsFolder.create(recursive: true); + } + + var html = """ + + + file scheme + + + + + + """; + fileHtml = File(htmlFolder.path + "index.html"); + fileHtml.writeAsStringSync(html); + + var js = """ + console.log('message'); + """; + fileJs = File(jsFolder.path + "main.js"); + fileJs.writeAsStringSync(js); + }); + + testWidgets('initialUrl with file:// scheme and allowingReadAccessTo', (WidgetTester tester) async { + final Completer consoleMessageShouldNotComplete = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrl: Uri.encodeFull('file://${fileHtml.path}'), + onConsoleMessage: (controller, consoleMessage) { + consoleMessageShouldNotComplete.complete(consoleMessage); + }, + ), + ), + ); + var result = await consoleMessageShouldNotComplete.future + .timeout(const Duration(seconds: 2), onTimeout: () => null); + expect(result, null); + + final Completer consoleMessageCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrl: Uri.encodeFull('file://${fileHtml.path}'), + initialOptions: InAppWebViewGroupOptions( + ios: IOSInAppWebViewOptions( + allowingReadAccessTo: Uri.encodeFull('file://${appSupportDir.path}/') + ) + ), + onConsoleMessage: (controller, consoleMessage) { + consoleMessageCompleter.complete(consoleMessage); + }, + ), + ), + ); + final ConsoleMessage consoleMessage = await consoleMessageCompleter.future; + expect(consoleMessage.messageLevel, ConsoleMessageLevel.LOG); + expect(consoleMessage.message, 'message'); + }); + + testWidgets('loadUrl with file:// scheme and iosAllowingReadAccessTo argument', (WidgetTester tester) async { + final Completer consoleMessageShouldNotComplete = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + onWebViewCreated: (controller) { + controller.loadUrl(url: Uri.encodeFull('file://${fileHtml.path}')); + }, + onConsoleMessage: (controller, consoleMessage) { + consoleMessageShouldNotComplete.complete(consoleMessage); + }, + ), + ), + ); + var result = await consoleMessageShouldNotComplete.future + .timeout(const Duration(seconds: 2), onTimeout: () => null); + expect(result, null); + + final Completer consoleMessageCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + onWebViewCreated: (controller) { + controller.loadUrl(url: Uri.encodeFull('file://${fileHtml.path}'), + iosAllowingReadAccessTo: Uri.encodeFull('file://${appSupportDir.path}/')); + }, + onConsoleMessage: (controller, consoleMessage) { + consoleMessageCompleter.complete(consoleMessage); + }, + ), + ), + ); + final ConsoleMessage consoleMessage = await consoleMessageCompleter.future; + expect(consoleMessage.messageLevel, ConsoleMessageLevel.LOG); + expect(consoleMessage.message, 'message'); + }); + }, skip: !Platform.isIOS); + testWidgets('JavaScript Handler', (WidgetTester tester) async { final Completer controllerCompleter = @@ -1080,7 +1202,7 @@ void main() { await pageLoads.stream.first; final InAppWebViewController controller = await controllerCompleter.future; - await controller.evaluateJavascript(source: 'window.open("about:blank", "_blank")'); + await controller.evaluateJavascript(source: 'window.open("about:blank", "_blank");'); await pageLoads.stream.first; final String? currentUrl = await controller.getUrl(); expect(currentUrl, 'about:blank'); diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index b59a9d8e..ff2151af 100755 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -2,12 +2,12 @@ # This is a generated file; do not edit or check into version control. export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter" export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example" -export "FLUTTER_TARGET=integration_test/webview_flutter_test.dart" +export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "SYMROOT=${SOURCE_ROOT}/../build/ios" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" -export "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" +export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index e733b15a..d3674e9a 100755 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -10,9 +10,13 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) - /*FlutterDownloaderPlugin.setPluginRegistrantCallback({(registry: FlutterPluginRegistry) in - - })*/ + //FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } + +//private func registerPlugins(registry: FlutterPluginRegistry) { +// if (!registry.hasPlugin("FlutterDownloaderPlugin")) { +// FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!) +// } +//} diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 9467a106..c038e500 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -1,8 +1,11 @@ import 'dart:collection'; +import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:path_provider/path_provider.dart'; // import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -95,9 +98,10 @@ class _InAppWebViewExampleScreenState extends State { crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: false, mediaPlaybackRequiresUserGesture: false, + clearCache: true, ), android: AndroidInAppWebViewOptions( - useHybridComposition: true, + useHybridComposition: false, ), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, diff --git a/example/lib/main.dart b/example/lib/main.dart index ed68cb64..ecfe4ca7 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter_inappwebview_example/chrome_safari_browser_example.scree import 'package:flutter_inappwebview_example/headless_in_app_webview.screen.dart'; import 'package:flutter_inappwebview_example/in_app_webiew_example.screen.dart'; import 'package:flutter_inappwebview_example/in_app_browser_example.screen.dart'; +import 'package:path_provider/path_provider.dart'; // import 'package:permission_handler/permission_handler.dart'; // InAppLocalhostServer localhostServer = new InAppLocalhostServer(); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0362f1a1..28ede661 100755 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 flutter_downloader: ^1.5.2 - path_provider: ^1.6.27 + path_provider: ^2.0.0-nullsafety permission_handler: ^5.0.1+1 url_launcher: ^6.0.0-nullsafety.4 # connectivity: ^0.4.5+6 diff --git a/flutter_inappwebview.iml b/flutter_inappwebview.iml index 4cb39159..0adae5aa 100755 --- a/flutter_inappwebview.iml +++ b/flutter_inappwebview.iml @@ -80,6 +80,5 @@ - \ No newline at end of file diff --git a/ios/Classes/InAppBrowser/InAppBrowserManager.swift b/ios/Classes/InAppBrowser/InAppBrowserManager.swift index 2d9a0df3..3d006191 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserManager.swift @@ -166,7 +166,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { if webViewController.isHidden { webViewController.view.isHidden = true webViewController.tmpWindow!.rootViewController!.present(webViewController, animated: false, completion: {() -> Void in - webViewController.webView.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl) + }) webViewController.presentingViewController?.dismiss(animated: false, completion: {() -> Void in webViewController.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) @@ -175,7 +175,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { } else { webViewController.tmpWindow!.rootViewController!.present(webViewController, animated: true, completion: {() -> Void in - webViewController.webView.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl) + }) } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift index ee221d3f..ae6c3974 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -71,12 +71,12 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: webViewOptions) if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { - self.webView = webViewTransport.webView - self.webView.IABController = self - self.webView.contextMenu = contextMenu - self.webView.channel = channel! + webView = webViewTransport.webView + webView.IABController = self + webView.contextMenu = contextMenu + webView.channel = channel! } else { - self.webView = InAppWebView(frame: .zero, + webView = InAppWebView(frame: .zero, configuration: preWebviewConfiguration, IABController: self, contextMenu: contextMenu, @@ -86,8 +86,8 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega methodCallDelegate = InAppWebViewMethodHandler(webView: webView!) channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle) - self.webView.appendUserScripts(userScripts: initUserScripts) - self.containerWebView.addSubview(self.webView) + webView.appendUserScripts(userScripts: initUserScripts) + containerWebView.addSubview(webView) prepareConstraints() prepareWebView() @@ -111,7 +111,7 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega let configuration = self.webView!.configuration configuration.userContentController.add(contentRuleList!) - self.initLoad(initURL: self.initURL, initData: self.initData, initMimeType: self.initMimeType, initEncoding: self.initEncoding, initBaseUrl: self.initBaseUrl, initHeaders: self.initHeaders) + self.initLoad() self.onBrowserCreated() } @@ -122,7 +122,7 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega } } - initLoad(initURL: initURL, initData: initData, initMimeType: initMimeType, initEncoding: initEncoding, initBaseUrl: initBaseUrl, initHeaders: initHeaders) + initLoad() } onBrowserCreated() @@ -131,9 +131,16 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega super.viewWillAppear(animated) } - public func initLoad(initURL: URL?, initData: String?, initMimeType: String?, initEncoding: String?, initBaseUrl: String?, initHeaders: [String: String]?) { - if self.initData == nil { - loadUrl(url: self.initURL!, headers: self.initHeaders) + public func initLoad() { + if initData == nil, let initURL = initURL { + var allowingReadAccessToURL: URL? = nil + if let allowingReadAccessTo = webView.options?.allowingReadAccessTo, initURL.scheme == "file" { + allowingReadAccessToURL = URL(string: allowingReadAccessTo) + if allowingReadAccessToURL?.scheme != "file" { + allowingReadAccessToURL = nil + } + } + loadUrl(url: initURL, headers: initHeaders, allowingReadAccessTo: allowingReadAccessToURL) } else { webView.loadData(data: initData!, mimeType: initMimeType!, encoding: initEncoding!, baseUrl: initBaseUrl!) @@ -144,29 +151,29 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega super.viewDidLoad() urlField.delegate = self - urlField.text = self.initURL?.absoluteString + urlField.text = initURL?.absoluteString urlField.backgroundColor = .white urlField.textColor = .black urlField.layer.borderWidth = 1.0 urlField.layer.borderColor = UIColor.lightGray.cgColor urlField.layer.cornerRadius = 4 - closeButton.addTarget(self, action: #selector(self.close), for: .touchUpInside) + closeButton.addTarget(self, action: #selector(close), for: .touchUpInside) forwardButton.target = self - forwardButton.action = #selector(self.goForward) + forwardButton.action = #selector(goForward) forwardButton.target = self - forwardButton.action = #selector(self.goForward) + forwardButton.action = #selector(goForward) backButton.target = self - backButton.action = #selector(self.goBack) + backButton.action = #selector(goBack) reloadButton.target = self - reloadButton.action = #selector(self.reload) + reloadButton.action = #selector(reload) shareButton.target = self - shareButton.action = #selector(self.share) + shareButton.action = #selector(share) spinner.hidesWhenStopped = true spinner.isHidden = false @@ -184,53 +191,53 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega } public func prepareConstraints () { - containerWebView_BottomFullScreenConstraint = NSLayoutConstraint(item: self.containerWebView!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0) - containerWebView_TopFullScreenConstraint = NSLayoutConstraint(item: self.containerWebView!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0) + containerWebView_BottomFullScreenConstraint = NSLayoutConstraint(item: containerWebView!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0) + containerWebView_TopFullScreenConstraint = NSLayoutConstraint(item: containerWebView!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0) webView.translatesAutoresizingMaskIntoConstraints = false - let height = NSLayoutConstraint(item: self.webView!, attribute: .height, relatedBy: .equal, toItem: containerWebView, attribute: .height, multiplier: 1, constant: 0) - let width = NSLayoutConstraint(item: self.webView!, attribute: .width, relatedBy: .equal, toItem: containerWebView, attribute: .width, multiplier: 1, constant: 0) - let leftConstraint = NSLayoutConstraint(item: self.webView!, attribute: .leftMargin, relatedBy: .equal, toItem: containerWebView, attribute: .leftMargin, multiplier: 1, constant: 0) - let rightConstraint = NSLayoutConstraint(item: self.webView!, attribute: .rightMargin, relatedBy: .equal, toItem: containerWebView, attribute: .rightMargin, multiplier: 1, constant: 0) - let bottomContraint = NSLayoutConstraint(item: self.webView!, attribute: .bottomMargin, relatedBy: .equal, toItem: containerWebView, attribute: .bottomMargin, multiplier: 1, constant: 0) + let height = NSLayoutConstraint(item: webView!, attribute: .height, relatedBy: .equal, toItem: containerWebView, attribute: .height, multiplier: 1, constant: 0) + let width = NSLayoutConstraint(item: webView!, attribute: .width, relatedBy: .equal, toItem: containerWebView, attribute: .width, multiplier: 1, constant: 0) + let leftConstraint = NSLayoutConstraint(item: webView!, attribute: .leftMargin, relatedBy: .equal, toItem: containerWebView, attribute: .leftMargin, multiplier: 1, constant: 0) + let rightConstraint = NSLayoutConstraint(item: webView!, attribute: .rightMargin, relatedBy: .equal, toItem: containerWebView, attribute: .rightMargin, multiplier: 1, constant: 0) + let bottomContraint = NSLayoutConstraint(item: webView!, attribute: .bottomMargin, relatedBy: .equal, toItem: containerWebView, attribute: .bottomMargin, multiplier: 1, constant: 0) containerWebView.addConstraints([height, width, leftConstraint, rightConstraint, bottomContraint]) - webView_BottomFullScreenConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerWebView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0) - webView_TopFullScreenConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerWebView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0) + webView_BottomFullScreenConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: containerWebView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0) + webView_TopFullScreenConstraint = NSLayoutConstraint(item: webView!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: containerWebView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0) } public func prepareWebView() { - self.webView.options = webViewOptions - self.webView.prepare() + webView.options = webViewOptions + webView.prepare() if (browserOptions?.hideUrlBar)! { - self.urlField.isHidden = true - self.urlField.isEnabled = false + urlField.isHidden = true + urlField.isEnabled = false } if (browserOptions?.toolbarTop)! { if browserOptions?.toolbarTopBackgroundColor != "" { - self.toolbarTop.backgroundColor = color(fromHexString: (browserOptions?.toolbarTopBackgroundColor)!) + toolbarTop.backgroundColor = color(fromHexString: (browserOptions?.toolbarTopBackgroundColor)!) } } else { - self.toolbarTop.isHidden = true - self.toolbarTop_BottomToWebViewTopConstraint.isActive = false - self.containerWebView_TopFullScreenConstraint.isActive = true - self.webView_TopFullScreenConstraint.isActive = true + toolbarTop.isHidden = true + toolbarTop_BottomToWebViewTopConstraint.isActive = false + containerWebView_TopFullScreenConstraint.isActive = true + webView_TopFullScreenConstraint.isActive = true } if (browserOptions?.toolbarBottom)! { if browserOptions?.toolbarBottomBackgroundColor != "" { - self.toolbarBottom.backgroundColor = color(fromHexString: (browserOptions?.toolbarBottomBackgroundColor)!) + toolbarBottom.backgroundColor = color(fromHexString: (browserOptions?.toolbarBottomBackgroundColor)!) } - self.toolbarBottom.isTranslucent = (browserOptions?.toolbarBottomTranslucent)! + toolbarBottom.isTranslucent = (browserOptions?.toolbarBottomTranslucent)! } else { - self.toolbarBottom.isHidden = true - self.toolbarBottom_TopToWebViewBottomConstraint.isActive = false - self.containerWebView_BottomFullScreenConstraint.isActive = true - self.webView_BottomFullScreenConstraint.isActive = true + toolbarBottom.isHidden = true + toolbarBottom_TopToWebViewBottomConstraint.isActive = false + containerWebView_BottomFullScreenConstraint.isActive = true + webView_BottomFullScreenConstraint.isActive = true } if browserOptions?.closeButtonCaption != "" { @@ -242,12 +249,12 @@ public class InAppBrowserWebViewController: UIViewController, UIScrollViewDelega } public func prepareBeforeViewWillAppear() { - self.modalPresentationStyle = UIModalPresentationStyle(rawValue: (browserOptions?.presentationStyle)!)! - self.modalTransitionStyle = UIModalTransitionStyle(rawValue: (browserOptions?.transitionStyle)!)! + modalPresentationStyle = UIModalPresentationStyle(rawValue: (browserOptions?.presentationStyle)!)! + modalTransitionStyle = UIModalTransitionStyle(rawValue: (browserOptions?.transitionStyle)!)! } - public func loadUrl(url: URL, headers: [String: String]?) { - webView.loadUrl(url: url, headers: headers) + public func loadUrl(url: URL, headers: [String: String]?, allowingReadAccessTo: URL?) { + webView.loadUrl(url: url, headers: headers, allowingReadAccessTo: allowingReadAccessTo) updateUrlTextField(url: (webView.currentURL?.absoluteString)!) } diff --git a/ios/Classes/InAppWebView/FlutterWebViewController.swift b/ios/Classes/InAppWebView/FlutterWebViewController.swift index 2460ba97..721fb011 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -134,9 +134,9 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { } public func load(initialUrl: String?, initialFile: String?, initialData: [String: String]?, initialHeaders: [String: String]?) { - if initialFile != nil { + if let initialFile = initialFile { do { - try webView!.loadFile(url: initialFile!, headers: initialHeaders) + try webView?.loadFile(url: initialFile, headers: initialHeaders) } catch let error as NSError { dump(error) @@ -144,15 +144,22 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { return } - if initialData != nil { - let data = initialData!["data"]! - let mimeType = initialData!["mimeType"]! - let encoding = initialData!["encoding"]! - let baseUrl = initialData!["baseUrl"]! - webView!.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl) + if let initialData = initialData { + let data = initialData["data"]! + let mimeType = initialData["mimeType"]! + let encoding = initialData["encoding"]! + let baseUrl = initialData["baseUrl"]! + webView?.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl) } - else if let url = URL(string: initialUrl!) { - webView!.loadUrl(url: url, headers: initialHeaders) + else if let initialUrl = initialUrl, let url = URL(string: initialUrl) { + var allowingReadAccessToURL: URL? = nil + if let allowingReadAccessTo = webView?.options?.allowingReadAccessTo, url.scheme == "file" { + allowingReadAccessToURL = URL(string: allowingReadAccessTo) + if allowingReadAccessToURL?.scheme != "file" { + allowingReadAccessToURL = nil + } + } + webView?.loadUrl(url: url, headers: initialHeaders, allowingReadAccessTo: allowingReadAccessToURL) } } } diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index 6f280f7f..9f94cede 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -1838,18 +1838,22 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi }) } - public func loadUrl(url: URL, headers: [String: String]?) { - var request = URLRequest(url: url) - currentURL = url - if headers != nil { - if let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest { - for (key, value) in headers! { - mutableRequest.setValue(value, forHTTPHeaderField: key) + public func loadUrl(url: URL, headers: [String: String]?, allowingReadAccessTo: URL?) { + if #available(iOS 9.0, *), let allowingReadAccessTo = allowingReadAccessTo, url.scheme == "file", allowingReadAccessTo.scheme == "file" { + loadFileURL(url, allowingReadAccessTo: allowingReadAccessTo) + } else { + var request = URLRequest(url: url) + currentURL = url + if headers != nil { + if let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest { + for (key, value) in headers! { + mutableRequest.setValue(value, forHTTPHeaderField: key) + } + request = mutableRequest as URLRequest } - request = mutableRequest as URLRequest } + load(request) } - load(request) } public func postUrl(url: URL, postData: Data, completionHandler: @escaping () -> Void) { @@ -1887,7 +1891,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if assetURL == nil { throw NSError(domain: url + " asset file cannot be found!", code: 0) } - loadUrl(url: assetURL!, headers: headers) + loadUrl(url: assetURL!, headers: headers, allowingReadAccessTo: nil) } func setOptions(newOptions: InAppWebViewOptions, newOptionsMap: [String: Any]) { @@ -3167,7 +3171,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if !handledByClient, InAppWebView.windowWebViews[windowId] != nil { InAppWebView.windowWebViews.removeValue(forKey: windowId) if let url = navigationAction.request.url { - self.loadUrl(url: url, headers: nil) + self.loadUrl(url: url, headers: nil, allowingReadAccessTo: nil) } } } @@ -3435,9 +3439,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi let serverTrust = challenge.protectionSpace.serverTrust! var secResult = SecTrustResultType.invalid - SecTrustEvaluate(serverTrust, &secResult); + let secTrustEvaluateStatus = SecTrustEvaluate(serverTrust, &secResult); - if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { + if secTrustEvaluateStatus == errSecSuccess, let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateCFData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateCFData) let size = CFDataGetLength(serverCertificateCFData) diff --git a/ios/Classes/InAppWebView/InAppWebViewOptions.swift b/ios/Classes/InAppWebView/InAppWebViewOptions.swift index a9f0e1d2..718f7ca6 100755 --- a/ios/Classes/InAppWebView/InAppWebViewOptions.swift +++ b/ios/Classes/InAppWebView/InAppWebViewOptions.swift @@ -67,6 +67,7 @@ public class InAppWebViewOptions: Options { var limitsNavigationsToAppBoundDomains = false var useOnNavigationResponse = false var applePayAPIEnabled = false + var allowingReadAccessTo: String? = nil override init(){ super.init() diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift index 5d072299..d8e47f21 100644 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -32,7 +32,12 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { case "loadUrl": let url = arguments!["url"] as! String let headers = arguments!["headers"] as! [String: String] - webView?.loadUrl(url: URL(string: url)!, headers: headers) + let allowingReadAccessTo = arguments!["iosAllowingReadAccessTo"] as? String + var allowingReadAccessToURL: URL? = nil + if let allowingReadAccessTo = allowingReadAccessTo { + allowingReadAccessToURL = URL(string: allowingReadAccessTo) + } + webView?.loadUrl(url: URL(string: url)!, headers: headers, allowingReadAccessTo: allowingReadAccessToURL) result(true) break case "postUrl": diff --git a/lib/src/X509Certificate/asn1_decoder.dart b/lib/src/X509Certificate/asn1_decoder.dart index abe06d1c..9db93ea6 100644 --- a/lib/src/X509Certificate/asn1_decoder.dart +++ b/lib/src/X509Certificate/asn1_decoder.dart @@ -193,9 +193,9 @@ class ASN1DERDecoder { } /// Decode DER OID bytes to String with dot notation - static String decodeOid({required List contentData}) { + static String? decodeOid({required List contentData}) { if (contentData.isEmpty) { - return ""; + return null; } var oid = ""; @@ -393,6 +393,16 @@ class ASN1DERDecoder { return date; } + + static List sequenceContent({required List data}) { + var iterator = data.iterator; + iterator.moveNext(); + try { + return loadSubContent(iterator: iterator); + } catch (e) { + return data; + } + } } BigInt? toIntValue(List data) { diff --git a/lib/src/X509Certificate/asn1_der_encoder.dart b/lib/src/X509Certificate/asn1_der_encoder.dart new file mode 100644 index 00000000..3ea2b55c --- /dev/null +++ b/lib/src/X509Certificate/asn1_der_encoder.dart @@ -0,0 +1,27 @@ +import 'dart:typed_data'; +import 'asn1_identifier.dart'; + +class ASN1DEREncoder { + + static Uint8List encodeSequence({required Uint8List content}) { + var encoded = Uint8List.fromList([]); + encoded.add(ASN1Identifier.constructedTag | ASN1IdentifierTagNumber.SEQUENCE.toValue()); + encoded.addAll(contentLength(size: content.length)); + encoded.addAll(content); + return Uint8List.fromList(encoded); + } + + static Uint8List contentLength({required int size}) { + if (size >= 128) { + var lenBytes = Uint8List(size); + while (lenBytes.first == 0) { + lenBytes.removeAt(0); + } + int len = 0x80 | lenBytes.length; + return Uint8List(len)..addAll(lenBytes); + } else { + return Uint8List(size); + } + } + +} diff --git a/lib/src/X509Certificate/asn1_distinguished_names.dart b/lib/src/X509Certificate/asn1_distinguished_names.dart index 98035db7..1d8dd4d1 100644 --- a/lib/src/X509Certificate/asn1_distinguished_names.dart +++ b/lib/src/X509Certificate/asn1_distinguished_names.dart @@ -1,3 +1,5 @@ +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + class ASN1DistinguishedNames { final String _oid; final String _representation; @@ -67,4 +69,40 @@ class ASN1DistinguishedNames { @override int get hashCode => _oid.hashCode; + + /// Format subject/issuer information in RFC1779 + static String string({required ASN1Object block}) { + var result = ""; + var oidNames = ASN1DistinguishedNames.values; + for (var oidName in oidNames) { + var oidBlock = block.findOid(oidValue: oidName.oid()); + if (oidBlock == null) { + continue; + } + if (result.isNotEmpty) { + result += ", "; + } + result += oidName.representation(); + result += "="; + + String? value; + try { + value = oidBlock.parent?.sub?.last.value as String?; + } catch (e) {} + if (value != null) { + result += quote(value); + } + } + return result; + } + + static String quote(String string) { + var specialChars = [",", "+", "=", "\n", "<", ">", "#", ";", "\\"]; + for (var specialChar in specialChars) { + if (string.contains(specialChar)) { + return "\"" + string + "\""; + } + } + return string; + } } diff --git a/lib/src/X509Certificate/asn1_identifier.dart b/lib/src/X509Certificate/asn1_identifier.dart index 0512b785..078b40a2 100644 --- a/lib/src/X509Certificate/asn1_identifier.dart +++ b/lib/src/X509Certificate/asn1_identifier.dart @@ -206,15 +206,16 @@ class ASN1IdentifierTagNumber { class ASN1Identifier { int rawValue; + static int constructedTag = 0x20; ASN1Identifier(this.rawValue); bool isPrimitive() { - return (rawValue & 0x20) == 0; + return (rawValue & ASN1Identifier.constructedTag) == 0; } bool isConstructed() { - return (rawValue & 0x20) != 0; + return (rawValue & ASN1Identifier.constructedTag) != 0; } ASN1IdentifierTagNumber tagNumber() { diff --git a/lib/src/X509Certificate/asn1_object.dart b/lib/src/X509Certificate/asn1_object.dart index 7fc73d08..fc8c1a74 100644 --- a/lib/src/X509Certificate/asn1_object.dart +++ b/lib/src/X509Certificate/asn1_object.dart @@ -9,7 +9,7 @@ class ASN1Object { /// This property contains the DER encoded object Uint8List? encoded; - /// This property contains the decoded Swift object whenever is possible + /// This property contains the decoded object whenever is possible dynamic value; ASN1Identifier? identifier; @@ -96,4 +96,22 @@ class ASN1Object { } return null; } + + String? get asString { + var string = value as String?; + if (string != null) { + return string; + } + + if (sub != null && sub!.length > 0) { + for (var item in sub!) { + var itemAsString = item.asString; + if (itemAsString != null) { + return itemAsString; + } + } + } + + return null; + } } diff --git a/lib/src/X509Certificate/main.dart b/lib/src/X509Certificate/main.dart index 2ee58377..ea3efce6 100644 --- a/lib/src/X509Certificate/main.dart +++ b/lib/src/X509Certificate/main.dart @@ -1,3 +1,5 @@ +// from https://github.com/filom/ASN1Decoder + export 'asn1_decoder.dart'; export 'asn1_distinguished_names.dart'; export 'asn1_identifier.dart'; @@ -7,3 +9,4 @@ export 'key_usage.dart'; export 'x509_certificate.dart'; export 'x509_extension.dart'; export 'x509_public_key.dart'; +export 'asn1_der_encoder.dart'; \ No newline at end of file diff --git a/lib/src/X509Certificate/oid.dart b/lib/src/X509Certificate/oid.dart index 52d6427c..423aa710 100644 --- a/lib/src/X509Certificate/oid.dart +++ b/lib/src/X509Certificate/oid.dart @@ -100,6 +100,11 @@ class OID { OID.codeSigning, OID.emailProtection, OID.timeStamping, + OID.pkcsSha256, + OID.sha2Family, + OID.sha3_244, + OID.sha3_256, + OID.sha3_384, ].toSet(); static OID? fromValue(String? value) { @@ -174,10 +179,15 @@ class OID { static const dateOfBirth = const OID._internal("1.3.6.1.5.5.7.9.1"); static const desCBC = const OID._internal("1.3.14.3.2.7"); static const sha1 = const OID._internal("1.3.14.3.2.26"); + static const pkcsSha256 = const OID._internal("1.3.6.1.4.1.22554.1.2.1"); + static const sha2Family = const OID._internal("1.3.6.1.4.1.22554.1.2"); + static const sha3_244 = const OID._internal("2.16.840.1.101.3.4.2.7"); + static const sha3_256 = const OID._internal("2.16.840.1.101.3.4.2.8"); + static const sha3_384 = const OID._internal("2.16.840.1.101.3.4.2.9"); + static const md5 = const OID._internal("0.2.262.1.10.1.3.2"); static const sha256 = const OID._internal("2.16.840.1.101.3.4.2.1"); static const sha384 = const OID._internal("2.16.840.1.101.3.4.2.2"); static const sha512 = const OID._internal("2.16.840.1.101.3.4.2.3"); - static const md5 = const OID._internal("1.2.840.113549.2.5"); static const VeriSignEVpolicy = const OID._internal("2.16.840.1.113733.1.7.23.6"); static const extendedValidation = const OID._internal("2.23.140.1.1"); @@ -278,10 +288,15 @@ class OID { "1.3.6.1.5.5.7.9.1": "dateOfBirth", "1.3.14.3.2.7": "desCBC", "1.3.14.3.2.26": "sha1", + "1.3.6.1.4.1.22554.1.2.1": "pkcsSha256", + "1.3.6.1.4.1.22554.1.2": "sha2Family", + "2.16.840.1.101.3.4.2.7": "sha3_244", + "2.16.840.1.101.3.4.2.8": "sha3_256", + "2.16.840.1.101.3.4.2.9": "sha3_384", "2.16.840.1.101.3.4.2.1": "sha256", "2.16.840.1.101.3.4.2.2": "sha384", "2.16.840.1.101.3.4.2.3": "sha512", - "1.2.840.113549.2.5": "md5", + "0.2.262.1.10.1.3.2": "md5", "2.16.840.1.113733.1.7.23.6": "VeriSign EV policy", "2.23.140.1.1": "extendedValidation", "2.23.140.1.2.2": "organizationValidated", diff --git a/lib/src/X509Certificate/x509_certificate.dart b/lib/src/X509Certificate/x509_certificate.dart index 4375e064..02070cf3 100644 --- a/lib/src/X509Certificate/x509_certificate.dart +++ b/lib/src/X509Certificate/x509_certificate.dart @@ -131,7 +131,7 @@ class X509Certificate { String? get issuerDistinguishedName { var issuerBlock = block1?.atIndex(X509BlockPosition.issuer); if (issuerBlock != null) { - return blockDistinguishedName(block: issuerBlock); + return ASN1DistinguishedNames.string(block: issuerBlock); } return null; } @@ -175,7 +175,7 @@ class X509Certificate { String? get subjectDistinguishedName { var subjectBlock = block1?.atIndex(X509BlockPosition.subject); if (subjectBlock != null) { - return blockDistinguishedName(block: subjectBlock); + return ASN1DistinguishedNames.string(block: subjectBlock); } return null; } @@ -278,11 +278,11 @@ class X509Certificate { ///Gets a collection of subject alternative names from the SubjectAltName extension, (OID = 2.5.29.17). List get subjectAlternativeNames => - extensionObject(oid: OID.subjectAltName)?.valueAsStrings ?? []; + extensionObject(oid: OID.subjectAltName)?.alternativeNameAsStrings ?? []; ///Gets a collection of issuer alternative names from the IssuerAltName extension, (OID = 2.5.29.18). List get issuerAlternativeNames => - extensionObject(oid: OID.issuerAltName)?.valueAsStrings ?? []; + extensionObject(oid: OID.issuerAltName)?.alternativeNameAsStrings ?? []; ///Gets the informations of the public key from this certificate. X509PublicKey? get publicKey { @@ -321,81 +321,24 @@ class X509Certificate { ///Gets the certificate constraints path length from the ///critical BasicConstraints extension, (OID = 2.5.29.19). - int get basicConstraints { - var sub = extensionObject(oid: OID.basicConstraints) - ?.block - ?.lastSub() - ?.lastSub() - ?.lastSub(); - if (sub != null) { - if (sub.value is List) { - return (sub.value as List).length; - } - } - return -1; - } + BasicConstraintExtension? get basicConstraints => extensionObject(oid: OID.basicConstraints) as BasicConstraintExtension?; ///Gets the raw bits from the Subject Key Identifier (SKID) extension, (OID = 2.5.29.14). - List get subjectKeyIdentifier => - extensionObject(oid: OID.subjectKeyIdentifier) - ?.block - ?.lastSub() - ?.lastSub() - ?.value ?? - []; + SubjectKeyIdentifierExtension? get subjectKeyIdentifier => extensionObject(oid: OID.subjectKeyIdentifier) as SubjectKeyIdentifierExtension?; ///Gets the raw bits from the Authority Key Identifier extension, (OID = 2.5.29.35). - List get authorityKeyIdentifier => - extensionObject(oid: OID.authorityKeyIdentifier) - ?.block - ?.lastSub() - ?.lastSub() - ?.firstSub() - ?.value ?? - []; + AuthorityKeyIdentifierExtension? get authorityKeyIdentifier => extensionObject(oid: OID.authorityKeyIdentifier) as AuthorityKeyIdentifierExtension?; ///Gets the list of certificate policies from the CertificatePolicies extension, (OID = 2.5.29.32). - List get certificatePolicies => - extensionObject(oid: OID.certificatePolicies) - ?.block - ?.lastSub() - ?.firstSub() - ?.sub - ?.map((e) => e.firstSub()?.value as String) - .toList() ?? - []; + CertificatePoliciesExtension? get certificatePolicies => extensionObject(oid: OID.certificatePolicies) as CertificatePoliciesExtension?; ///Gets the list of CRL distribution points from the CRLDistributionPoints extension, (OID = 2.5.29.31). - List get cRLDistributionPoints => - extensionObject(oid: OID.cRLDistributionPoints) - ?.block - ?.lastSub() - ?.firstSub() - ?.sub - ?.map((e) => e.firstSub()?.firstSub()?.firstSub()?.value as String) - .toList() ?? - []; + CRLDistributionPointsExtension? get cRLDistributionPoints => extensionObject(oid: OID.cRLDistributionPoints) as CRLDistributionPointsExtension?; ///Gets the map of the format (as a key) and location (as a value) of additional information ///about the CA who issued the certificate in which this extension appears ///from the AuthorityInfoAccess extension, (OID = 1.3.6.1.5.5.5.7.1.1). - Map get authorityInfoAccess { - var result = {}; - var sub = extensionObject(oid: OID.authorityInfoAccess) - ?.block - ?.lastSub() - ?.firstSub() - ?.sub; - if (sub != null) { - sub.forEach((element) { - if (element.subCount() > 1) { - result.putIfAbsent( - element.subAtIndex(0)!.value, () => element.subAtIndex(1)!.value); - } - }); - } - return result; - } + AuthorityInfoAccessExtension? get authorityInfoAccess => extensionObject(oid: OID.authorityInfoAccess) as AuthorityInfoAccessExtension?; List? get extensionBlocks => block1?.atIndex(X509BlockPosition.extensions)?.subAtIndex(0)?.sub; @@ -410,46 +353,25 @@ class X509Certificate { ?.findOid(oidValue: oidValue) ?.parent; if (block != null) { + if (oidValue == OID.basicConstraints.toValue()) { + return BasicConstraintExtension(block: block); + } else if (oidValue == OID.subjectKeyIdentifier.toValue()) { + return SubjectKeyIdentifierExtension(block: block); + } else if (oidValue == OID.authorityInfoAccess.toValue()) { + return AuthorityInfoAccessExtension(block: block); + } else if (oidValue == OID.authorityKeyIdentifier.toValue()) { + return AuthorityKeyIdentifierExtension(block: block); + } else if (oidValue == OID.certificatePolicies.toValue()) { + return CertificatePoliciesExtension(block: block); + } else if (oidValue == OID.cRLDistributionPoints.toValue()) { + return CRLDistributionPointsExtension(block: block); + } return X509Extension(block: block); } } return null; } - ///Format subject/issuer information in RFC1779 - String blockDistinguishedName({required ASN1Object block}) { - var result = ""; - for (var oidName in ASN1DistinguishedNames.values) { - var oidBlock = block.findOid(oidValue: oidName.oid()); - if (oidBlock != null) { - if (result.isNotEmpty) { - result += ", "; - } - result += oidName.representation(); - result += "="; - - var sub = oidBlock.parent?.sub; - if (sub != null && sub.length > 0) { - var value = sub.last.value as String?; - if (value != null) { - var specialChar = ",+=\n<>#;\\"; - var quote = ""; - for (var i = 0; i < value.length; i++) { - var char = value[i]; - if (specialChar.contains(char)) { - quote = "\""; - } - } - result += quote; - result += value; - result += quote; - } - } - } - } - return result; - } - @override String toString() { return description; @@ -457,7 +379,7 @@ class X509Certificate { Map toMap() { return { - "basicConstraints": basicConstraints, + "basicConstraints": basicConstraints?.toMap(), "subjectAlternativeNames": subjectAlternativeNames, "issuerAlternativeNames": issuerAlternativeNames, "extendedKeyUsage": extendedKeyUsage, @@ -476,11 +398,11 @@ class X509Certificate { "nonCriticalExtensionOIDs": nonCriticalExtensionOIDs, "encoded": encoded, "publicKey": publicKey?.toMap(), - "subjectKeyIdentifier": subjectKeyIdentifier, - "authorityKeyIdentifier": authorityKeyIdentifier, - "certificatePolicies": certificatePolicies, - "cRLDistributionPoints": cRLDistributionPoints, - "authorityInfoAccess": authorityInfoAccess, + "subjectKeyIdentifier": subjectKeyIdentifier?.toMap(), + "authorityKeyIdentifier": authorityKeyIdentifier?.toMap(), + "certificatePolicies": certificatePolicies?.toMap(), + "cRLDistributionPoints": cRLDistributionPoints?.toMap(), + "authorityInfoAccess": authorityInfoAccess?.toMap(), }; } diff --git a/lib/src/X509Certificate/x509_extension.dart b/lib/src/X509Certificate/x509_extension.dart index 3254561a..49a5afcf 100644 --- a/lib/src/X509Certificate/x509_extension.dart +++ b/lib/src/X509Certificate/x509_extension.dart @@ -1,6 +1,12 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + import 'x509_certificate.dart'; import 'asn1_object.dart'; import 'oid.dart'; +import 'asn1_distinguished_names.dart'; class X509Extension { ASN1Object? block; @@ -59,4 +65,413 @@ class X509Extension { } return result; } + + // Used for SubjectAltName and IssuerAltName + // Every name can be one of these subtype: + // - otherName [0] INSTANCE OF OTHER-NAME, + // - rfc822Name [1] IA5String, + // - dNSName [2] IA5String, + // - x400Address [3] ORAddress, + // - directoryName [4] Name, + // - ediPartyName [5] EDIPartyName, + // - uniformResourceIdentifier [6] IA5String, + // - IPAddress [7] OCTET STRING, + // - registeredID [8] OBJECT IDENTIFIER + // + // Result does not support: x400Address and ediPartyName + // + List get alternativeNameAsStrings { + List result = []; + var sub = []; + try { + sub = block?.sub?.last.sub?.last.sub ?? []; + } catch (e) {} + for (var item in sub) { + var name = generalName(item: item); + if (name != null) { + result.add(name); + } + } + return result; + } + + String? generalName({required ASN1Object item}) { + var nameType = item.identifier?.tagNumber().toValue(); + if (nameType == null) { + return null; + } + switch (nameType) { + case 0: + String? name; + try { + name = item.sub?.last.sub?.last.value as String?; + } catch (e) {} + return name; + case 1: + case 2: + case 6: + String? name = item.value as String?; + return name; + case 4: + return ASN1DistinguishedNames.string(block: item); + case 7: + var ip = item.value as List?; + if (ip != null) { + return ip.map((e) => e.toString()).join("."); + } + break; + case 8: + var value = item.value as String?; + if (value != null) { + try { + var data = utf8.encode(value); + var oid = ASN1DERDecoder.decodeOid(contentData: data); + return oid; + } catch (e) {} + } + break; + default: + return null; + } + return null; + } } + +/// Recognition for Basic Constraint Extension (2.5.29.19) +class BasicConstraintExtension extends X509Extension { + BasicConstraintExtension({required block}) : super(block: block); + + bool get isCA => valueAsBlock?.subAtIndex(0)?.subAtIndex(0)?.value as bool? ?? false; + + int? get pathLenConstraint { + var data = valueAsBlock?.subAtIndex(0)?.subAtIndex(0)?.value as List?; + if (data != null) { + return data.length; + } + return null; + } + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "isCA": isCA, + "pathLenConstraint": pathLenConstraint, + }; + } + + Map toJson() { + return toMap(); + } +} + +/// Recognition for Subject Key Identifier Extension (2.5.29.14) +class SubjectKeyIdentifierExtension extends X509Extension { + SubjectKeyIdentifierExtension({required block}) : super(block: block); + + @override + List? get value { + var rawValue = valueAsBlock?.encoded; + if (rawValue != null) { + return ASN1DERDecoder.sequenceContent(data: rawValue.toList()); + } + return null; + } + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "value": value, + }; + } + + Map toJson() { + return toMap(); + } +} + +class AuthorityInfoAccess { + String method; + String location; + + AuthorityInfoAccess({required this.method, required this.location}); + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "method": method, + "location": location, + }; + } + + Map toJson() { + return toMap(); + } +} + +/// Recognition for Authority Info Access Extension (1.3.6.1.5.5.7.1.1) +class AuthorityInfoAccessExtension extends X509Extension { + AuthorityInfoAccessExtension({required block}) : super(block: block); + + List? get infoAccess { + if (valueAsBlock == null) { + return null; + } + var subs = valueAsBlock!.subAtIndex(0)?.sub ?? []; + List result = []; + subs.forEach((sub) { + var oidData = sub.subAtIndex(0)?.encoded; + var nameBlock = sub.subAtIndex(1); + if (oidData == null || nameBlock == null) { + return; + } + var oid = ASN1DERDecoder.decodeOid(contentData: oidData.toList()); + var location = generalName(item: nameBlock); + if (oid != null && location != null) { + result.add(AuthorityInfoAccess(method: oid, location: location)); + } + }); + return result; + } + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "infoAccess": infoAccess?.map((e) => e.toMap()).toList(), + }; + } + + Map toJson() { + return toMap(); + } +} + +/// Recognition for Authority Key Identifier Extension (2.5.29.35) +class AuthorityKeyIdentifierExtension extends X509Extension { + AuthorityKeyIdentifierExtension({required block}) : super(block: block); + + ///AuthorityKeyIdentifier ::= SEQUENCE { + /// keyIdentifier [0] KeyIdentifier OPTIONAL, + /// authorityCertIssuer [1] GeneralNames OPTIONAL, + /// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL } + List? get keyIdentifier { + var sequence = valueAsBlock?.subAtIndex(0)?.sub; + if (sequence == null) { + return null; + } + ASN1Object? sub; + try { + sub = sequence.firstWhere((element) => element.identifier?.tagNumber().toValue() == 0); + return sub.encoded; + } catch (e) {} + return null; + } + + List? get certificateIssuer { + var sequence = valueAsBlock?.subAtIndex(0)?.sub; + if (sequence == null) { + return null; + } + ASN1Object? sub; + try { + sub = sequence.firstWhere((element) => element.identifier?.tagNumber().toValue() == 1); + List? result; + if (sub.sub != null) { + result = []; + sub.sub?.forEach((e) { + var name = generalName(item: e); + if (name != null) { + result!.add(name); + } + }); + } + return result; + } catch (e) {} + return null; + } + + List? get serialNumber { + var sequence = valueAsBlock?.subAtIndex(0)?.sub; + if (sequence == null) { + return null; + } + ASN1Object? sub; + try { + sub = sequence.firstWhere((element) => element.identifier?.tagNumber().toValue() == 2); + return sub.encoded; + } catch (e) {} + return null; + } + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "keyIdentifier": keyIdentifier, + "certificateIssuer": certificateIssuer, + "serialNumber": serialNumber, + }; + } + + Map toJson() { + return toMap(); + } +} + +class CertificatePolicyQualifier { + String oid; + String? value; + + CertificatePolicyQualifier({required this.oid, this.value}); + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "oid": oid, + "value": value, + }; + } + + Map toJson() { + return toMap(); + } +} +class CertificatePolicy { + String oid; + List? qualifiers; + + CertificatePolicy({required this.oid, this.qualifiers}); + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "oid": oid, + "qualifiers": qualifiers?.map((e) => e.toMap()).toList(), + }; + } + + Map toJson() { + return toMap(); + } +} + +/// Recognition for Certificate Policies Extension (2.5.29.32) +class CertificatePoliciesExtension extends X509Extension { + CertificatePoliciesExtension({required block}) : super(block: block); + + List? get policies { + if (valueAsBlock == null) { + return null; + } + var subs = valueAsBlock!.subAtIndex(0)?.sub ?? []; + + List result = []; + subs.forEach((sub) { + var data = sub.subAtIndex(0)?.encoded; + String? oid; + if (data != null) { + oid = ASN1DERDecoder.decodeOid(contentData: data.toList()); + if (oid == null) { + return; + } + } else { + return; + } + + List? qualifiers; + var subQualifiers = sub.subAtIndex(1); + if (subQualifiers != null && subQualifiers.sub != null) { + qualifiers = []; + subQualifiers.sub!.forEach((sub) { + var rawValue = sub.subAtIndex(0)?.encoded; + String? oid; + if (rawValue != null) { + oid = ASN1DERDecoder.decodeOid(contentData: rawValue.toList()); + if (oid == null) { + return; + } + var value = sub.subAtIndex(1)?.asString; + qualifiers!.add(CertificatePolicyQualifier(oid: oid, value: value)); + } + }); + } + result.add(CertificatePolicy(oid: oid, qualifiers: qualifiers)); + }); + return result; + } + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "policies": policies?.map((e) => e.toMap()).toList(), + }; + } + + Map toJson() { + return toMap(); + } +} + +class CRLDistributionPointsExtension extends X509Extension { + CRLDistributionPointsExtension({required block}) : super(block: block); + + List? get crls { + if (valueAsBlock == null) { + return null; + } + var subs = valueAsBlock!.subAtIndex(0)?.sub ?? []; + List result = []; + subs.forEach((sub) { + var asString = sub.asString; + if (asString != null) { + result.add(asString); + } + }); + return result; + } + + @override + String toString() { + return toMap().toString(); + } + + Map toMap() { + return { + "crls": crls, + }; + } + + Map toJson() { + return toMap(); + } +} \ No newline at end of file diff --git a/lib/src/X509Certificate/x509_public_key.dart b/lib/src/X509Certificate/x509_public_key.dart index ffd61698..079d0a3e 100644 --- a/lib/src/X509Certificate/x509_public_key.dart +++ b/lib/src/X509Certificate/x509_public_key.dart @@ -1,5 +1,7 @@ import 'dart:typed_data'; +import 'package:flutter_inappwebview/src/X509Certificate/asn1_der_encoder.dart'; + import 'asn1_decoder.dart'; import 'asn1_object.dart'; import 'oid.dart'; @@ -15,6 +17,14 @@ class X509PublicKey { String? get algParams => pkBlock?.subAtIndex(0)?.subAtIndex(1)?.value; + Uint8List? get derEncodedKey { + var value = pkBlock?.encoded; + if (value != null) { + return ASN1DEREncoder.encodeSequence(content: value); + } + return null; + } + Uint8List? get encoded { var oid = OID.fromValue(algOid); var keyData = pkBlock?.subAtIndex(1)?.value ?? null; diff --git a/lib/src/headless_in_app_webview.dart b/lib/src/headless_in_app_webview.dart index 8652d969..cc9f89a0 100644 --- a/lib/src/headless_in_app_webview.dart +++ b/lib/src/headless_in_app_webview.dart @@ -70,7 +70,6 @@ class HeadlessInAppWebView implements WebView { this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, this.androidOnScaleChanged, - this.androidOnRequestFocus, this.androidOnReceivedIcon, this.androidOnReceivedTouchIconUrl, this.androidOnJsBeforeUnload, @@ -230,9 +229,6 @@ class HeadlessInAppWebView implements WebView { @override final void Function(InAppWebViewController controller)? onWindowBlur; - @override - final void Function(InAppWebViewController controller)? androidOnRequestFocus; - @override final void Function(InAppWebViewController controller, String url)? onDownloadStart; diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index 35db52d0..49f71d8a 100755 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -686,14 +686,6 @@ class InAppBrowser { ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) void androidOnScaleChanged(double oldScale, double newScale) {} - ///Event fired when there is a request to display and focus for this WebView. - ///This may happen due to another WebView opening a link in this WebView and requesting that this WebView be displayed. - /// - ///**NOTE**: available only on Android. - /// - ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onRequestFocus(android.webkit.WebView) - void androidOnRequestFocus() {} - ///Event fired when there is new favicon for the current page. /// ///[icon] represents the favicon for the current page. diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index abe84b18..edd8c180 100755 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -82,7 +82,6 @@ class InAppWebView extends StatefulWidget implements WebView { this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, this.androidOnScaleChanged, - this.androidOnRequestFocus, this.androidOnReceivedIcon, this.androidOnReceivedTouchIconUrl, this.androidOnJsBeforeUnload, @@ -191,9 +190,6 @@ class InAppWebView extends StatefulWidget implements WebView { @override final void Function(InAppWebViewController controller)? onWindowBlur; - @override - final void Function(InAppWebViewController controller)? androidOnRequestFocus; - @override final void Function(InAppWebViewController controller, Uint8List icon)? androidOnReceivedIcon; diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index f74e5083..8a15b7be 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -373,11 +373,6 @@ class InAppWebViewController { else if (_inAppBrowser != null) _inAppBrowser!.androidOnScaleChanged(oldScale, newScale); break; - case "onRequestFocus": - if (_webview != null && _webview!.androidOnRequestFocus != null) - _webview!.androidOnRequestFocus!(this); - else if (_inAppBrowser != null) _inAppBrowser!.androidOnRequestFocus(); - break; case "onReceivedIcon": Uint8List icon = Uint8List.fromList(call.arguments["icon"].cast()); @@ -1250,15 +1245,26 @@ class InAppWebViewController { ///Loads the given [url] with optional [headers] specified as a map from name to value. /// + ///[iosAllowingReadAccessTo], used in combination with [url] (using the `file://` scheme), + ///is an iOS-specific argument that represents the URL from which to read the web content. + ///This URL must be a file-based URL (using the `file://` scheme). + ///Specify the same value as the URL parameter to prevent WebView from reading any other content. + ///Specify a directory to give WebView permission to read additional files in the specified directory. + /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#loadUrl(java.lang.String) /// - ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414954-load + ///**Official iOS API**: + ///- https://developer.apple.com/documentation/webkit/wkwebview/1414954-load + ///- if [iosAllowingReadAccessTo] is used, https://developer.apple.com/documentation/webkit/wkwebview/1414973-loadfileurl Future loadUrl( - {required String url, Map headers = const {}}) async { + {required String url, Map headers = const {}, String? iosAllowingReadAccessTo}) async { assert(url.isNotEmpty); + assert(iosAllowingReadAccessTo == null || iosAllowingReadAccessTo.startsWith("file://")); + Map args = {}; args.putIfAbsent('url', () => url); args.putIfAbsent('headers', () => headers); + args.putIfAbsent('iosAllowingReadAccessTo', () => iosAllowingReadAccessTo); await _channel.invokeMethod('loadUrl', args); } diff --git a/lib/src/webview.dart b/lib/src/webview.dart index ac7a57d4..01ca7007 100644 --- a/lib/src/webview.dart +++ b/lib/src/webview.dart @@ -529,14 +529,6 @@ abstract class WebView { InAppWebViewController controller, double oldScale, double newScale)? androidOnScaleChanged; - ///Event fired when there is a request to display and focus for this WebView. - ///This may happen due to another WebView opening a link in this WebView and requesting that this WebView be displayed. - /// - ///**NOTE**: available only on Android. - /// - ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onRequestFocus(android.webkit.WebView) - final void Function(InAppWebViewController controller)? androidOnRequestFocus; - ///Event fired when there is new favicon for the current page. /// ///[icon] represents the favicon for the current page. @@ -696,7 +688,6 @@ abstract class WebView { this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, this.androidOnScaleChanged, - this.androidOnRequestFocus, this.androidOnReceivedIcon, this.androidOnReceivedTouchIconUrl, this.androidOnJsBeforeUnload, diff --git a/lib/src/webview_options.dart b/lib/src/webview_options.dart index 07d5b3c1..2b5cec68 100755 --- a/lib/src/webview_options.dart +++ b/lib/src/webview_options.dart @@ -923,6 +923,12 @@ class IOSInAppWebViewOptions ///**NOTE**: available on iOS 13.0+. bool applePayAPIEnabled; + ///Used in combination with [WebView.initialUrl] (using the `file://` scheme), it represents the URL from which to read the web content. + ///This URL must be a file-based URL (using the `file://` scheme). + ///Specify the same value as the [WebView.initialUrl] parameter to prevent WebView from reading any other content. + ///Specify a directory to give WebView permission to read additional files in the specified directory. + String? allowingReadAccessTo; + IOSInAppWebViewOptions( {this.disallowOverScroll = false, this.enableViewportScale = false, @@ -953,7 +959,10 @@ class IOSInAppWebViewOptions this.pageZoom = 1.0, this.limitsNavigationsToAppBoundDomains = false, this.useOnNavigationResponse = false, - this.applePayAPIEnabled = false}); + this.applePayAPIEnabled = false, + this.allowingReadAccessTo}) { + assert(allowingReadAccessTo == null || allowingReadAccessTo!.startsWith("file://")); + } @override Map toMap() { @@ -995,6 +1004,7 @@ class IOSInAppWebViewOptions "limitsNavigationsToAppBoundDomains": limitsNavigationsToAppBoundDomains, "useOnNavigationResponse": useOnNavigationResponse, "applePayAPIEnabled": applePayAPIEnabled, + "allowingReadAccessTo": allowingReadAccessTo, }; } @@ -1050,6 +1060,7 @@ class IOSInAppWebViewOptions options.limitsNavigationsToAppBoundDomains = map["limitsNavigationsToAppBoundDomains"]; options.useOnNavigationResponse = map["useOnNavigationResponse"]; options.applePayAPIEnabled = map["applePayAPIEnabled"]; + options.allowingReadAccessTo = map["allowingReadAccessTo"]; return options; }