Commit ab3b5c39 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

updated android safe browsing, updated ios browser and SafariViewController...

updated android safe browsing, updated ios browser and SafariViewController options, added onSafeBrowsingHit event for android
parent 153ab602
This diff is collapsed.
......@@ -18,6 +18,7 @@
- Added new iOS WebView options: `applicationNameForUserAgent`, `isFraudulentWebsiteWarningEnabled`, `selectionGranularity`, `dataDetectorTypes`, `preferredContentMode`
- Added `onGeolocationPermissionsShowPrompt` event and `GeolocationPermissionShowPromptResponse` class (available only for Android)
- Added `startSafeBrowsing`, `setSafeBrowsingWhitelist` and `getSafeBrowsingPrivacyPolicyUrl` methods (available only for Android)
- Added `onSafeBrowsingHit` event (available only for Android)
- Added `onJsAlert`, `onJsConfirm` and `onJsPrompt` events to manage javascript popup dialogs
### BREAKING CHANGES
......
......@@ -10,8 +10,12 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
......@@ -41,11 +45,19 @@ public class ContentBlockerHandler {
this.ruleList = newRuleList;
}
public WebResourceResponse checkUrl(final InAppWebView webView, String url, ContentBlockerTriggerResourceType responseResourceType) throws URISyntaxException, InterruptedException {
public WebResourceResponse checkUrl(final InAppWebView webView, String url, ContentBlockerTriggerResourceType responseResourceType) throws URISyntaxException, InterruptedException, MalformedURLException {
if (webView.options.contentBlockers == null)
return null;
URI u = new URI(url);
URI u;
try {
u = new URI(url);
} catch (URISyntaxException e) {
String[] urlSplitted = url.split(":");
String scheme = urlSplitted[0];
URL tempUrl = new URL(url.replace(scheme, "https"));
u = new URI(scheme, tempUrl.getUserInfo(), tempUrl.getHost(), tempUrl.getPort(), tempUrl.getPath(), tempUrl.getQuery(), tempUrl.getRef());
}
String host = u.getHost();
int port = u.getPort();
String scheme = u.getScheme();
......@@ -182,12 +194,12 @@ public class ContentBlockerHandler {
return null;
}
public WebResourceResponse checkUrl(final InAppWebView webView, String url) throws URISyntaxException, InterruptedException {
public WebResourceResponse checkUrl(final InAppWebView webView, String url) throws URISyntaxException, InterruptedException, MalformedURLException {
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromUrl(webView, url);
return checkUrl(webView, url, responseResourceType);
}
public WebResourceResponse checkUrl(final InAppWebView webView, String url, String contentType) throws URISyntaxException, InterruptedException {
public WebResourceResponse checkUrl(final InAppWebView webView, String url, String contentType) throws URISyntaxException, InterruptedException, MalformedURLException {
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromContentType(contentType);
return checkUrl(webView, url, responseResourceType);
}
......
......@@ -3,6 +3,7 @@ package com.pichillilorenzo.flutter_inappbrowser;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.webkit.WebChromeClient;
......@@ -226,6 +227,12 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
else
result.success(false);
break;
case "getSafeBrowsingPrivacyPolicyUrl":
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
result.success(webView.getSafeBrowsingPrivacyPolicyUrl().toString());
} else
result.success(null);
break;
case "dispose":
dispose();
result.success(true);
......
......@@ -447,7 +447,7 @@ public class InAppWebChromeClient extends WebChromeClient {
obj.put("sourceURL", consoleMessage.sourceId());
obj.put("lineNumber", consoleMessage.lineNumber());
obj.put("message", consoleMessage.message());
obj.put("messageLevel", consoleMessage.messageLevel().toString());
obj.put("messageLevel", consoleMessage.messageLevel().ordinal());
getChannel().invokeMethod("onConsoleMessage", obj);
return true;
}
......
......@@ -10,6 +10,7 @@ import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.HttpAuthHandler;
import android.webkit.SafeBrowsingResponse;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebResourceRequest;
......@@ -306,6 +307,58 @@ public class InAppWebViewClient extends WebViewClient {
webView.scale = newScale;
}
@RequiresApi(api = Build.VERSION_CODES.O_MR1)
@Override
public void onSafeBrowsingHit(final WebView view, WebResourceRequest request, int threatType, final SafeBrowsingResponse callback) {
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("url", request.getUrl().toString());
obj.put("threatType", threatType);
getChannel().invokeMethod("onSafeBrowsingHit", obj, new MethodChannel.Result() {
@Override
public void success(Object response) {
if (response != null) {
Map<String, Object> responseMap = (Map<String, Object>) response;
Boolean report = (Boolean) responseMap.get("report");
Integer action = (Integer) responseMap.get("action");
Log.d(LOG_TAG, "\n\nreport: " + report);
Log.d(LOG_TAG, "\n\naction: " + action);
report = report != null ? report : true;
if (action != null) {
switch (action) {
case 0:
callback.backToSafety(report);
return;
case 1:
callback.proceed(report);
return;
case 2:
callback.showInterstitial(report);
return;
}
}
}
callback.showInterstitial(true);
}
@Override
public void error(String s, String s1, Object o) {
Log.e(LOG_TAG, s + ", " + s1);
}
@Override
public void notImplemented() {
callback.showInterstitial(true);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
......
......@@ -65,6 +65,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
child: InAppWebView(
//initialUrl: "https://www.youtube.com/embed/M7lc1UVf-VE?playsinline=1",
//initialUrl: "https://flutter.dev/",
//initialUrl: "chrome://safe-browsing/match?type=malware",
initialFile: "assets/index.html",
initialHeaders: {},
initialOptions: [
......@@ -88,6 +89,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
appCacheEnabled: true,
domStorageEnabled: true,
geolocationEnabled: true,
safeBrowsingEnabled: true,
//blockNetworkImage: true,
),
iOSInAppWebViewOptions(
......@@ -97,6 +99,9 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
if (Platform.isAndroid)
webView.startSafeBrowsing();
webView.addJavaScriptHandler('handlerFoo', (args) {
return new Foo(bar: 'bar_value', baz: 'baz_value');
});
......@@ -142,7 +147,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel}
messageLevel: ${consoleMessage.messageLevel.toValue()}
""");
},
onDownloadStart: (InAppWebViewController controller, String url) async {
......@@ -210,6 +215,10 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
JsPromptResponseAction action = await createPromptDialog(context, message);
return new JsPromptResponse(handledByClient: true, action: action, value: _textFieldController.text);
},
onSafeBrowsingHit: (InAppWebViewController controller, String url, SafeBrowsingThreat threatType) async {
SafeBrowsingResponseAction action = SafeBrowsingResponseAction.BACK_TO_SAFETY;
return new SafeBrowsingResponse(report: true, action: action);
},
),
),
),
......
......@@ -63,7 +63,7 @@ class MyInappBrowser extends InAppBrowser {
sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel}
messageLevel: ${consoleMessage.messageLevel.toValue()}
""");
}
......
......@@ -394,6 +394,18 @@ class InAppBrowser {
}
///Event fires when the webview notifies that a loading URL has been flagged by Safe Browsing.
///The default behavior is to show an interstitial to the user, with the reporting checkbox visible.
///
///[url] represents the url of the request.
///
///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat].
///
///**NOTE**: available only for Android.
Future<SafeBrowsingResponse> onSafeBrowsingHit(String url, SafeBrowsingThreat threatType) {
}
void throwIsAlreadyOpened({String message = ''}) {
if (this.isOpened()) {
throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is already opened.']);
......
......@@ -162,9 +162,20 @@ class InAppWebView extends StatefulWidget {
///If [JsPromptResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog.
///
///[message] represents the message to be displayed in the alert dialog.
///
///[defaultValue] represents the default value displayed in the prompt dialog.
final onJsPromptCallback onJsPrompt;
///Event fires when the webview notifies that a loading URL has been flagged by Safe Browsing.
///The default behavior is to show an interstitial to the user, with the reporting checkbox visible.
///
///[url] represents the url of the request.
///
///[threatType] represents the reason the resource was caught by Safe Browsing, corresponding to a [SafeBrowsingThreat].
///
///**NOTE**: available only for Android.
final onSafeBrowsingHitCallback onSafeBrowsingHit;
///Initial url that will be loaded.
final String initialUrl;
///Initial asset file that will be loaded. See [InAppWebView.loadFile()] for explanation.
......@@ -207,6 +218,7 @@ class InAppWebView extends StatefulWidget {
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.onSafeBrowsingHit,
this.gestureRecognizers,
}) : super(key: key);
......@@ -370,13 +382,7 @@ class InAppWebViewController {
String sourceURL = call.arguments["sourceURL"];
int lineNumber = call.arguments["lineNumber"];
String message = call.arguments["message"];
ConsoleMessageLevel messageLevel;
ConsoleMessageLevel.values.forEach((element) {
if ("ConsoleMessageLevel." + call.arguments["messageLevel"] == element.toString()) {
messageLevel = element;
return;
}
});
ConsoleMessageLevel messageLevel = ConsoleMessageLevel.fromValue(call.arguments["messageLevel"]);
if (_widget != null && _widget.onConsoleMessage != null)
_widget.onConsoleMessage(this, ConsoleMessage(sourceURL, lineNumber, message, messageLevel));
else if (_inAppBrowser != null)
......@@ -454,6 +460,14 @@ class InAppWebViewController {
else if (_inAppBrowser != null)
return (await _inAppBrowser.onJsPrompt(message, defaultValue))?.toMap();
break;
case "onSafeBrowsingHit":
String url = call.arguments["url"];
SafeBrowsingThreat threatType = SafeBrowsingThreat.fromValue(call.arguments["threatType"]);
if (_widget != null && _widget.onJsPrompt != null)
return (await _widget.onSafeBrowsingHit(this, url, threatType))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onSafeBrowsingHit(url, threatType))?.toMap();
break;
case "onCallJsHandler":
String handlerName = call.arguments["handlerName"];
// decode args to json
......@@ -918,6 +932,17 @@ class InAppWebViewController {
return await _channel.invokeMethod('setSafeBrowsingWhitelist', args);
}
///Returns a URL pointing to the privacy policy for Safe Browsing reporting. This value will never be `null`.
///
///**NOTE**: available only for Android.
Future<String> getSafeBrowsingPrivacyPolicyUrl() async {
Map<String, dynamic> args = <String, dynamic>{};
if (_inAppBrowserUuid != null && _inAppBrowser != null) {
_inAppBrowser.throwIsNotOpened();
args.putIfAbsent('uuid', () => _inAppBrowserUuid);
}
return await _channel.invokeMethod('getSafeBrowsingPrivacyPolicyUrl', args);
}
///Dispose/Destroy the WebView.
Future<void> _dispose() async {
......
......@@ -18,9 +18,22 @@ typedef Future<dynamic> ListenerCallback(MethodCall call);
///In this case, simply return data that you want to send and it will be automatically json encoded using [jsonEncode] from the `dart:convert` library.
typedef dynamic JavaScriptHandlerCallback(List<dynamic> arguments);
///Enum representing the level of a console message.
enum ConsoleMessageLevel {
DEBUG, ERROR, LOG, TIP, WARNING
///Class representing the level of a console message.
class ConsoleMessageLevel {
final int _value;
const ConsoleMessageLevel._internal(this._value);
static ConsoleMessageLevel fromValue(int value) {
if (value >= 0 && value <= 4)
return ConsoleMessageLevel._internal(value);
return null;
}
toValue() => _value;
static const TIP = const ConsoleMessageLevel._internal(0);
static const LOG = const ConsoleMessageLevel._internal(1);
static const WARNING = const ConsoleMessageLevel._internal(2);
static const ERROR = const ConsoleMessageLevel._internal(3);
static const DEBUG = const ConsoleMessageLevel._internal(4);
}
///Public class representing a resource response of the [InAppBrowser] WebView.
......@@ -217,6 +230,46 @@ class JsPromptResponse {
}
}
class SafeBrowsingThreat {
final int _value;
const SafeBrowsingThreat._internal(this._value);
static SafeBrowsingThreat fromValue(int value) {
if (value >= 0 && value <= 4)
return SafeBrowsingThreat._internal(value);
return null;
}
toValue() => _value;
static const SAFE_BROWSING_THREAT_UNKNOWN = const SafeBrowsingThreat._internal(0);
static const SAFE_BROWSING_THREAT_MALWARE = const SafeBrowsingThreat._internal(1);
static const SAFE_BROWSING_THREAT_PHISHING = const SafeBrowsingThreat._internal(2);
static const SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE = const SafeBrowsingThreat._internal(3);
static const SAFE_BROWSING_THREAT_BILLING = const SafeBrowsingThreat._internal(4);
}
class SafeBrowsingResponseAction {
final int _value;
const SafeBrowsingResponseAction._internal(this._value);
toValue() => _value;
static const BACK_TO_SAFETY = const SafeBrowsingResponseAction._internal(0);
static const PROCEED = const SafeBrowsingResponseAction._internal(1);
static const SHOW_INTERSTITIAL = const SafeBrowsingResponseAction._internal(2);
}
class SafeBrowsingResponse {
bool report;
SafeBrowsingResponseAction action;
SafeBrowsingResponse({this.report = true, this.action = SafeBrowsingResponseAction.SHOW_INTERSTITIAL});
Map<String, dynamic> toMap() {
return {
"report": report,
"action": action?.toValue()
};
}
}
typedef onWebViewCreatedCallback = void Function(InAppWebViewController controller);
typedef onWebViewLoadStartCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadStopCallback = void Function(InAppWebViewController controller, String url);
......@@ -232,4 +285,5 @@ typedef onTargetBlankCallback = void Function(InAppWebViewController controller,
typedef onGeolocationPermissionsShowPromptCallback = Future<GeolocationPermissionShowPromptResponse> Function(InAppWebViewController controller, String origin);
typedef onJsAlertCallback = Future<JsAlertResponse> Function(InAppWebViewController controller, String message);
typedef onJsConfirmCallback = Future<JsConfirmResponse> Function(InAppWebViewController controller, String message);
typedef onJsPromptCallback = Future<JsPromptResponse> Function(InAppWebViewController controller, String message, String defaultValue);
\ No newline at end of file
typedef onJsPromptCallback = Future<JsPromptResponse> Function(InAppWebViewController controller, String message, String defaultValue);
typedef onSafeBrowsingHitCallback = Future<SafeBrowsingResponse> Function(InAppWebViewController controller, String url, SafeBrowsingThreat threatType);
\ No newline at end of file
......@@ -127,7 +127,6 @@ class AndroidInAppWebViewOptions implements WebViewOptions, BrowserOptions {
bool safeBrowsingEnabled;
bool transparentBackground;
AndroidInAppWebViewMixedContentMode mixedContentMode;
bool allowContentAccess;
bool allowFileAccess;
bool allowFileAccessFromFileURLs;
......@@ -258,7 +257,6 @@ class iOSInAppWebViewOptions implements WebViewOptions, BrowserOptions {
bool allowsInlineMediaPlayback;
bool allowsPictureInPictureMediaPlayback;
bool transparentBackground;
String applicationNameForUserAgent;
bool isFraudulentWebsiteWarningEnabled;
iOSInAppWebViewSelectionGranularity selectionGranularity;
......@@ -337,18 +335,47 @@ class AndroidInAppBrowserOptions implements BrowserOptions {
}
}
class iOSInAppBrowserOptionsPresentationStyle {
final int _value;
const iOSInAppBrowserOptionsPresentationStyle._internal(this._value);
toValue() => _value;
static const FULL_SCREEN = const iOSInAppBrowserOptionsPresentationStyle._internal(0);
static const PAGE_SHEET = const iOSInAppBrowserOptionsPresentationStyle._internal(1);
static const FORM_SHEET = const iOSInAppBrowserOptionsPresentationStyle._internal(2);
static const CURRENT_CONTEXT = const iOSInAppBrowserOptionsPresentationStyle._internal(3);
static const CUSTOM = const iOSInAppBrowserOptionsPresentationStyle._internal(4);
static const OVER_FULL_SCREEN = const iOSInAppBrowserOptionsPresentationStyle._internal(5);
static const OVER_CURRENT_CONTEXT = const iOSInAppBrowserOptionsPresentationStyle._internal(6);
static const POPOVER = const iOSInAppBrowserOptionsPresentationStyle._internal(7);
static const NONE = const iOSInAppBrowserOptionsPresentationStyle._internal(8);
static const AUTOMATIC = const iOSInAppBrowserOptionsPresentationStyle._internal(9);
}
class iOSInAppBrowserOptionsTransitionStyle {
final int _value;
const iOSInAppBrowserOptionsTransitionStyle._internal(this._value);
toValue() => _value;
static const COVER_VERTICAL = const iOSInAppBrowserOptionsTransitionStyle._internal(0);
static const FLIP_HORIZONTAL = const iOSInAppBrowserOptionsTransitionStyle._internal(1);
static const CROSS_DISSOLVE = const iOSInAppBrowserOptionsTransitionStyle._internal(2);
static const PARTIAL_CURL = const iOSInAppBrowserOptionsTransitionStyle._internal(3);
}
class iOSInAppBrowserOptions implements BrowserOptions {
bool toolbarBottom;
String toolbarBottomBackgroundColor;
bool toolbarBottomTranslucent;
String closeButtonCaption;
String closeButtonColor;
int presentationStyle; //default fullscreen
int transitionStyle; //default crossDissolve
iOSInAppBrowserOptionsPresentationStyle presentationStyle;
iOSInAppBrowserOptionsTransitionStyle transitionStyle;
bool spinner;
iOSInAppBrowserOptions({this.toolbarBottom = true, this.toolbarBottomBackgroundColor = "", this.toolbarBottomTranslucent = true, this.closeButtonCaption = "",
this.closeButtonColor = "", this.presentationStyle = 0, this.transitionStyle = 0, this.spinner = true});
this.closeButtonColor = "", this.presentationStyle = iOSInAppBrowserOptionsPresentationStyle.FULL_SCREEN,
this.transitionStyle = iOSInAppBrowserOptionsTransitionStyle.COVER_VERTICAL, this.spinner = true});
@override
Map<String, dynamic> toMap() {
......@@ -358,8 +385,8 @@ class iOSInAppBrowserOptions implements BrowserOptions {
"toolbarBottomTranslucent": toolbarBottomTranslucent,
"closeButtonCaption": closeButtonCaption,
"closeButtonColor": closeButtonColor,
"presentationStyle": presentationStyle,
"transitionStyle": transitionStyle,
"presentationStyle": presentationStyle.toValue(),
"transitionStyle": transitionStyle.toValue(),
"spinner": spinner,
};
}
......@@ -392,28 +419,39 @@ class AndroidChromeCustomTabsOptions implements ChromeCustomTabsOptions {
}
}
class iOSChromeCustomTabsOptionsDismissButtonStyle {
final int _value;
const iOSChromeCustomTabsOptionsDismissButtonStyle._internal(this._value);
toValue() => _value;
static const DONE = const iOSChromeCustomTabsOptionsDismissButtonStyle._internal(0);
static const CLOSE = const iOSChromeCustomTabsOptionsDismissButtonStyle._internal(1);
static const CANCEL = const iOSChromeCustomTabsOptionsDismissButtonStyle._internal(2);
}
class iOSChromeCustomTabsOptions implements ChromeCustomTabsOptions {
bool entersReaderIfAvailable;
bool barCollapsingEnabled;
int dismissButtonStyle; //default done
iOSChromeCustomTabsOptionsDismissButtonStyle dismissButtonStyle;
String preferredBarTintColor;
String preferredControlTintColor;
int presentationStyle; //default fullscreen
int transitionStyle; //default crossDissolve
iOSInAppBrowserOptionsPresentationStyle presentationStyle;
iOSInAppBrowserOptionsTransitionStyle transitionStyle;
iOSChromeCustomTabsOptions({this.entersReaderIfAvailable = false, this.barCollapsingEnabled = false, this.dismissButtonStyle = 0, this.preferredBarTintColor = "",
this.preferredControlTintColor = "", this.presentationStyle = 0, this.transitionStyle = 0});
iOSChromeCustomTabsOptions({this.entersReaderIfAvailable = false, this.barCollapsingEnabled = false, this.dismissButtonStyle = iOSChromeCustomTabsOptionsDismissButtonStyle.DONE,
this.preferredBarTintColor = "", this.preferredControlTintColor = "", this.presentationStyle = iOSInAppBrowserOptionsPresentationStyle.FULL_SCREEN,
this.transitionStyle = iOSInAppBrowserOptionsTransitionStyle.COVER_VERTICAL});
@override
Map<String, dynamic> toMap() {
return {
"entersReaderIfAvailable": entersReaderIfAvailable,
"barCollapsingEnabled": barCollapsingEnabled,
"dismissButtonStyle": dismissButtonStyle,
"dismissButtonStyle": dismissButtonStyle.toValue(),
"preferredBarTintColor": preferredBarTintColor,
"preferredControlTintColor": preferredControlTintColor,
"presentationStyle": presentationStyle,
"transitionStyle": transitionStyle,
"presentationStyle": presentationStyle.toValue(),
"transitionStyle": transitionStyle.toValue(),
};
}
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment