Path: blob/master/src/java.base/share/classes/sun/net/spi/DefaultProxySelector.java
41159 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.spi;2627import java.net.InetSocketAddress;28import java.net.Proxy;29import java.net.ProxySelector;30import java.net.SocketAddress;31import java.net.URI;32import java.util.Collections;33import java.util.List;34import java.io.IOException;35import java.security.AccessController;36import java.security.PrivilegedAction;37import java.util.StringJoiner;38import java.util.regex.Pattern;39import java.util.stream.Stream;40import sun.net.NetProperties;41import sun.net.SocksProxy;42import static java.util.regex.Pattern.quote;43import static java.util.stream.Collectors.collectingAndThen;44import static java.util.stream.Collectors.toList;4546/**47* Supports proxy settings using system properties This proxy selector48* provides backward compatibility with the old http protocol handler49* as far as how proxy is set50*51* Most of the implementation copied from the old http protocol handler52*53* Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort,54* proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks.55*/56public class DefaultProxySelector extends ProxySelector {5758/**59* This is where we define all the valid System Properties we have to60* support for each given protocol.61* The format of this 2 dimensional array is :62* - 1 row per protocol (http, ftp, ...)63* - 1st element of each row is the protocol name64* - subsequent elements are prefixes for Host & Port properties65* listed in order of priority.66* Example:67* {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},68* means for FTP we try in that oder:69* + ftp.proxyHost & ftp.proxyPort70* + ftpProxyHost & ftpProxyPort71* + proxyHost & proxyPort72* + socksProxyHost & socksProxyPort73*74* Note that the socksProxy should *always* be the last on the list75*/76static final String[][] props = {77/*78* protocol, Property prefix 1, Property prefix 2, ...79*/80{"http", "http.proxy", "proxy", "socksProxy"},81{"https", "https.proxy", "proxy", "socksProxy"},82{"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},83{"socket", "socksProxy"}84};8586private static final String SOCKS_PROXY_VERSION = "socksProxyVersion";8788private static boolean hasSystemProxies = false;8990private static final List<Proxy> NO_PROXY_LIST = List.of(Proxy.NO_PROXY);9192static {93final String key = "java.net.useSystemProxies";94@SuppressWarnings("removal")95Boolean b = AccessController.doPrivileged(96new PrivilegedAction<Boolean>() {97public Boolean run() {98return NetProperties.getBoolean(key);99}});100if (b != null && b.booleanValue()) {101jdk.internal.loader.BootLoader.loadLibrary("net");102hasSystemProxies = init();103}104}105106@SuppressWarnings("removal")107public static int socksProxyVersion() {108return AccessController.doPrivileged(109new PrivilegedAction<Integer>() {110@Override public Integer run() {111return NetProperties.getInteger(SOCKS_PROXY_VERSION, 5);112}113});114}115116/**117* How to deal with "non proxy hosts":118* since we do have to generate a pattern we don't want to do that if119* it's not necessary. Therefore we do cache the result, on a per-protocol120* basis, and change it only when the "source", i.e. the system property,121* did change.122*/123124static class NonProxyInfo {125// Default value for nonProxyHosts, this provides backward compatibility126// by excluding localhost and its litteral notations.127static final String defStringVal = "localhost|127.*|[::1]|0.0.0.0|[::0]";128129String hostsSource;130Pattern pattern;131final String property;132final String defaultVal;133static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal);134static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal);135static NonProxyInfo socksNonProxyInfo = new NonProxyInfo("socksNonProxyHosts", null, null, defStringVal);136137NonProxyInfo(String p, String s, Pattern pattern, String d) {138property = p;139hostsSource = s;140this.pattern = pattern;141defaultVal = d;142}143}144145146/**147* select() method. Where all the hard work is done.148* Build a list of proxies depending on URI.149* Since we're only providing compatibility with the system properties150* from previous releases (see list above), that list will typically151* contain one single proxy, default being NO_PROXY.152* If we can get a system proxy it might contain more entries.153*/154public java.util.List<Proxy> select(URI uri) {155if (uri == null) {156throw new IllegalArgumentException("URI can't be null.");157}158String protocol = uri.getScheme();159String host = uri.getHost();160161if (host == null) {162// This is a hack to ensure backward compatibility in two163// cases: 1. hostnames contain non-ascii characters,164// internationalized domain names. in which case, URI will165// return null, see BugID 4957669; 2. Some hostnames can166// contain '_' chars even though it's not supposed to be167// legal, in which case URI will return null for getHost,168// but not for getAuthority() See BugID 4913253169String auth = uri.getAuthority();170if (auth != null) {171int i;172i = auth.indexOf('@');173if (i >= 0) {174auth = auth.substring(i+1);175}176i = auth.lastIndexOf(':');177if (i >= 0) {178auth = auth.substring(0,i);179}180host = auth;181}182}183184if (protocol == null || host == null) {185throw new IllegalArgumentException("protocol = "+protocol+" host = "+host);186}187188NonProxyInfo pinfo = null;189190if ("http".equalsIgnoreCase(protocol)) {191pinfo = NonProxyInfo.httpNonProxyInfo;192} else if ("https".equalsIgnoreCase(protocol)) {193// HTTPS uses the same property as HTTP, for backward194// compatibility195pinfo = NonProxyInfo.httpNonProxyInfo;196} else if ("ftp".equalsIgnoreCase(protocol)) {197pinfo = NonProxyInfo.ftpNonProxyInfo;198} else if ("socket".equalsIgnoreCase(protocol)) {199pinfo = NonProxyInfo.socksNonProxyInfo;200}201202/**203* Let's check the System properties for that protocol204*/205final String proto = protocol;206final NonProxyInfo nprop = pinfo;207final String urlhost = host.toLowerCase();208209/**210* This is one big doPrivileged call, but we're trying to optimize211* the code as much as possible. Since we're checking quite a few212* System properties it does help having only 1 call to doPrivileged.213* Be mindful what you do in here though!214*/215@SuppressWarnings("removal")216Proxy[] proxyArray = AccessController.doPrivileged(217new PrivilegedAction<Proxy[]>() {218public Proxy[] run() {219int i, j;220String phost = null;221int pport = 0;222String nphosts = null;223InetSocketAddress saddr = null;224225// Then let's walk the list of protocols in our array226for (i=0; i<props.length; i++) {227if (props[i][0].equalsIgnoreCase(proto)) {228for (j = 1; j < props[i].length; j++) {229/* System.getProp() will give us an empty230* String, "" for a defined but "empty"231* property.232*/233phost = NetProperties.get(props[i][j]+"Host");234if (phost != null && phost.length() != 0)235break;236}237if (phost == null || phost.isEmpty()) {238/**239* No system property defined for that240* protocol. Let's check System Proxy241* settings (Gnome, MacOsX & Windows) if242* we were instructed to.243*/244if (hasSystemProxies) {245String sproto;246if (proto.equalsIgnoreCase("socket"))247sproto = "socks";248else249sproto = proto;250return getSystemProxies(sproto, urlhost);251}252return null;253}254// If a Proxy Host is defined for that protocol255// Let's get the NonProxyHosts property256if (nprop != null) {257nphosts = NetProperties.get(nprop.property);258synchronized (nprop) {259if (nphosts == null) {260if (nprop.defaultVal != null) {261nphosts = nprop.defaultVal;262} else {263nprop.hostsSource = null;264nprop.pattern = null;265}266} else if (!nphosts.isEmpty()) {267// add the required default patterns268// but only if property no set. If it269// is empty, leave empty.270nphosts += "|" + NonProxyInfo271.defStringVal;272}273if (nphosts != null) {274if (!nphosts.equals(nprop.hostsSource)) {275nprop.pattern = toPattern(nphosts);276nprop.hostsSource = nphosts;277}278}279if (shouldNotUseProxyFor(nprop.pattern, urlhost)) {280return null;281}282}283}284// We got a host, let's check for port285286pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue();287if (pport == 0 && j < (props[i].length - 1)) {288// Can't find a port with same prefix as Host289// AND it's not a SOCKS proxy290// Let's try the other prefixes for that proto291for (int k = 1; k < (props[i].length - 1); k++) {292if ((k != j) && (pport == 0))293pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue();294}295}296297// Still couldn't find a port, let's use default298if (pport == 0) {299if (j == (props[i].length - 1)) // SOCKS300pport = defaultPort("socket");301else302pport = defaultPort(proto);303}304// We did find a proxy definition.305// Let's create the address, but don't resolve it306// as this will be done at connection time307saddr = InetSocketAddress.createUnresolved(phost, pport);308// Socks is *always* the last on the list.309if (j == (props[i].length - 1)) {310return new Proxy[] {SocksProxy.create(saddr, socksProxyVersion())};311}312return new Proxy[] {new Proxy(Proxy.Type.HTTP, saddr)};313}314}315return null;316}});317318319if (proxyArray != null) {320// Remove duplicate entries, while preserving order.321return Stream.of(proxyArray).distinct().collect(322collectingAndThen(toList(), Collections::unmodifiableList));323}324325// If no specific proxy was found, return a standard list containing326// only one NO_PROXY entry.327return NO_PROXY_LIST;328}329330public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {331if (uri == null || sa == null || ioe == null) {332throw new IllegalArgumentException("Arguments can't be null.");333}334// ignored335}336337338private int defaultPort(String protocol) {339if ("http".equalsIgnoreCase(protocol)) {340return 80;341} else if ("https".equalsIgnoreCase(protocol)) {342return 443;343} else if ("ftp".equalsIgnoreCase(protocol)) {344return 80;345} else if ("socket".equalsIgnoreCase(protocol)) {346return 1080;347} else {348return -1;349}350}351352private static native boolean init();353private synchronized native Proxy[] getSystemProxies(String protocol, String host);354355/**356* @return {@code true} if given this pattern for non-proxy hosts and this357* urlhost the proxy should NOT be used to access this urlhost358*/359static boolean shouldNotUseProxyFor(Pattern pattern, String urlhost) {360if (pattern == null || urlhost.isEmpty())361return false;362boolean matches = pattern.matcher(urlhost).matches();363return matches;364}365366/**367* @param mask non-null mask368* @return {@link java.util.regex.Pattern} corresponding to this mask369* or {@code null} in case mask should not match anything370*/371static Pattern toPattern(String mask) {372boolean disjunctionEmpty = true;373StringJoiner joiner = new StringJoiner("|");374for (String disjunct : mask.split("\\|")) {375if (disjunct.isEmpty())376continue;377disjunctionEmpty = false;378String regex = disjunctToRegex(disjunct.toLowerCase());379joiner.add(regex);380}381return disjunctionEmpty ? null : Pattern.compile(joiner.toString());382}383384/**385* @param disjunct non-null mask disjunct386* @return java regex string corresponding to this mask387*/388static String disjunctToRegex(String disjunct) {389String regex;390if (disjunct.equals("*")) {391regex = ".*";392} else if (disjunct.startsWith("*") && disjunct.endsWith("*")) {393regex = ".*" + quote(disjunct.substring(1, disjunct.length() - 1)) + ".*";394} else if (disjunct.startsWith("*")) {395regex = ".*" + quote(disjunct.substring(1));396} else if (disjunct.endsWith("*")) {397regex = quote(disjunct.substring(0, disjunct.length() - 1)) + ".*";398} else {399regex = quote(disjunct);400}401return regex;402}403}404405406