From 80df4a8fb22d7ea6557234cbc91cdd1983fde95a Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 26 Sep 2011 10:58:41 -0500 Subject: [PATCH] White list support. Pull request https://github.com/phonegap/phonegap-android/pull/211 from imhotep would not merge, so combined it with enhancements for this commit. --- framework/assets/js/app.js | 10 +++ framework/res/xml/phonegap.xml | 4 + framework/src/com/phonegap/App.java | 12 +++ framework/src/com/phonegap/DroidGap.java | 95 +++++++++++++++++++++++- 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100755 framework/res/xml/phonegap.xml diff --git a/framework/assets/js/app.js b/framework/assets/js/app.js index 788e8187..be3c2268 100755 --- a/framework/assets/js/app.js +++ b/framework/assets/js/app.js @@ -79,6 +79,16 @@ App.prototype.exitApp = function() { return PhoneGap.exec(null, null, "App", "exitApp", []); }; +/** + * Add entry to approved list of URLs (whitelist) that will be loaded into PhoneGap container instead of default browser. + * + * @param origin URL regular expression to allow + * @param subdomains T=include all subdomains under origin + */ +App.prototype.addWhiteListEntry = function(origin, subdomains) { + return PhoneGap.exec(null, null, "App", "addWhiteListEntry", [origin, subdomains]); +}; + PhoneGap.addConstructor(function() { navigator.app = new App(); }); diff --git a/framework/res/xml/phonegap.xml b/framework/res/xml/phonegap.xml new file mode 100755 index 00000000..5c6a441b --- /dev/null +++ b/framework/res/xml/phonegap.xml @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/com/phonegap/App.java b/framework/src/com/phonegap/App.java index d2a35fa9..a9325730 100755 --- a/framework/src/com/phonegap/App.java +++ b/framework/src/com/phonegap/App.java @@ -54,6 +54,9 @@ public class App extends Plugin { else if (action.equals("exitApp")) { this.exitApp(); } + else if (action.equals("addWhiteListEntry")) { + this.addWhiteListEntry(args.getString(0), args.optBoolean(1)); + } return new PluginResult(status, result); } catch (JSONException e) { return new PluginResult(PluginResult.Status.JSON_EXCEPTION); @@ -172,4 +175,13 @@ public class App extends Plugin { ((DroidGap)this.ctx).finish(); } + /** + * Add entry to approved list of URLs (whitelist) + * + * @param origin URL regular expression to allow + * @param subdomains T=include all subdomains under origin + */ + public void addWhiteListEntry(String origin, boolean subdomains) { + ((DroidGap)this.ctx).addWhiteListEntry(origin, subdomains); + } } diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index add7f9df..ea3bc10f 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -9,6 +9,11 @@ package com.phonegap; import java.util.HashMap; import java.util.Map.Entry; +import java.util.ArrayList; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.Iterator; +import java.io.IOException; import org.json.JSONArray; import org.json.JSONException; @@ -23,6 +28,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; +import android.content.res.XmlResourceParser; import android.graphics.Color; import android.graphics.Rect; import android.media.AudioManager; @@ -52,6 +58,7 @@ import android.widget.LinearLayout; import com.phonegap.api.PhonegapActivity; import com.phonegap.api.IPlugin; import com.phonegap.api.PluginManager; +import org.xmlpull.v1.XmlPullParserException; /** * This class is the main Android activity that represents the PhoneGap @@ -124,6 +131,8 @@ public class DroidGap extends PhonegapActivity { // The webview for our app protected WebView appView; protected WebViewClient webViewClient; + private ArrayList whiteList = new ArrayList(); + private HashMap whiteListCache = new HashMap(); protected LinearLayout root; public boolean bound = false; @@ -195,6 +204,9 @@ public class DroidGap extends PhonegapActivity { root.setBackgroundColor(this.backgroundColor); root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); + + // Load white list of allowed URLs + this.loadWhiteList(); // If url was passed in to intent, then init webview, which will load the url Bundle bundle = this.getIntent().getExtras(); @@ -882,7 +894,7 @@ public class DroidGap extends PhonegapActivity { // Security check to make sure any requests are coming from the page initially // loaded in webview and not another loaded in an iframe. boolean reqOk = false; - if (url.indexOf(this.ctx.baseUrl) == 0) { + if (url.indexOf(this.ctx.baseUrl) == 0 || isUrlWhiteListed(url)) { reqOk = true; } @@ -1119,7 +1131,7 @@ public class DroidGap extends PhonegapActivity { // If our app or file:, then load into a new phonegap webview container by starting a new instance of our activity. // Our app continues to run. When BACK is pressed, our app is redisplayed. - if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { + if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0 || isUrlWhiteListed(url)) { try { // Init parameters to new DroidGap activity and propagate existing parameters HashMap params = new HashMap(); @@ -1509,4 +1521,83 @@ public class DroidGap extends PhonegapActivity { oldWidth = width; } } + + /** + * Load approved list of URLs that can be loaded into DroidGap. + * This list is loaded from res/xml/phonegap.xml + * + */ + private void loadWhiteList() { + int id = getResources().getIdentifier("phonegap", "xml", getPackageName()); + if (id == 0) { + Log.i("PhoneGapLog", "phonegap.xml missing. Ignoring..."); + return; + } + XmlResourceParser xml = getResources().getXml(id); + int eventType = -1; + while (eventType != XmlResourceParser.END_DOCUMENT) { + if (eventType == XmlResourceParser.START_TAG) { + String strNode = xml.getName(); + if (strNode.equals("access")) { + String origin = xml.getAttributeValue(null, "origin"); + String subdomains = xml.getAttributeValue(null, "subdomains"); + if (origin != null) { + this.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); + } + } + } + try { + eventType = xml.next(); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Add entry to approved list of URLs (whitelist) + * + * @param origin URL regular expression to allow + * @param subdomains T=include all subdomains under origin + */ + public void addWhiteListEntry(String origin, boolean subdomains) { + if (subdomains) { + Log.d("PhoneGapLog", "Origin to allow with subdomains: "+origin); + whiteList.add(Pattern.compile(origin.replaceFirst("https{0,1}://", "^https{0,1}://.*"))); + } else { + Log.d("PhoneGapLog", "Origin to allow: "+origin); + whiteList.add(Pattern.compile(origin.replaceFirst("https{0,1}://", "^https{0,1}://"))); + } + } + + /** + * Determine if URL is in approved list of URLs to load. + * + * @param url + * @return + */ + private boolean isUrlWhiteListed(String url) { + + // Check to see if we have matched url previously + if (whiteListCache.get(url) != null) { + return true; + } + + // Look for match in white list + Iterator pit = whiteList.iterator(); + while (pit.hasNext()) { + Pattern p = pit.next(); + Matcher m = p.matcher(url); + + // If match found, then cache it to speed up subsequent comparisons + if (m.find()) { + whiteListCache.put(url, true); + return true; + } + } + return false; + } + } \ No newline at end of file