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;
}