You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ia...@apache.org on 2013/07/23 17:27:52 UTC

android commit: [CB-4096] Implemente new unified whitelist for android

Updated Branches:
  refs/heads/master 7c7230dd3 -> 463c7b502


[CB-4096] Implemente new unified whitelist for android


Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/463c7b50
Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/463c7b50
Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/463c7b50

Branch: refs/heads/master
Commit: 463c7b50277dda5978e2d5e1f1e296ab9e6a6e82
Parents: 7c7230d
Author: Ian Clelland <ic...@chromium.org>
Authored: Sat Jul 13 21:36:43 2013 -0400
Committer: Ian Clelland <ic...@chromium.org>
Committed: Tue Jul 23 11:23:14 2013 -0400

----------------------------------------------------------------------
 framework/src/org/apache/cordova/Config.java    |   5 +
 framework/src/org/apache/cordova/Whitelist.java | 191 +++++++++++--------
 2 files changed, 117 insertions(+), 79 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/463c7b50/framework/src/org/apache/cordova/Config.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/Config.java b/framework/src/org/apache/cordova/Config.java
index 6e0c147..51f8f3f 100644
--- a/framework/src/org/apache/cordova/Config.java
+++ b/framework/src/org/apache/cordova/Config.java
@@ -78,6 +78,11 @@ public class Config {
             return;
         }
 
