1 /*
   2  * Copyright (c) 2003, 2018 Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.net.spi;
  27 
  28 import java.net.InetSocketAddress;
  29 import java.net.Proxy;
  30 import java.net.ProxySelector;
  31 import java.net.SocketAddress;
  32 import java.net.URI;
  33 import java.util.Collections;
  34 import java.util.List;
  35 import java.io.IOException;
  36 import java.security.AccessController;
  37 import java.security.PrivilegedAction;
  38 import java.util.StringJoiner;
  39 import java.util.regex.Pattern;
  40 import java.util.stream.Stream;
  41 import sun.net.NetProperties;
  42 import sun.net.SocksProxy;
  43 import static java.util.regex.Pattern.quote;
  44 import static java.util.stream.Collectors.collectingAndThen;
  45 import static java.util.stream.Collectors.toList;
  46 
  47 /**
  48  * Supports proxy settings using system properties This proxy selector
  49  * provides backward compatibility with the old http protocol handler
  50  * as far as how proxy is set
  51  *
  52  * Most of the implementation copied from the old http protocol handler
  53  *
  54  * Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort,
  55  * proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks.
  56  * NOTE: need to do gopher as well
  57  */
  58 public class DefaultProxySelector extends ProxySelector {
  59 
  60     /**
  61      * This is where we define all the valid System Properties we have to
  62      * support for each given protocol.
  63      * The format of this 2 dimensional array is :
  64      * - 1 row per protocol (http, ftp, ...)
  65      * - 1st element of each row is the protocol name
  66      * - subsequent elements are prefixes for Host & Port properties
  67      *   listed in order of priority.
  68      * Example:
  69      * {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
  70      * means for FTP we try in that oder:
  71      *          + ftp.proxyHost & ftp.proxyPort
  72      *          + ftpProxyHost & ftpProxyPort
  73      *          + proxyHost & proxyPort
  74      *          + socksProxyHost & socksProxyPort
  75      *
  76      * Note that the socksProxy should *always* be the last on the list
  77      */
  78     static final String[][] props = {
  79         /*
  80          * protocol, Property prefix 1, Property prefix 2, ...
  81          */
  82         {"http", "http.proxy", "proxy", "socksProxy"},
  83         {"https", "https.proxy", "proxy", "socksProxy"},
  84         {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
  85         {"gopher", "gopherProxy", "socksProxy"},
  86         {"socket", "socksProxy"}
  87     };
  88 
  89     private static final String SOCKS_PROXY_VERSION = "socksProxyVersion";
  90 
  91     private static boolean hasSystemProxies = false;
  92 
  93     private static final List<Proxy> NO_PROXY_LIST = List.of(Proxy.NO_PROXY);
  94 
  95     static {
  96         final String key = "java.net.useSystemProxies";
  97         Boolean b = AccessController.doPrivileged(
  98             new PrivilegedAction<Boolean>() {
  99                 public Boolean run() {
 100                     return NetProperties.getBoolean(key);
 101                 }});
 102         if (b != null && b.booleanValue()) {
 103             java.security.AccessController.doPrivileged(
 104                 new java.security.PrivilegedAction<Void>() {
 105                     public Void run() {
 106                         System.loadLibrary("net");
 107                         return null;
 108                     }
 109                 });
 110             hasSystemProxies = init();
 111         }
 112     }
 113 
 114     public static int socksProxyVersion() {
 115         return AccessController.doPrivileged(
 116                 new PrivilegedAction<Integer>() {
 117                     @Override public Integer run() {
 118                         return NetProperties.getInteger(SOCKS_PROXY_VERSION, 5);
 119                     }
 120                 });
 121     }
 122 
 123     /**
 124      * How to deal with "non proxy hosts":
 125      * since we do have to generate a pattern we don't want to do that if
 126      * it's not necessary. Therefore we do cache the result, on a per-protocol
 127      * basis, and change it only when the "source", i.e. the system property,
 128      * did change.
 129      */
 130 
 131     static class NonProxyInfo {
 132         // Default value for nonProxyHosts, this provides backward compatibility
 133         // by excluding localhost and its litteral notations.
 134         static final String defStringVal = "localhost|127.*|[::1]|0.0.0.0|[::0]";
 135 
 136         String hostsSource;
 137         Pattern pattern;
 138         final String property;
 139         final String defaultVal;
 140         static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal);
 141         static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal);
 142         static NonProxyInfo socksNonProxyInfo = new NonProxyInfo("socksNonProxyHosts", null, null, defStringVal);
 143 
 144         NonProxyInfo(String p, String s, Pattern pattern, String d) {
 145             property = p;
 146             hostsSource = s;
 147             this.pattern = pattern;
 148             defaultVal = d;
 149         }
 150     }
 151 
 152 
 153     /**
 154      * select() method. Where all the hard work is done.
 155      * Build a list of proxies depending on URI.
 156      * Since we're only providing compatibility with the system properties
 157      * from previous releases (see list above), that list will typically
 158      * contain one single proxy, default being NO_PROXY.
 159      * If we can get a system proxy it might contain more entries.
 160      */
 161     public java.util.List<Proxy> select(URI uri) {
 162         if (uri == null) {
 163             throw new IllegalArgumentException("URI can't be null.");
 164         }
 165         String protocol = uri.getScheme();
 166         String host = uri.getHost();
 167 
 168         if (host == null) {
 169             // This is a hack to ensure backward compatibility in two
 170             // cases: 1. hostnames contain non-ascii characters,
 171             // internationalized domain names. in which case, URI will
 172             // return null, see BugID 4957669; 2. Some hostnames can
 173             // contain '_' chars even though it's not supposed to be
 174             // legal, in which case URI will return null for getHost,
 175             // but not for getAuthority() See BugID 4913253
 176             String auth = uri.getAuthority();
 177             if (auth != null) {
 178                 int i;
 179                 i = auth.indexOf('@');
 180                 if (i >= 0) {
 181                     auth = auth.substring(i+1);
 182                 }
 183                 i = auth.lastIndexOf(':');
 184                 if (i >= 0) {
 185                     auth = auth.substring(0,i);
 186                 }
 187                 host = auth;
 188             }
 189         }
 190 
 191         if (protocol == null || host == null) {
 192             throw new IllegalArgumentException("protocol = "+protocol+" host = "+host);
 193         }
 194 
 195         NonProxyInfo pinfo = null;
 196 
 197         if ("http".equalsIgnoreCase(protocol)) {
 198             pinfo = NonProxyInfo.httpNonProxyInfo;
 199         } else if ("https".equalsIgnoreCase(protocol)) {
 200             // HTTPS uses the same property as HTTP, for backward
 201             // compatibility
 202             pinfo = NonProxyInfo.httpNonProxyInfo;
 203         } else if ("ftp".equalsIgnoreCase(protocol)) {
 204             pinfo = NonProxyInfo.ftpNonProxyInfo;
 205         } else if ("socket".equalsIgnoreCase(protocol)) {
 206             pinfo = NonProxyInfo.socksNonProxyInfo;
 207         }
 208 
 209         /**
 210          * Let's check the System properties for that protocol
 211          */
 212         final String proto = protocol;
 213         final NonProxyInfo nprop = pinfo;
 214         final String urlhost = host.toLowerCase();
 215 
 216         /**
 217          * This is one big doPrivileged call, but we're trying to optimize
 218          * the code as much as possible. Since we're checking quite a few
 219          * System properties it does help having only 1 call to doPrivileged.
 220          * Be mindful what you do in here though!
 221          */
 222         Proxy[] proxyArray = AccessController.doPrivileged(
 223             new PrivilegedAction<Proxy[]>() {
 224                 public Proxy[] run() {
 225                     int i, j;
 226                     String phost =  null;
 227                     int pport = 0;
 228                     String nphosts =  null;
 229                     InetSocketAddress saddr = null;
 230 
 231                     // Then let's walk the list of protocols in our array
 232                     for (i=0; i<props.length; i++) {
 233                         if (props[i][0].equalsIgnoreCase(proto)) {
 234                             for (j = 1; j < props[i].length; j++) {
 235                                 /* System.getProp() will give us an empty
 236                                  * String, "" for a defined but "empty"
 237                                  * property.
 238                                  */
 239                                 phost =  NetProperties.get(props[i][j]+"Host");
 240                                 if (phost != null && phost.length() != 0)
 241                                     break;
 242                             }
 243                             if (phost == null || phost.isEmpty()) {
 244                                 /**
 245                                  * No system property defined for that
 246                                  * protocol. Let's check System Proxy
 247                                  * settings (Gnome, MacOsX & Windows) if
 248                                  * we were instructed to.
 249                                  */
 250                                 if (hasSystemProxies) {
 251                                     String sproto;
 252                                     if (proto.equalsIgnoreCase("socket"))
 253                                         sproto = "socks";
 254                                     else
 255                                         sproto = proto;
 256                                     return getSystemProxies(sproto, urlhost);
 257                                 }
 258                                 return null;
 259                             }
 260                             // If a Proxy Host is defined for that protocol
 261                             // Let's get the NonProxyHosts property
 262                             if (nprop != null) {
 263                                 nphosts = NetProperties.get(nprop.property);
 264                                 synchronized (nprop) {
 265                                     if (nphosts == null) {
 266                                         if (nprop.defaultVal != null) {
 267                                             nphosts = nprop.defaultVal;
 268                                         } else {
 269                                             nprop.hostsSource = null;
 270                                             nprop.pattern = null;
 271                                         }
 272                                     } else if (!nphosts.isEmpty()) {
 273                                         // add the required default patterns
 274                                         // but only if property no set. If it
 275                                         // is empty, leave empty.
 276                                         nphosts += "|" + NonProxyInfo
 277                                                          .defStringVal;
 278                                     }
 279                                     if (nphosts != null) {
 280                                         if (!nphosts.equals(nprop.hostsSource)) {
 281                                             nprop.pattern = toPattern(nphosts);
 282                                             nprop.hostsSource = nphosts;
 283                                         }
 284                                     }
 285                                     if (shouldNotUseProxyFor(nprop.pattern, urlhost)) {
 286                                         return null;
 287                                     }
 288                                 }
 289                             }
 290                             // We got a host, let's check for port
 291 
 292                             pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue();
 293                             if (pport == 0 && j < (props[i].length - 1)) {
 294                                 // Can't find a port with same prefix as Host
 295                                 // AND it's not a SOCKS proxy
 296                                 // Let's try the other prefixes for that proto
 297                                 for (int k = 1; k < (props[i].length - 1); k++) {
 298                                     if ((k != j) && (pport == 0))
 299                                         pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue();
 300                                 }
 301                             }
 302 
 303                             // Still couldn't find a port, let's use default
 304                             if (pport == 0) {
 305                                 if (j == (props[i].length - 1)) // SOCKS
 306                                     pport = defaultPort("socket");
 307                                 else
 308                                     pport = defaultPort(proto);
 309                             }
 310                             // We did find a proxy definition.
 311                             // Let's create the address, but don't resolve it
 312                             // as this will be done at connection time
 313                             saddr = InetSocketAddress.createUnresolved(phost, pport);
 314                             // Socks is *always* the last on the list.
 315                             if (j == (props[i].length - 1)) {
 316                                 return new Proxy[] {SocksProxy.create(saddr, socksProxyVersion())};
 317                             }
 318                             return new Proxy[] {new Proxy(Proxy.Type.HTTP, saddr)};
 319                         }
 320                     }
 321                     return null;
 322                 }});
 323 
 324 
 325         if (proxyArray != null) {
 326             // Remove duplicate entries, while preserving order.
 327             return Stream.of(proxyArray).distinct().collect(
 328                     collectingAndThen(toList(), Collections::unmodifiableList));
 329         }
 330 
 331         // If no specific proxy was found, return a standard list containing
 332         // only one NO_PROXY entry.
 333         return NO_PROXY_LIST;
 334     }
 335 
 336     public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
 337         if (uri == null || sa == null || ioe == null) {
 338             throw new IllegalArgumentException("Arguments can't be null.");
 339         }
 340         // ignored
 341     }
 342 
 343 
 344     private int defaultPort(String protocol) {
 345         if ("http".equalsIgnoreCase(protocol)) {
 346             return 80;
 347         } else if ("https".equalsIgnoreCase(protocol)) {
 348             return 443;
 349         } else if ("ftp".equalsIgnoreCase(protocol)) {
 350             return 80;
 351         } else if ("socket".equalsIgnoreCase(protocol)) {
 352             return 1080;
 353         } else if ("gopher".equalsIgnoreCase(protocol)) {
 354             return 80;
 355         } else {
 356             return -1;
 357         }
 358     }
 359 
 360     private static native boolean init();
 361     private synchronized native Proxy[] getSystemProxies(String protocol, String host);
 362 
 363     /**
 364      * @return {@code true} if given this pattern for non-proxy hosts and this
 365      *         urlhost the proxy should NOT be used to access this urlhost
 366      */
 367     static boolean shouldNotUseProxyFor(Pattern pattern, String urlhost) {
 368         if (pattern == null || urlhost.isEmpty())
 369             return false;
 370         boolean matches = pattern.matcher(urlhost).matches();
 371         return matches;
 372     }
 373 
 374     /**
 375      * @param mask non-null mask
 376      * @return {@link java.util.regex.Pattern} corresponding to this mask
 377      *         or {@code null} in case mask should not match anything
 378      */
 379     static Pattern toPattern(String mask) {
 380         boolean disjunctionEmpty = true;
 381         StringJoiner joiner = new StringJoiner("|");
 382         for (String disjunct : mask.split("\\|")) {
 383             if (disjunct.isEmpty())
 384                 continue;
 385             disjunctionEmpty = false;
 386             String regex = disjunctToRegex(disjunct.toLowerCase());
 387             joiner.add(regex);
 388         }
 389         return disjunctionEmpty ? null : Pattern.compile(joiner.toString());
 390     }
 391 
 392     /**
 393      * @param disjunct non-null mask disjunct
 394      * @return java regex string corresponding to this mask
 395      */
 396     static String disjunctToRegex(String disjunct) {
 397         String regex;
 398         if (disjunct.startsWith("*") && disjunct.endsWith("*")) {
 399             regex = ".*" + quote(disjunct.substring(1, disjunct.length() - 1)) + ".*";
 400         } else if (disjunct.startsWith("*")) {
 401             regex = ".*" + quote(disjunct.substring(1));
 402         } else if (disjunct.endsWith("*")) {
 403             regex = quote(disjunct.substring(0, disjunct.length() - 1)) + ".*";
 404         } else {
 405             regex = quote(disjunct);
 406         }
 407         return regex;
 408     }
 409 }