From ea02a62024b8bf99a82fb288f08dad0609ceb098 Mon Sep 17 00:00:00 2001 From: Pavel Rappo Date: Wed, 26 Mar 2014 19:53:07 +0000 Subject: [PATCH] 8035158: Remove dependency on sun.misc.RegexpPool and friends Reviewed-by: chegar, michaelm --- .../sun/net/spi/DefaultProxySelector.java | 73 +++-- jdk/test/java/net/ProxySelector/B8035158.java | 260 ++++++++++++++++++ 2 files changed, 315 insertions(+), 18 deletions(-) create mode 100644 jdk/test/java/net/ProxySelector/B8035158.java diff --git a/jdk/src/share/classes/sun/net/spi/DefaultProxySelector.java b/jdk/src/share/classes/sun/net/spi/DefaultProxySelector.java index f7018ed023f..36db753365a 100644 --- a/jdk/src/share/classes/sun/net/spi/DefaultProxySelector.java +++ b/jdk/src/share/classes/sun/net/spi/DefaultProxySelector.java @@ -32,13 +32,14 @@ import java.net.SocketAddress; import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.StringTokenizer; import java.io.IOException; -import sun.misc.RegexpPool; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.StringJoiner; +import java.util.regex.Pattern; import sun.net.NetProperties; import sun.net.SocksProxy; +import static java.util.regex.Pattern.quote; /** * Supports proxy settings using system properties This proxy selector @@ -107,7 +108,7 @@ public class DefaultProxySelector extends ProxySelector { /** * How to deal with "non proxy hosts": - * since we do have to generate a RegexpPool we don't want to do that if + * since we do have to generate a pattern we don't want to do that if * it's not necessary. Therefore we do cache the result, on a per-protocol * basis, and change it only when the "source", i.e. the system property, * did change. @@ -119,17 +120,17 @@ public class DefaultProxySelector extends ProxySelector { static final String defStringVal = "localhost|127.*|[::1]|0.0.0.0|[::0]"; String hostsSource; - RegexpPool hostsPool; + Pattern pattern; final String property; final String defaultVal; static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal); static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal); static NonProxyInfo socksNonProxyInfo = new NonProxyInfo("socksNonProxyHosts", null, null, defStringVal); - NonProxyInfo(String p, String s, RegexpPool pool, String d) { + NonProxyInfo(String p, String s, Pattern pattern, String d) { property = p; hostsSource = s; - hostsPool = pool; + this.pattern = pattern; defaultVal = d; } } @@ -255,7 +256,7 @@ public class DefaultProxySelector extends ProxySelector { nphosts = nprop.defaultVal; } else { nprop.hostsSource = null; - nprop.hostsPool = null; + nprop.pattern = null; } } else if (nphosts.length() != 0) { // add the required default patterns @@ -266,20 +267,11 @@ public class DefaultProxySelector extends ProxySelector { } if (nphosts != null) { if (!nphosts.equals(nprop.hostsSource)) { - RegexpPool pool = new RegexpPool(); - StringTokenizer st = new StringTokenizer(nphosts, "|", false); - try { - while (st.hasMoreTokens()) { - pool.add(st.nextToken().toLowerCase(), Boolean.TRUE); - } - } catch (sun.misc.REException ex) { - } - nprop.hostsPool = pool; + nprop.pattern = toPattern(nphosts); nprop.hostsSource = nphosts; } } - if (nprop.hostsPool != null && - nprop.hostsPool.match(urlhost) != null) { + if (shouldNotUseProxyFor(nprop.pattern, urlhost)) { return Proxy.NO_PROXY; } } @@ -355,4 +347,49 @@ public class DefaultProxySelector extends ProxySelector { private native static boolean init(); private synchronized native Proxy getSystemProxy(String protocol, String host); + + /** + * @return {@code true} if given this pattern for non-proxy hosts and this + * urlhost the proxy should NOT be used to access this urlhost + */ + static boolean shouldNotUseProxyFor(Pattern pattern, String urlhost) { + if (pattern == null || urlhost.isEmpty()) + return false; + boolean matches = pattern.matcher(urlhost).matches(); + return matches; + } + + /** + * @param mask non-null mask + * @return {@link java.util.regex.Pattern} corresponding to this mask + * or {@code null} in case mask should not match anything + */ + static Pattern toPattern(String mask) { + boolean disjunctionEmpty = true; + StringJoiner joiner = new StringJoiner("|"); + for (String disjunct : mask.split("\\|")) { + if (disjunct.isEmpty()) + continue; + disjunctionEmpty = false; + String regex = disjunctToRegex(disjunct.toLowerCase()); + joiner.add(regex); + } + return disjunctionEmpty ? null : Pattern.compile(joiner.toString()); + } + + /** + * @param disjunct non-null mask disjunct + * @return java regex string corresponding to this mask + */ + static String disjunctToRegex(String disjunct) { + String regex; + if (disjunct.startsWith("*")) { + regex = ".*" + quote(disjunct.substring(1)); + } else if (disjunct.endsWith("*")) { + regex = quote(disjunct.substring(0, disjunct.length() - 1)) + ".*"; + } else { + regex = quote(disjunct); + } + return regex; + } } diff --git a/jdk/test/java/net/ProxySelector/B8035158.java b/jdk/test/java/net/ProxySelector/B8035158.java new file mode 100644 index 00000000000..bc498923d0f --- /dev/null +++ b/jdk/test/java/net/ProxySelector/B8035158.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8035158 + * @run main/othervm B8035158 + */ + +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.util.*; +import java.util.concurrent.Callable; + +public class B8035158 { + + public static void main(String[] args) { + for (TestCase t : emptyNonProxiesHosts()) t.run(); + for (TestCase t : nonEmptyNonProxiesHosts()) t.run(); + for (TestCase t : misc()) t.run(); + } + + // Setting http.nonProxyHosts to an empty string has an effect of + // not including default hosts to the list of exceptions + // (i.e. if you want everything to be connected directly rather than + // through proxy, you should set this property to an empty string) + private static Collection emptyNonProxiesHosts() { + List tests = new LinkedList<>(); + String[] loopbacks = {"localhost", "[::1]", "[::0]", "0.0.0.0", + "127.0.0.0", "127.0.0.1", "127.0.1.0", "127.0.1.1", + "127.1.0.0", "127.1.0.1", "127.1.1.0", "127.1.1.1"}; + Map properties = new HashMap<>(); + properties.put("http.proxyHost", "http://proxy.example.com"); + properties.put("http.nonProxyHosts", ""); + for (String s : loopbacks) { + tests.add(new TestCase(properties, "http://" + s, true)); + } + return tests; + } + + // No matter what is set into the http.nonProxyHosts (as far as it is not + // an empty string) loopback address aliases must be always connected + // directly + private static Collection nonEmptyNonProxiesHosts() { + List tests = new LinkedList<>(); + String[] nonProxyHosts = { + "google.com", + "localhost", "[::1]", "[::0]", "0.0.0.0", + "127.0.0.0", "127.0.0.1", "127.0.1.0", "127.0.1.1", + "127.1.0.0", "127.1.0.1", "127.1.1.0", "127.1.1.1"}; + String[] loopbacks = {"localhost", "[::1]", "[::0]", "0.0.0.0", + "127.0.0.0", "127.0.0.1", "127.0.1.0", "127.0.1.1", + "127.1.0.0", "127.1.0.1", "127.1.1.0", "127.1.1.1"}; + for (String h : nonProxyHosts) { + for (String s : loopbacks) { + Map properties = new HashMap<>(); + properties.put("http.proxyHost", "http://proxy.example.com"); + properties.put("http.nonProxyHosts", h); + tests.add(new TestCase(properties, "http://" + s, false)); + } + } + return tests; + } + + // unsorted tests + private static Collection misc() { + List t = new LinkedList<>(); + t.add(new TestCase("oracle.com", "http://137.254.16.101", true)); + t.add(new TestCase("google.com", "http://74.125.200.101", true)); + + t.add(new TestCase("google.com|google.ie", "http://google.co.uk", + true)); + t.add(new TestCase("google.com|google.ie", "http://google.com", + false)); + t.add(new TestCase("google.com|google.ie", "http://google.ie", + false)); + + t.add(new TestCase("google.com|bing.com|yahoo.com", + "http://127.0.0.1", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", + "http://google.com", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", + "http://bing.com", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", + "http://yahoo.com", false)); + + t.add(new TestCase("google.com|bing.com", "http://google.com", false)); + t.add(new TestCase("google.com|bing.com", "http://bing.com", false)); + t.add(new TestCase("google.com|bing.com", "http://yahoo.com", + true)); + t.add(new TestCase("google.com|bing.co*", "http://google.com", false)); + t.add(new TestCase("google.com|bing.co*", "http://bing.com", false)); + t.add(new TestCase("google.com|bing.co*", "http://yahoo.com", + true)); + t.add(new TestCase("google.com|*ing.com", "http://google.com", false)); + t.add(new TestCase("google.com|*ing.com", "http://bing.com", false)); + t.add(new TestCase("google.com|*ing.com", "http://yahoo.com", + true)); + t.add(new TestCase("google.co*|bing.com", "http://google.com", false)); + t.add(new TestCase("google.co*|bing.com", "http://bing.com", false)); + t.add(new TestCase("google.co*|bing.com", "http://yahoo.com", + true)); + t.add(new TestCase("google.co*|bing.co*", "http://google.com", false)); + t.add(new TestCase("google.co*|bing.co*", "http://bing.com", false)); + t.add(new TestCase("google.co*|bing.co*", "http://yahoo.com", + true)); + t.add(new TestCase("google.co*|*ing.com", "http://google.com", false)); + t.add(new TestCase("google.co*|*ing.com", "http://bing.com", false)); + t.add(new TestCase("google.co*|*ing.com", "http://yahoo.com", + true)); + t.add(new TestCase("*oogle.com|bing.com", "http://google.com", false)); + t.add(new TestCase("*oogle.com|bing.com", "http://bing.com", false)); + t.add(new TestCase("*oogle.com|bing.com", "http://yahoo.com", + true)); + t.add(new TestCase("*oogle.com|bing.co*", "http://google.com", false)); + t.add(new TestCase("*oogle.com|bing.co*", "http://bing.com", false)); + t.add(new TestCase("*oogle.com|bing.co*", "http://yahoo.com", + true)); + t.add(new TestCase("*oogle.com|*ing.com", "http://google.com", false)); + t.add(new TestCase("*oogle.com|*ing.com", "http://bing.com", false)); + t.add(new TestCase("*oogle.com|*ing.com", "http://yahoo.com", + true)); + + t.add(new TestCase("google.com|bing.com|yahoo.com", "http://google.com", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", "http://bing.com", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", "http://yahoo.com", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", + "http://duckduckgo.com", true)); + + t.add(new TestCase("p-proxy.com", "http://p-proxy.com", false)); + t.add(new TestCase("google.co*|google.ie", "http://google.co.uk", + false)); + + t.add(new TestCase("*oracle.com", "http://my.oracle.com", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", "http://127.0.0.1", false)); + t.add(new TestCase("google.com|bing.com|yahoo.com", "http://yahoo.com", false)); + + // example from + // http://docs.oracle.com/javase/7/docs/technotes/guides/net/proxies.html + t.add(new TestCase("localhost|host.example.com", "http://localhost", + false)); + t.add(new TestCase("localhost|host.example.com", + "http://host.example.com", false)); + t.add(new TestCase("localhost|host.example.com", + "http://google.com", true)); + return t; + } + + + private static T withSystemPropertiesSet( + Map localProperties, + Callable code) { + Map backup = new HashMap<>(); + try { + backupAndSetProperties(localProperties, backup); + return code.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + restoreProperties(backup); + } + } + + private static void backupAndSetProperties( + Map localProperties, + Map oldProperties) { + for (Map.Entry e : localProperties.entrySet()) { + String oldValue = System.setProperty(e.getKey(), e.getValue()); + oldProperties.put(e.getKey(), oldValue); + } + } + + private static void restoreProperties(Map oldProperties) { + for (Map.Entry e : oldProperties.entrySet()) { + String oldValue = e.getValue(); + String key = e.getKey(); + if (oldValue == null) + System.getProperties().remove(key); + else + System.setProperty(key, oldValue); + } + } + + private static class TestCase { + + final Map localProperties; + final String urlhost; + final boolean expectedProxying; + + TestCase(String nonProxyHosts, String urlhost, + boolean expectedProxying) { + this(nonProxyHosts, "proxy.example.com", urlhost, + expectedProxying); + } + + TestCase(String nonProxyHosts, String proxyHost, String urlhost, + boolean expectedProxying) { + this(new HashMap() { + { + put("http.nonProxyHosts", nonProxyHosts); + put("http.proxyHost", proxyHost); + } + }, urlhost, expectedProxying); + } + + TestCase(Map localProperties, String urlhost, + boolean expectedProxying) { + this.localProperties = localProperties; + this.urlhost = urlhost; + this.expectedProxying = expectedProxying; + } + + void run() { + System.out.printf("urlhost=%s properties=%s: proxied? %s%n", + urlhost, localProperties, expectedProxying); + + List proxies = withSystemPropertiesSet(localProperties, + () -> ProxySelector.getDefault().select( + URI.create(urlhost)) + ); + + verify(proxies); + } + + void verify(List proxies) { + + boolean actualProxying = !(proxies.size() == 1 && + proxies.get(0).type() == Proxy.Type.DIRECT); + + if (actualProxying != expectedProxying) + throw new AssertionError(String.format( + "Expected %s connection for %s (given " + + "properties=%s). Here's the list of proxies " + + "returned: %s", + expectedProxying ? "proxied" : "direct", urlhost, + localProperties, proxies + )); + } + } +}