Permalink
Browse files

iOS only: Breaking Change: Restrict WebView to only http(s) URLs

Summary:
To prevent people from linking file:// or other URLs inside RN WebViews, default <WebView> to not allowing those types of URLs.
This adds the originWhitelist to specify other schemes or domains to be allowed.

If the url is not allowed, it will be opened in Safari/by the OS instead.

Reviewed By: yungsters

Differential Revision: D7833203

fbshipit-source-id: 6881acd3b434d17910240e4edd585c0a10b5df8c
  • Loading branch information...
mmmulani authored and facebook-github-bot committed May 4, 2018
1 parent cd48a61 commit 634e7e11e3ad39e0b13bf20cc7722c0cfd3c3e28
@@ -48,6 +48,7 @@ class WebViewTest extends React.Component {
<WebView
source={source}
onMessage = {processMessage}
originWhitelist={['about:blank']}
/>
);
}
@@ -10,15 +10,17 @@
const ActivityIndicator = require('ActivityIndicator');
const EdgeInsetsPropType = require('EdgeInsetsPropType');
const React = require('React');
const Linking = require('Linking');
const PropTypes = require('prop-types');
const React = require('React');
const ReactNative = require('ReactNative');
const ScrollView = require('ScrollView');
const StyleSheet = require('StyleSheet');
const Text = require('Text');
const UIManager = require('UIManager');
const View = require('View');
const ViewPropTypes = require('ViewPropTypes');
const ScrollView = require('ScrollView');
const WebViewShared = require('WebViewShared');
const deprecatedPropType = require('deprecatedPropType');
const invariant = require('fbjs/lib/invariant');
@@ -353,6 +355,15 @@ class WebView extends React.Component {
*/
mediaPlaybackRequiresUserAction: PropTypes.bool,
/**
* List of origin strings to allow being navigated to. The strings allow
* wildcards and get matched against *just* the origin (not the full URL).
* If the user taps to navigate to a new page but the new page is not in
* this whitelist, we will open the URL in Safari.
* The default whitelisted origins are "http://*" and "https://*".
*/
originWhitelist: PropTypes.arrayOf(PropTypes.string),
/**
* Function that accepts a string that will be passed to the WebView and
* executed immediately as JavaScript.
@@ -398,6 +409,7 @@ class WebView extends React.Component {
};
static defaultProps = {
originWhitelist: WebViewShared.defaultOriginWhitelist,
scalesPageToFit: true,
};
@@ -446,9 +458,19 @@ class WebView extends React.Component {
const viewManager = nativeConfig.viewManager || RCTWebViewManager;
const onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => {
const shouldStart = this.props.onShouldStartLoadWithRequest &&
this.props.onShouldStartLoadWithRequest(event.nativeEvent);
const compiledWhitelist = (this.props.originWhitelist || []).map(WebViewShared.originWhitelistToRegex);
const onShouldStartLoadWithRequest = ((event: Event) => {
let shouldStart = true;
const {url} = event.nativeEvent;
const origin = WebViewShared.extractOrigin(url);
const passesWhitelist = compiledWhitelist.some(x => new RegExp(x).test(origin));
shouldStart = shouldStart && passesWhitelist;
if (!passesWhitelist) {
Linking.openURL(url);
}
if (this.props.onShouldStartLoadWithRequest) {
shouldStart = shouldStart && this.props.onShouldStartLoadWithRequest(event.nativeEvent);
}
viewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
});
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
const escapeStringRegexp = require('escape-string-regexp');
const WebViewShared = {
defaultOriginWhitelist: ['http://*', 'https://*'],
extractOrigin: (url: string): ?string => {
const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
return result === null ? null : result[0];
},
originWhitelistToRegex: (originWhitelist: string): string => {
return escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*');
},
};
module.exports = WebViewShared;
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+react_native
*/
'use strict';
const WebViewShared = require('WebViewShared');
describe('WebViewShared', () => {
it('extracts the origin correctly', () => {
expect(WebViewShared.extractOrigin('http://facebook.com')).toBe('http://facebook.com');
expect(WebViewShared.extractOrigin('https://facebook.com')).toBe('https://facebook.com');
expect(WebViewShared.extractOrigin('http://facebook.com:8081')).toBe('http://facebook.com:8081');
expect(WebViewShared.extractOrigin('ftp://facebook.com')).toBe('ftp://facebook.com');
expect(WebViewShared.extractOrigin('myweirdscheme://')).toBe('myweirdscheme://');
expect(WebViewShared.extractOrigin('http://facebook.com/')).toBe('http://facebook.com');
expect(WebViewShared.extractOrigin('http://facebook.com/longerurl')).toBe('http://facebook.com');
expect(WebViewShared.extractOrigin('http://facebook.com/http://facebook.com')).toBe('http://facebook.com');
expect(WebViewShared.extractOrigin('http://facebook.com//http://facebook.com')).toBe('http://facebook.com');
expect(WebViewShared.extractOrigin('http://facebook.com//http://facebook.com//')).toBe('http://facebook.com');
expect(WebViewShared.extractOrigin('about:blank')).toBe('about:blank');
});
it('rejects bad urls', () => {
expect(WebViewShared.extractOrigin('a/b')).toBeNull();
expect(WebViewShared.extractOrigin('a//b')).toBeNull();
});
it('creates a whitelist regex correctly', () => {
expect(WebViewShared.originWhitelistToRegex('http://*')).toBe('http://.*');
expect(WebViewShared.originWhitelistToRegex('*')).toBe('.*');
expect(WebViewShared.originWhitelistToRegex('*//test')).toBe('.*//test');
expect(WebViewShared.originWhitelistToRegex('*/*')).toBe('.*/.*');
expect(WebViewShared.originWhitelistToRegex('*.com')).toBe('.*\\.com');
});
});
View
@@ -158,6 +158,7 @@
"denodeify": "^1.2.1",
"envinfo": "^3.0.0",
"errorhandler": "^1.5.0",
"escape-string-regexp": "^1.0.5",
"eslint-plugin-react-native": "^3.2.1",
"event-target-shim": "^1.0.5",
"fbjs": "^0.8.14",

0 comments on commit 634e7e1

Please sign in to comment.