Skip to content

Commit b67d748

Browse files
Merge pull request #76 from Nodonisko/feat/improve-security
feat: add allowFileDownloads and suppressJavaScriptDialogs props
2 parents bace2d8 + 9e9ec5e commit b67d748

17 files changed

Lines changed: 284 additions & 49 deletions

File tree

android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.view.ViewGroup;
1616
import android.webkit.ConsoleMessage;
1717
import android.webkit.GeolocationPermissions;
18+
import android.webkit.JsResult;
1819
import android.webkit.JsPromptResult;
1920
import android.webkit.PermissionRequest;
2021
import android.webkit.ValueCallback;
@@ -62,6 +63,7 @@ public class RNCWebChromeClient extends WebChromeClient implements LifecycleEven
6263

6364
// This boolean block JS prompts and alerts from displaying during loading
6465
protected boolean blockJsDuringLoading = true;
66+
protected boolean suppressJavaScriptDialogs = false;
6567

6668
/*
6769
* - Permissions -
@@ -445,14 +447,32 @@ public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathC
445447

446448
@Override
447449
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
448-
if (blockJsDuringLoading) {
450+
if (shouldSuppressDialogs()) {
449451
result.cancel();
450452
return true;
451453
} else {
452454
return super.onJsPrompt(view, url, message, defaultValue, result);
453455
}
454456
}
455457

458+
@Override
459+
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
460+
if (shouldSuppressDialogs()) {
461+
result.cancel();
462+
return true;
463+
}
464+
return super.onJsAlert(view, url, message, result);
465+
}
466+
467+
@Override
468+
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
469+
if (shouldSuppressDialogs()) {
470+
result.cancel();
471+
return true;
472+
}
473+
return super.onJsConfirm(view, url, message, result);
474+
}
475+
456476
@Override
457477
public void onHostResume() {
458478
if (mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
@@ -486,4 +506,12 @@ public void setAllowsProtectedMedia(boolean enabled) {
486506
public void setHasOnOpenWindowEvent(boolean hasEvent) {
487507
mHasOnOpenWindowEvent = hasEvent;
488508
}
489-
}
509+
510+
public void setSuppressJavaScriptDialogs(boolean suppress) {
511+
suppressJavaScriptDialogs = suppress;
512+
}
513+
514+
private boolean shouldSuppressDialogs() {
515+
return suppressJavaScriptDialogs || blockJsDuringLoading;
516+
}
517+
}

android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
8181
protected boolean hasScrollEvent = false;
8282
protected boolean nestedScrollEnabled = false;
8383
protected ProgressChangedFilter progressChangedFilter;
84+
protected boolean allowFileDownloads = true;
85+
protected boolean suppressJavaScriptDialogs = false;
8486

8587
/** Samsung Manufacturer Name */
8688
private static final String SAMSUNG_MANUFACTURER_NAME = "samsung";
@@ -121,6 +123,22 @@ public void setNestedScrollEnabled(boolean nestedScrollEnabled) {
121123
this.nestedScrollEnabled = nestedScrollEnabled;
122124
}
123125

126+
public void setAllowFileDownloads(boolean allow) {
127+
this.allowFileDownloads = allow;
128+
}
129+
130+
public boolean getAllowFileDownloads() {
131+
return this.allowFileDownloads;
132+
}
133+
134+
public void setSuppressJavaScriptDialogs(boolean suppress) {
135+
this.suppressJavaScriptDialogs = suppress;
136+
}
137+
138+
public boolean getSuppressJavaScriptDialogs() {
139+
return this.suppressJavaScriptDialogs;
140+
}
141+
124142
@Override
125143
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
126144
InputConnection inputConnection;

android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,11 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
102102
webView.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
103103
}
104104
val base64DownloaderRequestFilePermission = { base64: String ->
105-
webView.reactApplicationContext.getNativeModule(RNCWebViewModule::class.java)?.let { module ->
106-
module.setBase64DownloadRequest(base64)
107-
module.grantFileDownloaderPermissions(getDownloadingMessageOrDefault(), getLackPermissionToDownloadMessageOrDefault())
105+
if (webView.allowFileDownloads) {
106+
webView.reactApplicationContext.getNativeModule(RNCWebViewModule::class.java)?.let { module ->
107+
module.setBase64DownloadRequest(base64)
108+
module.grantFileDownloaderPermissions(getDownloadingMessageOrDefault(), getLackPermissionToDownloadMessageOrDefault())
109+
}
108110
}
109111
Unit
110112
}
@@ -113,6 +115,9 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
113115
requestFilePermission = base64DownloaderRequestFilePermission,
114116
)
115117
webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
118+
if (!webView.allowFileDownloads) {
119+
return@DownloadListener
120+
}
116121
if (url.startsWith("data:")) {
117122
Base64FileDownloader.downloadBase64File(
118123
context = context,
@@ -255,6 +260,7 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
255260
}
256261
webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia);
257262
webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent);
263+
webChromeClient.setSuppressJavaScriptDialogs(webView.suppressJavaScriptDialogs);
258264
webView.webChromeClient = webChromeClient
259265
} else {
260266
var webChromeClient = webView.webChromeClient as RNCWebChromeClient?
@@ -266,6 +272,7 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
266272
}
267273
webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia);
268274
webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent);
275+
webChromeClient.setSuppressJavaScriptDialogs(webView.suppressJavaScriptDialogs);
269276
webView.webChromeClient = webChromeClient
270277
}
271278
}
@@ -673,6 +680,11 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
673680
mLackPermissionToDownloadMessage = value
674681
}
675682

