diff --git a/framework/src/org/apache/cordova/AndroidWebView.java b/framework/src/org/apache/cordova/AndroidWebView.java index 6ad1af6d..c814c666 100755 --- a/framework/src/org/apache/cordova/AndroidWebView.java +++ b/framework/src/org/apache/cordova/AndroidWebView.java @@ -88,10 +88,9 @@ public class AndroidWebView extends WebView implements CordovaWebView { private WebChromeClient.CustomViewCallback mCustomViewCallback; private CordovaResourceApi resourceApi; - private Whitelist internalWhitelist; - private Whitelist externalWhitelist; private CordovaPreferences preferences; private CoreAndroid appPlugin; + private CordovaUriHelper helper; // The URL passed to loadUrl(), not necessarily the URL of the current page. String loadedUrl; @@ -114,15 +113,13 @@ public class AndroidWebView extends WebView implements CordovaWebView { // Use two-phase init so that the control will work with XML layouts. @Override public void init(final CordovaInterface cordova, List pluginEntries, - Whitelist internalWhitelist, Whitelist externalWhitelist, CordovaPreferences preferences) { if (this.cordova != null) { throw new IllegalStateException(); } this.cordova = cordova; - this.internalWhitelist = internalWhitelist; - this.externalWhitelist = externalWhitelist; this.preferences = preferences; + this.helper = new CordovaUriHelper(cordova, this); pluginManager = new PluginManager(this, this.cordova, pluginEntries); cookieManager = new AndroidCookieManager(this); @@ -141,7 +138,7 @@ public class AndroidWebView extends WebView implements CordovaWebView { cordova.getActivity().runOnUiThread(r); } })); - bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue, this.cordova.getActivity().getPackageName()); + bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue, this.cordova.getActivity().getPackageName(), helper); initWebViewSettings(); pluginManager.addService(CoreAndroid.PLUGIN_NAME, CoreAndroid.class.getCanonicalName()); pluginManager.init(); @@ -386,7 +383,7 @@ public class AndroidWebView extends WebView implements CordovaWebView { if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) { LOG.d(TAG, ">>> loadUrlNow()"); } - if (url.startsWith("file://") || url.startsWith("javascript:") || url.startsWith("about:") || internalWhitelist.isUrlWhiteListed(url)) { + if (url.startsWith("javascript:") || helper.shouldAllowNavigation(url)) { super.loadUrl(url); } } @@ -461,7 +458,7 @@ public class AndroidWebView extends WebView implements CordovaWebView { if (!openExternal) { // Make sure url is in whitelist - if (url.startsWith("file://") || internalWhitelist.isUrlWhiteListed(url)) { + if (helper.shouldAllowNavigation(url)) { // TODO: What about params? // Load new URL loadUrlIntoView(url, true); @@ -800,16 +797,6 @@ public class AndroidWebView extends WebView implements CordovaWebView { return this; } - @Override - public Whitelist getWhitelist() { - return this.internalWhitelist; - } - - @Override - public Whitelist getExternalWhitelist() { - return this.externalWhitelist; - } - @Override public CordovaPreferences getPreferences() { return preferences; diff --git a/framework/src/org/apache/cordova/Config.java b/framework/src/org/apache/cordova/Config.java index f13292c3..f261b781 100644 --- a/framework/src/org/apache/cordova/Config.java +++ b/framework/src/org/apache/cordova/Config.java @@ -46,48 +46,6 @@ public class Config { parser = new ConfigXmlParser(); } } - - /** - * Add entry to approved list of URLs (whitelist) - * - * @param origin URL regular expression to allow - * @param subdomains T=include all subdomains under origin - */ - public static void addWhiteListEntry(String origin, boolean subdomains) { - if (parser == null) { - Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?"); - return; - } - parser.getInternalWhitelist().addWhiteListEntry(origin, subdomains); - } - - /** - * Determine if URL is in approved list of URLs to load. - * - * @param url - * @return true if whitelisted - */ - public static boolean isUrlWhiteListed(String url) { - if (parser == null) { - Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?"); - return false; - } - return parser.getInternalWhitelist().isUrlWhiteListed(url); - } - - /** - * Determine if URL is in approved list of URLs to launch external applications. - * - * @param url - * @return true if whitelisted - */ - public static boolean isUrlExternallyWhiteListed(String url) { - if (parser == null) { - Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?"); - return false; - } - return parser.getExternalWhitelist().isUrlWhiteListed(url); - } public static String getStartUrl() { if (parser == null) { @@ -100,14 +58,6 @@ public class Config { return parser.getPreferences().getString("errorurl", null); } - public static Whitelist getWhitelist() { - return parser.getInternalWhitelist(); - } - - public static Whitelist getExternalWhitelist() { - return parser.getExternalWhitelist(); - } - public static List getPluginEntries() { return parser.getPluginEntries(); } diff --git a/framework/src/org/apache/cordova/ConfigXmlParser.java b/framework/src/org/apache/cordova/ConfigXmlParser.java index 0b1f5290..1ea30b01 100644 --- a/framework/src/org/apache/cordova/ConfigXmlParser.java +++ b/framework/src/org/apache/cordova/ConfigXmlParser.java @@ -36,18 +36,8 @@ public class ConfigXmlParser { private String launchUrl = "file:///android_asset/www/index.html"; private CordovaPreferences prefs = new CordovaPreferences(); - private Whitelist internalWhitelist = new Whitelist(); - private Whitelist externalWhitelist = new Whitelist(); private ArrayList pluginEntries = new ArrayList(20); - public Whitelist getInternalWhitelist() { - return internalWhitelist; - } - - public Whitelist getExternalWhitelist() { - return externalWhitelist; - } - public CordovaPreferences getPreferences() { return prefs; } @@ -59,7 +49,7 @@ public class ConfigXmlParser { public String getLaunchUrl() { return launchUrl; } - + public void parse(Activity action) { // First checking the class namespace for config.xml int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName()); @@ -74,78 +64,20 @@ public class ConfigXmlParser { parse(action.getResources().getXml(id)); } + boolean insideFeature = false; + String service = "", pluginClass = "", paramType = ""; + boolean onload = false; + public void parse(XmlResourceParser xml) { int eventType = -1; - String service = "", pluginClass = "", paramType = ""; - boolean onload = false; - boolean insideFeature = false; - - // Add implicitly allowed URLs - internalWhitelist.addWhiteListEntry("file:///*", false); - internalWhitelist.addWhiteListEntry("content:///*", false); - internalWhitelist.addWhiteListEntry("data:*", false); while (eventType != XmlResourceParser.END_DOCUMENT) { if (eventType == XmlResourceParser.START_TAG) { - String strNode = xml.getName(); - if (strNode.equals("feature")) { - //Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc) - //Set the bit for reading params - insideFeature = true; - service = xml.getAttributeValue(null, "name"); - } - else if (insideFeature && strNode.equals("param")) { - paramType = xml.getAttributeValue(null, "name"); - if (paramType.equals("service")) // check if it is using the older service param - service = xml.getAttributeValue(null, "value"); - else if (paramType.equals("package") || paramType.equals("android-package")) - pluginClass = xml.getAttributeValue(null,"value"); - else if (paramType.equals("onload")) - onload = "true".equals(xml.getAttributeValue(null, "value")); - } - else if (strNode.equals("access")) { - String origin = xml.getAttributeValue(null, "origin"); - String subdomains = xml.getAttributeValue(null, "subdomains"); - boolean external = (xml.getAttributeValue(null, "launch-external") != null); - if (origin != null) { - if (external) { - externalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); - } else { - if ("*".equals(origin)) { - // Special-case * origin to mean http and https when used for internal - // whitelist. This prevents external urls like sms: and geo: from being - // handled internally. - internalWhitelist.addWhiteListEntry("http://*/*", false); - internalWhitelist.addWhiteListEntry("https://*/*", false); - } else { - internalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); - } - } - } - } - else if (strNode.equals("preference")) { - String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH); - String value = xml.getAttributeValue(null, "value"); - prefs.set(name, value); - } - else if (strNode.equals("content")) { - String src = xml.getAttributeValue(null, "src"); - if (src != null) { - setStartUrl(src); - } - } + handleStartTag(xml); } else if (eventType == XmlResourceParser.END_TAG) { - String strNode = xml.getName(); - if (strNode.equals("feature")) { - pluginEntries.add(new PluginEntry(service, pluginClass, onload)); - - service = ""; - pluginClass = ""; - insideFeature = false; - onload = false; - } + handleEndTag(xml); } try { eventType = xml.next(); @@ -157,6 +89,48 @@ public class ConfigXmlParser { } } + public void handleStartTag(XmlResourceParser xml) { + String strNode = xml.getName(); + if (strNode.equals("feature")) { + //Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc) + //Set the bit for reading params + insideFeature = true; + service = xml.getAttributeValue(null, "name"); + } + else if (insideFeature && strNode.equals("param")) { + paramType = xml.getAttributeValue(null, "name"); + if (paramType.equals("service")) // check if it is using the older service param + service = xml.getAttributeValue(null, "value"); + else if (paramType.equals("package") || paramType.equals("android-package")) + pluginClass = xml.getAttributeValue(null,"value"); + else if (paramType.equals("onload")) + onload = "true".equals(xml.getAttributeValue(null, "value")); + } + else if (strNode.equals("preference")) { + String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH); + String value = xml.getAttributeValue(null, "value"); + prefs.set(name, value); + } + else if (strNode.equals("content")) { + String src = xml.getAttributeValue(null, "src"); + if (src != null) { + setStartUrl(src); + } + } + } + + public void handleEndTag(XmlResourceParser xml) { + String strNode = xml.getName(); + if (strNode.equals("feature")) { + pluginEntries.add(new PluginEntry(service, pluginClass, onload)); + + service = ""; + pluginClass = ""; + insideFeature = false; + onload = false; + } + } + private void setStartUrl(String src) { Pattern schemeRegex = Pattern.compile("^[a-z-]+://"); Matcher matcher = schemeRegex.matcher(src); diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java index 6874f5b2..bc262a6c 100755 --- a/framework/src/org/apache/cordova/CordovaActivity.java +++ b/framework/src/org/apache/cordova/CordovaActivity.java @@ -94,8 +94,6 @@ public class CordovaActivity extends Activity { // Read from config.xml: protected CordovaPreferences preferences; - protected Whitelist internalWhitelist; - protected Whitelist externalWhitelist; protected String launchUrl; protected ArrayList pluginEntries; protected CordovaInterfaceImpl cordovaInterface; @@ -142,7 +140,7 @@ public class CordovaActivity extends Activity { appView = makeWebView(); createViews(); //TODO: Add null check against CordovaInterfaceImpl, since this can be fragile - appView.init(cordovaInterface, pluginEntries, internalWhitelist, externalWhitelist, preferences); + appView.init(cordovaInterface, pluginEntries, preferences); cordovaInterface.setPluginManager(appView.getPluginManager()); // Wire the hardware volume controls to control media if desired. @@ -159,8 +157,6 @@ public class CordovaActivity extends Activity { preferences = parser.getPreferences(); preferences.setPreferencesBundle(getIntent().getExtras()); preferences.copyIntoIntentExtras(this); - internalWhitelist = parser.getInternalWhitelist(); - externalWhitelist = parser.getExternalWhitelist(); launchUrl = parser.getLaunchUrl(); pluginEntries = parser.getPluginEntries(); Config.parser = parser; @@ -354,7 +350,8 @@ public class CordovaActivity extends Activity { // If errorUrl specified, then load it final String errorUrl = preferences.getString("errorUrl", null); - if ((errorUrl != null) && (errorUrl.startsWith("file://") || internalWhitelist.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) { + CordovaUriHelper helper = new CordovaUriHelper(this.cordovaInterface, appView); + if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) { // Load URL on UI thread me.runOnUiThread(new Runnable() { public void run() { diff --git a/framework/src/org/apache/cordova/CordovaBridge.java b/framework/src/org/apache/cordova/CordovaBridge.java index becbd529..a6a97dfb 100644 --- a/framework/src/org/apache/cordova/CordovaBridge.java +++ b/framework/src/org/apache/cordova/CordovaBridge.java @@ -38,11 +38,13 @@ public class CordovaBridge { private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread. private String loadedUrl; private String appContentUrlPrefix; + protected CordovaUriHelper helper; - public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, String packageName) { + public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, String packageName, CordovaUriHelper helper) { this.pluginManager = pluginManager; this.jsMessageQueue = jsMessageQueue; this.appContentUrlPrefix = "content://" + packageName + "."; + this.helper = helper; } public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException { @@ -167,11 +169,11 @@ public class CordovaBridge { } else if (defaultValue != null && defaultValue.startsWith("gap_init:")) { // Protect against random iframes being able to talk through the bridge. - // Trust only file URLs and the start URL's domain. - // The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin. + // Trust only file URLs and pages which the app would have been allowed + // to navigate to anyway. if (origin.startsWith("file:") || origin.startsWith(this.appContentUrlPrefix) || - (origin.startsWith("http") && loadedUrl.startsWith(origin))) { + helper.shouldAllowNavigation(origin)) { // Enable the bridge int bridgeMode = Integer.parseInt(defaultValue.substring(9)); jsMessageQueue.setBridgeMode(bridgeMode); diff --git a/framework/src/org/apache/cordova/CordovaPlugin.java b/framework/src/org/apache/cordova/CordovaPlugin.java index cee6754a..71bf5cd6 100644 --- a/framework/src/org/apache/cordova/CordovaPlugin.java +++ b/framework/src/org/apache/cordova/CordovaPlugin.java @@ -162,19 +162,67 @@ public class CordovaPlugin { * Called when an activity you launched exits, giving you the requestCode you started it with, * the resultCode it returned, and any additional data from it. * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param intent An Intent, which can return result data to the caller (various data can be + * attached to Intent "extras"). */ public void onActivityResult(int requestCode, int resultCode, Intent intent) { } + /** + * Hook for blocking the loading of external resources. + * + * This will be called when the WebView's shouldInterceptRequest wants to + * know whether to open a connection to an external resource. Return false + * to block the request: if any plugin returns false, Cordova will block + * the request. If all plugins return null, the default policy will be + * enforced. If at least one plugin returns true, and no plugins return + * false, then the request will proceed. + * + * Note that this only affects resource requests which are routed through + * WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and + * img tag loads. WebSockets and media requests (such as