+        // Add implicitly allowed URLs
+        whitelist.addWhiteListEntry("file:///*", false);
+        whitelist.addWhiteListEntry("content:///*", false);
+        whitelist.addWhiteListEntry("data:*", false);
+
         XmlResourceParser xml = action.getResources().getXml(id);
         int eventType = -1;
         while (eventType != XmlResourceParser.END_DOCUMENT) {

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/463c7b50/framework/src/org/apache/cordova/Whitelist.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/Whitelist.java b/framework/src/org/apache/cordova/Whitelist.java
index 736e5a7..59d22ab 100644
--- a/framework/src/org/apache/cordova/Whitelist.java
+++ b/framework/src/org/apache/cordova/Whitelist.java
@@ -1,89 +1,128 @@
 package org.apache.cordova;
 
+import java.net.MalformedURLException;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.cordova.LOG;
 
+import android.net.Uri;
+
 public class Whitelist {
-    private ArrayList<Pattern> whiteList;
-    private HashMap<String, Boolean> whiteListCache;
+    private static class URLPattern {
+        public Pattern scheme;
+        public Pattern host;
+        public Integer port;
+        public Pattern path;
 
-    public static final String TAG = "Whitelist";
+        private String regexFromPattern(String pattern, boolean allowWildcards) {
+            final String toReplace = "\\.[]{}()^$?+|";
+            StringBuilder regex = new StringBuilder();
+            for (int i=0; i < pattern.length(); i++) {
+                char c = pattern.charAt(i);
+                if (c == '*' && allowWildcards) {
+                    regex.append(".");
+                } else if (toReplace.indexOf(c) > -1) {
+                    regex.append('\\');
+                }
+                regex.append(c);
+            }
+            return regex.toString();
+        }
 
-	public Whitelist() {
-		this.whiteList = new ArrayList<Pattern>();
-		this.whiteListCache = new HashMap<String, Boolean>();
-	}
+        public URLPattern(String scheme, String host, String port, String path) throws MalformedURLException {
+            try {
+                if (scheme == null || "*".equals(scheme)) {
+                    this.scheme = null;
+                } else {
+                    this.scheme = Pattern.compile(regexFromPattern(scheme, false));
+                }
+                if ("*".equals(host)) {
+                    this.host = null;
+                } else if (host.startsWith("*.")) {
+                    this.host = Pattern.compile("([a-z0-9.-]*\\.)?" + regexFromPattern(host.substring(2), false));
+                } else {
+                    this.host = Pattern.compile(regexFromPattern(host, false));
+                }
+                if (port == null || "*".equals(port)) {
+                    this.port = null;
+                } else {
+                    this.port = Integer.parseInt(port,10);
+                }
+                if (path == null || "/*".equals(path)) {
+                    this.path = null;
+                } else {
+                    this.path = Pattern.compile(regexFromPattern(path, true));
+                }
+            } catch (NumberFormatException e) {
+                throw new MalformedURLException("Port must be a number");
+            }
+        }
 
-    /*
-     * Trying to figure out how to match * is a pain
-     * So, we don't use a regex here
-     */
-    
-    private boolean originHasWildcard(String origin){
-        //First, check for a protocol, then split it if it has one.
-        if(origin.contains("//"))
-        {
-            origin = origin.split("//")[1];
+        public boolean matches(Uri uri) {
+            try {
+                return ((scheme == null || scheme.matcher(uri.getScheme()).matches()) &&
+                        (host == null || host.matcher(uri.getHost()).matches()) &&
+                        (port == null || port.equals(uri.getPort())) &&
+                        (path == null || path.matcher(uri.getPath()).matches()));
+            } catch (Exception e) {
+                LOG.d(TAG, e.toString());
+                return false;
+            }
         }
-        return origin.startsWith("*");
     }
 
+    private ArrayList<URLPattern> whiteList;
+
+    public static final String TAG = "Whitelist";
+
+    public Whitelist() {
+        this.whiteList = new ArrayList<URLPattern>();
+    }
+
+    /* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html)
+     *
+     * <url-pattern> := <scheme>://<host><path>
+     * <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome-extension'
+     * <host> := '*' | '*.' <any char except '/' and '*'>+
+     * <path> := '/' <any chars>
+     *
+     * We extend this to explicitly allow a port attached to the host, and we allow
+     * the scheme to be omitted for backwards compatibility. (Also host is not required
+     * to begin with a "*" or "*.".)
+     */
     public void addWhiteListEntry(String origin, boolean subdomains) {
-        try {
-            // Unlimited access to network resources
-            if (origin.compareTo("*") == 0) {
-                LOG.d(TAG, "Unlimited access to network resources");
-                whiteList.add(Pattern.compile(".*"));
-            }
-            else { // specific access
-                // check if subdomains should be included
-                if(originHasWildcard(origin))
-                {
-                    subdomains = true;
-                    //Remove the wildcard so this works properly
-                    origin = origin.replace("*.", "");
+        if (whiteList != null) {
+            try {
+                // Unlimited access to network resources
+                if (origin.compareTo("*") == 0) {
+                    LOG.d(TAG, "Unlimited access to network resources");
+                    whiteList = null;
                 }
-                
-                // TODO: we should not add more domains if * has already been added
-                Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
-                Matcher matcher = schemeRegex.matcher(origin);
-                if (subdomains) {
-                    // Check for http or https protocols
-                    if (origin.startsWith("http")) {
-                        whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?")));
-                    }
-                    // Check for other protocols
-                    else if(matcher.find()){
-                        whiteList.add(Pattern.compile("^" + origin.replaceFirst("//", "//(.*\\.)?")));
+                else { // specific access
+                    Pattern parts = Pattern.compile("^((\\*|[a-z-]+)://)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
+                    Matcher m = parts.matcher(origin);
+                    if (m.matches()) {
+                        String scheme = m.group(2);
+                        String host = m.group(3);
+                        // Special case for two urls which are allowed to have empty hosts
+                        if (("file".equals(scheme) || "content".equals(scheme)) && host == null) host = "*";
+                        String port = m.group(7);
+                        String path = m.group(8);
+                        if (scheme == null) {
+                            // XXX making it stupid friendly for people who forget to include protocol/SSL
+                            whiteList.add(new URLPattern("http", host, port, path));
+                            whiteList.add(new URLPattern("https", host, port, path));
+                        } else {
+                            whiteList.add(new URLPattern(scheme, host, port, path));
+                        }
                     }
-                    // XXX making it stupid friendly for people who forget to include protocol/SSL
-                    else {
-                        whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin));
-                    }
-                    LOG.d(TAG, "Origin to allow with subdomains: %s", origin);
-                } else {
-                    // Check for http or https protocols
-                    if (origin.startsWith("http")) {
-                        whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://")));
-                    }
-                    // Check for other protocols
-                    else if(matcher.find()){
-                        whiteList.add(Pattern.compile("^" + origin));
-                    }
-                    // XXX making it stupid friendly for people who forget to include protocol/SSL
-                    else {
-                        whiteList.add(Pattern.compile("^https?://" + origin));
-                    }
-                    LOG.d(TAG, "Origin to allow: %s", origin);
                 }
+            } catch (Exception e) {
+                LOG.d(TAG, "Failed to add origin %s", origin);
             }
-        } catch (Exception e) {
-            LOG.d(TAG, "Failed to add origin %s", origin);
         }
     }
 
@@ -91,25 +130,19 @@ public class Whitelist {
     /**
      * Determine if URL is in approved list of URLs to load.
      *
-     * @param url
+     * @param uri
      * @return
      */
-    public boolean isUrlWhiteListed(String url) {
-
-        // Check to see if we have matched url previously
-        if (whiteListCache.get(url) != null) {
-            return true;
-        }
+    public boolean isUrlWhiteListed(String uri) {
+        // If there is no whitelist, then it's wide open
+        if (whiteList == null) return true;
 
+        Uri parsedUri = Uri.parse(uri);
         // Look for match in white list
-        Iterator<Pattern> pit = whiteList.iterator();
+        Iterator<URLPattern> 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);
+            URLPattern p = pit.next();
+            if (p.matches(parsedUri)) {
                 return true;
             }
         }