| /* |
| * Copyright 2007 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.shell; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| /** |
| * This class contains utility functions to do whitelist/blacklist handling. |
| */ |
| public class BrowserWidgetHostChecker { |
| |
| /** |
| * The set of always allowed URLs, which are immune to blacklisting. |
| */ |
| private static final Pattern alwaysValidHttpHosts; |
| |
| /** |
| * The set of blacklisted URLs. |
| */ |
| private static final Set<String> invalidHttpHosts = new HashSet<String>(); |
| |
| private static String oldBlackList = null; |
| |
| private static String oldWhiteList = null; |
| |
| /** |
| * The set of whitelisted URLs. |
| */ |
| private static final Set<String> validHttpHosts = new HashSet<String>(); |
| |
| static { |
| Set<String> regexes = new HashSet<String>(); |
| // Regular URLs may or may not have a port, and must either end with |
| // the host+port, or be followed by a slash (to avoid attacks like |
| // localhost.evildomain.org). |
| String portSuffix = "(:\\d+)?(/.*)?"; |
| regexes.add("https?://localhost" + portSuffix); |
| regexes.add("https?://localhost[.]localdomain" + portSuffix); |
| regexes.add("https?://127[.]0[.]0[.]1" + portSuffix); |
| String hostName; |
| try { |
| hostName = InetAddress.getLocalHost().getHostName(); |
| if (hostName != null) { |
| hostName = hostName.replace(".", "[.]"); |
| regexes.add("https?://" + hostName + portSuffix); |
| } |
| } catch (UnknownHostException e) { |
| // Ignore |
| } |
| String addr; |
| try { |
| addr = InetAddress.getLocalHost().getHostAddress(); |
| if (addr != null) { |
| addr = addr.replace(".", "[.]"); |
| regexes.add("https?://" + addr + portSuffix); |
| } |
| } catch (UnknownHostException e) { |
| // Ignore |
| } |
| regexes.add("file:.*"); |
| regexes.add("about:.*"); |
| regexes.add("res:.*"); |
| regexes.add("javascript:.*"); |
| regexes.add("([a-z][:])[/\\\\].*"); |
| // matches c:\ and c:/ |
| StringBuilder buf = new StringBuilder(); |
| String prefix = "("; |
| for (String regex : regexes) { |
| buf.append(prefix).append('(').append(regex).append(')'); |
| prefix = "|"; |
| } |
| buf.append(")"); |
| alwaysValidHttpHosts = Pattern.compile(buf.toString(), |
| Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); |
| } |
| |
| /** |
| * This method blacklists the supplied regexes, separated by comma or space. |
| * |
| * @param regexes the regexes to be forbidden |
| */ |
| public static boolean blacklistRegexes(String regexes) { |
| return addRegex(regexes, false); |
| } |
| |
| /** |
| * This method blacklists the supplied URL, and any that share the same host. |
| * |
| * @param url the host to be forbidden |
| */ |
| public static void blacklistURL(String url) { |
| String hostRegex = computeHostRegex(url); |
| blacklistRegexes(hostRegex); |
| } |
| |
| /** |
| * This method checks the host to see if it is in the supplied set of regexes. |
| * |
| * @param hostUnderConsideration the host to be checked |
| * @param hosts the set of regexes to be checked against |
| * @return true if the host matches |
| */ |
| public static String checkHost(String hostUnderConsideration, |
| Set<String> hosts) { |
| // TODO(jat): build a single regex instead of looping |
| hostUnderConsideration = hostUnderConsideration.toLowerCase(Locale.ENGLISH); |
| for (String rule : hosts) { |
| rule = rule.toLowerCase(Locale.ENGLISH); |
| // match on lowercased regex |
| if (hostUnderConsideration.matches(".*" + rule + ".*")) { |
| return rule; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * This method computes the host regular expression for the given url. |
| * |
| * @param url the url to be allowed or disallowed |
| * @return the regex that matches the host in the url |
| */ |
| public static String computeHostRegex(String url) { |
| // the entire URL up to the first slash not prefixed by a slash or colon. |
| String raw = url.split("(?<![:/])/")[0]; |
| // escape the dots and put a begin line specifier on the result |
| return "^" + escapeString(raw); |
| } |
| |
| /** |
| * This method formats the blacklist for display in the treeLogger. |
| * |
| * @return the list of regexes as a String |
| */ |
| public static String formatBlackList() { |
| return formatRules(invalidHttpHosts); |
| } |
| |
| /** |
| * This method formats the list of rules for display in the treeLogger. |
| * |
| * @param hosts the set of regexes that match hosts |
| * @return the list of regexes as a String |
| */ |
| public static String formatRules(Set<String> hosts) { |
| StringBuffer out = new StringBuffer(); |
| for (Iterator<String> i = hosts.iterator(); i.hasNext();) { |
| String rule = i.next(); |
| out.append(rule); |
| out.append(" "); |
| } |
| return out.toString(); |
| } |
| |
| /** |
| * This method formats the whitelist for display in the treeLogger. |
| * |
| * @return the list of regexes as a String |
| */ |
| public static String formatWhiteList() { |
| return formatRules(validHttpHosts); |
| } |
| |
| /** |
| * This method returns true if the host is always admissible, regardless of |
| * the blacklist. |
| * |
| * @param url the URL to be verified |
| * @return returns true if the host is always admissible |
| */ |
| public static boolean isAlwaysWhitelisted(String url) { |
| return alwaysValidHttpHosts.matcher(url).matches(); |
| } |
| |
| /** |
| * This method returns non-null if the host is forbidden. |
| * |
| * @param url the URL to be verified |
| * @return returns the regex that specified the host matches the blacklist |
| */ |
| public static String matchBlacklisted(String url) { |
| oldBlackList = formatBlackList(); |
| return checkHost(url, invalidHttpHosts); |
| } |
| |
| /** |
| * This method returns null if the host is admissible, provided it is not on |
| * the blacklist. |
| * |
| * @param url the URL to be verified |
| * @return returns the regex that specified the host matches the whitelist |
| */ |
| public static String matchWhitelisted(String url) { |
| oldWhiteList = formatWhiteList(); |
| return checkHost(url, validHttpHosts); |
| } |
| |
| /** |
| * This method formats a message, and logs it to the treelogger, stating that |
| * the url was blocked. |
| * |
| * @param url the URL that was disallowed |
| * @param header the treelogger under which these messages will be put |
| * @param msgType either a caution or an error |
| */ |
| public static void notifyBlacklistedHost(String blacklistRuleFound, |
| String url, TreeLogger header, TreeLogger.Type msgType) { |
| TreeLogger reason = header.branch(msgType, "reason: " + url |
| + " is blacklisted", null); |
| reason.log(msgType, "To fix: remove \"" + blacklistRuleFound |
| + "\" from system property gwt.hosts.blacklist", null); |
| } |
| |
| /** |
| * This method formats a message, and logs it to the treelogger, stating that |
| * the url was not trusted. |
| * |
| * @param url the URL that provoked the dialog box |
| * @param header the treelogger under which these messages will be put |
| * @param msgType either a caution or an error |
| */ |
| public static void notifyUntrustedHost(String url, TreeLogger header, |
| TreeLogger.Type msgType) { |
| String whiteListStr = oldWhiteList; |
| String blackListStr = oldBlackList; |
| String hostRegex = computeHostRegex(url); |
| TreeLogger reason = header.branch(msgType, "reason: " + url |
| + " is not in the whitelist", null); |
| reason.log(msgType, "whitelist: " + whiteListStr, null); |
| reason.log(msgType, "blacklist: " + blackListStr, null); |
| TreeLogger fix = header.branch(msgType, "To fix: add regex matching " |
| + "URL to -whitelist command line argument", null); |
| fix.log(msgType, "Example: -whitelist \"" + whiteListStr + " " + hostRegex |
| + "\"", null); |
| TreeLogger reject = header.branch(msgType, |
| "To reject automatically: add regex matching " |
| + "URL to -blacklist command line argument", null); |
| reject.log(msgType, "Example: -blacklist \"" + blackListStr + " " |
| + hostRegex + "\"", null); |
| } |
| |
| /** |
| * This method whitelists the supplied String of regexes, separated by comma |
| * or space. |
| * |
| * @param regexes the regexes to be allowed |
| */ |
| public static boolean whitelistRegexes(String regexes) { |
| return addRegex(regexes, true); |
| } |
| |
| /** |
| * This method whitelists the supplied URL, and any that share the same host. |
| * |
| * @param url the host to be allowed |
| */ |
| public static void whitelistURL(String url) { |
| String hostRegex = computeHostRegex(url); |
| whitelistRegexes(hostRegex); |
| } |
| |
| /** |
| * This method blacklists or whitelists the supplied regexes, and any that |
| * share the same host. |
| * |
| * @param whitelist if <code>true</code> the host will be whitelisted |
| * @param regexes the regular expressions to be forbidden, seperated by comma |
| * or space |
| */ |
| private static boolean addRegex(String regexes, boolean whitelist) { |
| if (regexes.equals("")) { |
| return true; // adding empty string is harmless and happens by default |
| } |
| String[] items = regexes.split("[ ,]"); |
| for (int i = 0; i < items.length; i++) { |
| try { |
| Pattern.compile(items[i]); |
| } catch (PatternSyntaxException e) { |
| System.err.println("The regex '" + items[i] + " has syntax errors."); |
| System.err.println(e.toString()); |
| return false; |
| } |
| if (whitelist) { |
| validHttpHosts.add(items[i]); |
| } else { |
| invalidHttpHosts.add(items[i]); |
| } |
| } |
| return true; |
| } |
| |
| private static String escapeString(String raw) { |
| StringBuffer out = new StringBuffer(); |
| for (int i = 0; i < raw.length(); i++) { |
| char c = raw.charAt(i); |
| if (Character.isLetterOrDigit(c) || c == '-' || c == '_') { |
| out.append(c); |
| } else if (c == '\\') { |
| out.append("[\\\\]"); |
| } else if (c == ']') { |
| out.append("[\\]]"); |
| } else if (c == '^') { |
| out.append("[\\^]"); |
| } else if (c == '[') { |
| out.append("[\\[]"); |
| } else { |
| out.append("["); |
| out.append(c); |
| out.append("]"); |
| } |
| } |
| return out.toString(); |
| } |
| |
| } |