683+
fun setAllowFileDownloads(viewWrapper: RNCWebViewWrapper, value: Boolean) {
684+
val view = viewWrapper.webView
685+
view.allowFileDownloads = value
686+
}
687+
676688
fun setHasOnOpenWindowEvent(viewWrapper: RNCWebViewWrapper, value: Boolean) {
677689
val view = viewWrapper.webView
678690
mHasOnOpenWindowEvent = value
@@ -698,6 +710,15 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
698710
}
699711
}
700712

713+
fun setSuppressJavaScriptDialogs(viewWrapper: RNCWebViewWrapper, suppress: Boolean) {
714+
val view = viewWrapper.webView
715+
view.suppressJavaScriptDialogs = suppress
716+
val client = view.webChromeClient
717+
if (client is RNCWebChromeClient) {
718+
client.setSuppressJavaScriptDialogs(suppress)
719+
}
720+
}
721+
701722
fun setMenuCustomItems(viewWrapper: RNCWebViewWrapper, value: ReadableArray?) {
702723
val view = viewWrapper.webView
703724
when (value) {

android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
package com.reactnativecommunity.webview.extension.file
22

3-
import android.content.Context
43
import android.webkit.JavascriptInterface
54
import com.reactnativecommunity.webview.RNCWebView
65
import com.reactnativecommunity.webview.extension.file.Base64FileDownloader.downloadBase64File
76
import java.io.IOException
87

98
internal fun RNCWebView.addBlobFileDownloaderJavascriptInterface(downloadingMessage: String, requestFilePermission: (String) -> Unit) {
109
this.addJavascriptInterface(
11-
BlobFileDownloader(this.context, downloadingMessage, requestFilePermission),
10+
BlobFileDownloader(this, downloadingMessage, requestFilePermission),
1211
BlobFileDownloader.JS_INTERFACE_TAG,
1312
)
1413
}
1514

1615
internal class BlobFileDownloader(
17-
private val context: Context,
16+
private val webView: RNCWebView,
1817
private val downloadingMessage: String,
1918
private val requestFilePermission: (String) -> Unit,
2019
) {
2120

2221
@JavascriptInterface
2322
@Throws(IOException::class)
2423
fun getBase64FromBlobData(base64: String) {
25-
downloadBase64File(context, base64, downloadingMessage, requestFilePermission)
24+
if (!webView.allowFileDownloads) {
25+
return
26+
}
27+
downloadBase64File(webView.context, base64, downloadingMessage, requestFilePermission)
2628
}
2729

2830
companion object {

android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,24 @@ public void setLackPermissionToDownloadMessage(RNCWebViewWrapper view, @Nullable
207207
mRNCWebViewManagerImpl.setLackPermissionToDownloadMessage(value);
208208
}
209209

210+
@Override
211+
@ReactProp(name = "allowFileDownloads", defaultBoolean = true)
212+
public void setAllowFileDownloads(RNCWebViewWrapper view, boolean value) {
213+
mRNCWebViewManagerImpl.setAllowFileDownloads(view, value);
214+
}
215+
210216
@Override
211217
@ReactProp(name = "hasOnOpenWindowEvent")
212218
public void setHasOnOpenWindowEvent(RNCWebViewWrapper view, boolean hasEvent) {
213219
mRNCWebViewManagerImpl.setHasOnOpenWindowEvent(view, hasEvent);
214220
}
215221

222+
@Override
223+
@ReactProp(name = "suppressJavaScriptDialogs")
224+
public void setSuppressJavaScriptDialogs(RNCWebViewWrapper view, boolean suppress) {
225+
mRNCWebViewManagerImpl.setSuppressJavaScriptDialogs(view, suppress);
226+
}
227+
216228
@Override
217229
@ReactProp(name = "mediaPlaybackRequiresUserAction")
218230
public void setMediaPlaybackRequiresUserAction(RNCWebViewWrapper view, boolean value) {
@@ -556,4 +568,4 @@ public void onDropViewInstance(@NonNull RNCWebViewWrapper view) {
556568
mRNCWebViewManagerImpl.onDropViewInstance(view);
557569
super.onDropViewInstance(view);
558570
}
559-
}
571+
}

android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,21 @@ public void setLackPermissionToDownloadMessage(RNCWebViewWrapper view, @Nullable
168168
mRNCWebViewManagerImpl.setLackPermissionToDownloadMessage(value);
169169
}
170170

171+
@ReactProp(name = "allowFileDownloads", defaultBoolean = true)
172+
public void setAllowFileDownloads(RNCWebViewWrapper view, boolean value) {
173+
mRNCWebViewManagerImpl.setAllowFileDownloads(view, value);
174+
}
175+
171176
@ReactProp(name = "hasOnOpenWindowEvent")
172177
public void setHasOnOpenWindowEvent(RNCWebViewWrapper view, boolean hasEvent) {
173178
mRNCWebViewManagerImpl.setHasOnOpenWindowEvent(view, hasEvent);
174179
}
175180

181+
@ReactProp(name = "suppressJavaScriptDialogs")
182+
public void setSuppressJavaScriptDialogs(RNCWebViewWrapper view, boolean suppress) {
183+
mRNCWebViewManagerImpl.setSuppressJavaScriptDialogs(view, suppress);
184+
}
185+
176186
@ReactProp(name = "mediaPlaybackRequiresUserAction")
177187
public void setMediaPlaybackRequiresUserAction(RNCWebViewWrapper view, boolean value) {
178188
mRNCWebViewManagerImpl.setMediaPlaybackRequiresUserAction(view, value);
@@ -330,4 +340,4 @@ public void onDropViewInstance(@NonNull RNCWebViewWrapper view) {
330340
mRNCWebViewManagerImpl.onDropViewInstance(view);
331341
super.onDropViewInstance(view);
332342
}
333-
}
343+
}

apple/RNCWebView.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
277277
REMAP_WEBVIEW_STRING_PROP(injectedJavaScriptObject)
278278
REMAP_WEBVIEW_PROP(javaScriptEnabled)
279279
REMAP_WEBVIEW_PROP(javaScriptCanOpenWindowsAutomatically)
280+
REMAP_WEBVIEW_PROP(suppressJavaScriptDialogs)
280281
REMAP_WEBVIEW_PROP(allowFileAccessFromFileURLs)
281282
REMAP_WEBVIEW_PROP(allowUniversalAccessFromFileURLs)
282283
REMAP_WEBVIEW_PROP(allowsInlineMediaPlayback)

apple/RNCWebViewImpl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
9797
@property (nonatomic, assign) BOOL cacheEnabled;
9898
@property (nonatomic, assign) BOOL javaScriptEnabled;
9999
@property (nonatomic, assign) BOOL javaScriptCanOpenWindowsAutomatically;
100+
@property (nonatomic, assign) BOOL suppressJavaScriptDialogs;
100101
@property (nonatomic, assign) BOOL allowFileAccessFromFileURLs;
101102
@property (nonatomic, assign) BOOL allowUniversalAccessFromFileURLs;
102103
@property (nonatomic, assign) BOOL allowsLinkPreview;

apple/RNCWebViewImpl.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,10 @@ - (void) webView:(WKWebView *)webView
12681268
*/
12691269
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
12701270
{
1271+
if (_suppressJavaScriptDialogs) {
1272+
completionHandler();
1273+
return;
1274+
}
12711275
#if !TARGET_OS_OSX
12721276
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
12731277
[alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
@@ -1287,6 +1291,10 @@ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSStrin
12871291
* confirm
12881292
*/
12891293
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
1294+
if (_suppressJavaScriptDialogs) {
1295+
completionHandler(NO);
1296+
return;
1297+
}
12901298
#if !TARGET_OS_OSX
12911299
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
12921300
[alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
@@ -1312,6 +1320,10 @@ - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSStr
13121320
* prompt
13131321
*/
13141322
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
1323+
if (_suppressJavaScriptDialogs) {
1324+
completionHandler(nil);
1325+
return;
1326+
}
13151327
if (!_disablePromptDuringLoading) {
13161328
#if !TARGET_OS_OSX
13171329
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];

apple/RNCWebViewManager.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ - (RNCView *)view
5959
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptObject, NSString)
6060
RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)
6161
RCT_EXPORT_VIEW_PROPERTY(javaScriptCanOpenWindowsAutomatically, BOOL)
62+
RCT_EXPORT_VIEW_PROPERTY(suppressJavaScriptDialogs, BOOL)
6263
RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL)
6364
RCT_EXPORT_VIEW_PROPERTY(allowUniversalAccessFromFileURLs, BOOL)
6465
RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)

0 commit comments

Comments
 (0)