77
88package com .facebook .react .views .webview ;
99
10+ import android .annotation .TargetApi ;
11+ import android .content .Context ;
12+ import java .util .LinkedList ;
13+ import java .util .List ;
14+ import java .util .regex .Pattern ;
1015import javax .annotation .Nullable ;
1116
1217import java .io .UnsupportedEncodingException ;
@@ -110,6 +115,7 @@ protected static class ReactWebViewClient extends WebViewClient {
110115
111116 protected boolean mLastLoadFailed = false ;
112117 protected @ Nullable ReadableArray mUrlPrefixesForDefaultIntent ;
118+ protected @ Nullable List <Pattern > mOriginWhitelist ;
113119
114120 @ Override
115121 public void onPageFinished (WebView webView , String url ) {
@@ -137,32 +143,50 @@ public void onPageStarted(WebView webView, String url, Bitmap favicon) {
137143
138144 @ Override
139145 public boolean shouldOverrideUrlLoading (WebView view , String url ) {
140- boolean useDefaultIntent = false ;
141- if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent .size () > 0 ) {
142- ArrayList <Object > urlPrefixesForDefaultIntent =
143- mUrlPrefixesForDefaultIntent .toArrayList ();
144- for (Object urlPrefix : urlPrefixesForDefaultIntent ) {
145- if (url .startsWith ((String ) urlPrefix )) {
146- useDefaultIntent = true ;
147- break ;
148- }
146+ if (url .equals (BLANK_URL )) return false ;
147+
148+ // url blacklisting
149+ if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent .size () > 0 ) {
150+ ArrayList <Object > urlPrefixesForDefaultIntent =
151+ mUrlPrefixesForDefaultIntent .toArrayList ();
152+ for (Object urlPrefix : urlPrefixesForDefaultIntent ) {
153+ if (url .startsWith ((String ) urlPrefix )) {
154+ launchIntent (view .getContext (), url );
155+ return true ;
149156 }
150157 }
158+ }
151159
152- if (!useDefaultIntent &&
153- (url .startsWith ("http://" ) || url .startsWith ("https://" ) ||
154- url .startsWith ("file://" ) || url .equals ("about:blank" ))) {
155- return false ;
156- } else {
157- try {
158- Intent intent = new Intent (Intent .ACTION_VIEW , Uri .parse (url ));
159- intent .setFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
160- view .getContext ().startActivity (intent );
161- } catch (ActivityNotFoundException e ) {
162- FLog .w (ReactConstants .TAG , "activity not found to handle uri scheme for: " + url , e );
163- }
160+ if (mOriginWhitelist != null && shouldHandleURL (mOriginWhitelist , url )) {
161+ return false ;
162+ }
163+
164+ launchIntent (view .getContext (), url );
165+ return true ;
166+ }
167+
168+ private void launchIntent (Context context , String url ) {
169+ try {
170+ Intent intent = new Intent (Intent .ACTION_VIEW , Uri .parse (url ));
171+ intent .setFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
172+ intent .addCategory (Intent .CATEGORY_BROWSABLE );
173+ context .startActivity (intent );
174+ } catch (ActivityNotFoundException e ) {
175+ FLog .w (ReactConstants .TAG , "activity not found to handle uri scheme for: " + url , e );
176+ }
177+ }
178+
179+ private boolean shouldHandleURL (List <Pattern > originWhitelist , String url ) {
180+ Uri uri = Uri .parse (url );
181+ String scheme = uri .getScheme () != null ? uri .getScheme () : "" ;
182+ String authority = uri .getAuthority () != null ? uri .getAuthority () : "" ;
183+ String urlToCheck = scheme + "://" + authority ;
184+ for (Pattern pattern : originWhitelist ) {
185+ if (pattern .matcher (urlToCheck ).matches ()) {
164186 return true ;
165187 }
188+ }
189+ return false ;
166190 }
167191
168192 @ Override
@@ -211,6 +235,10 @@ protected WritableMap createWebViewEvent(WebView webView, String url) {
211235 public void setUrlPrefixesForDefaultIntent (ReadableArray specialUrls ) {
212236 mUrlPrefixesForDefaultIntent = specialUrls ;
213237 }
238+
239+ public void setOriginWhitelist (List <Pattern > originWhitelist ) {
240+ mOriginWhitelist = originWhitelist ;
241+ }
214242 }
215243
216244 /**
@@ -356,6 +384,7 @@ protected ReactWebView createReactWebViewInstance(ThemedReactContext reactContex
356384 }
357385
358386 @ Override
387+ @ TargetApi (Build .VERSION_CODES .LOLLIPOP )
359388 protected WebView createViewInstance (ThemedReactContext reactContext ) {
360389 ReactWebView webView = createReactWebViewInstance (reactContext );
361390 webView .setWebChromeClient (new WebChromeClient () {
@@ -375,9 +404,18 @@ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermiss
375404 });
376405 reactContext .addLifecycleEventListener (webView );
377406 mWebViewConfig .configWebView (webView );
378- webView .getSettings ().setBuiltInZoomControls (true );
379- webView .getSettings ().setDisplayZoomControls (false );
380- webView .getSettings ().setDomStorageEnabled (true );
407+ WebSettings settings = webView .getSettings ();
408+ settings .setBuiltInZoomControls (true );
409+ settings .setDisplayZoomControls (false );
410+ settings .setDomStorageEnabled (true );
411+
412+ settings .setAllowFileAccess (false );
413+ settings .setAllowContentAccess (false );
414+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .JELLY_BEAN ) {
415+ settings .setAllowFileAccessFromFileURLs (false );
416+ setAllowUniversalAccessFromFileURLs (webView , false );
417+ }
418+ setMixedContentMode (webView , "never" );
381419
382420 // Fixes broken full-screen modals/galleries due to body height being 0.
383421 webView .setLayoutParams (
@@ -546,6 +584,20 @@ public void setGeolocationEnabled(
546584 view .getSettings ().setGeolocationEnabled (isGeolocationEnabled != null && isGeolocationEnabled );
547585 }
548586
587+ @ ReactProp (name = "originWhitelist" )
588+ public void setOriginWhitelist (
589+ WebView view ,
590+ @ Nullable ReadableArray originWhitelist ) {
591+ ReactWebViewClient client = ((ReactWebView ) view ).getReactWebViewClient ();
592+ if (client != null && originWhitelist != null ) {
593+ List <Pattern > whiteList = new LinkedList <>();
594+ for (int i = 0 ; i < originWhitelist .size () ; i ++) {
595+ whiteList .add (Pattern .compile (originWhitelist .getString (i )));
596+ }
597+ client .setOriginWhitelist (whiteList );
598+ }
599+ }
600+
549601 @ Override
550602 protected void addEventEmitters (ThemedReactContext reactContext , WebView view ) {
551603 // Do not register default touch emitter and let WebView implementation handle touches
0 commit comments