diff --git a/framework/src/org/apache/cordova/Config.java b/framework/src/org/apache/cordova/Config.java index 6e0c147f..51f8f3f8 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) { diff --git a/framework/src/org/apache/cordova/Whitelist.java b/framework/src/org/apache/cordova/Whitelist.java index 736e5a78..59d22ab9 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 whiteList; - private HashMap whiteListCache; + private static class URLPattern { + public Pattern scheme; + public Pattern host; + public Integer port; + public Pattern path; + + 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 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"); + } + } + + 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; + } + } + } + + private ArrayList whiteList; public static final String TAG = "Whitelist"; - public Whitelist() { - this.whiteList = new ArrayList(); - this.whiteListCache = new HashMap(); - } - - /* - * 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]; - } - return origin.startsWith("*"); + public Whitelist() { + this.whiteList = new ArrayList(); } + /* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html) + * + * := :// + * := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome-extension' + * := '*' | '*.' + + * := '/' + * + * 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?://(.*\\.)?"))); + 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)); + } } - // Check for other protocols - else if(matcher.find()){ - whiteList.add(Pattern.compile("^" + origin.replaceFirst("//", "//(.*\\.)?"))); - } - // 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 pit = whiteList.iterator(); + 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); + URLPattern p = pit.next(); + if (p.matches(parsedUri)) { return true; } }