1 /*
   2  * Copyright (c) 2003, 2011, 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.ArrayList;
  34 import java.util.List;
  35 import java.util.StringTokenizer;
  36 import java.io.IOException;
  37 import sun.misc.RegexpPool;
  38 import java.security.AccessController;
  39 import java.security.PrivilegedAction;
  40 import sun.net.NetProperties;
  41 import sun.net.SocksProxy;
  42 
  43 /**
  44  * Supports proxy settings using system properties This proxy selector
  45  * provides backward compatibility with the old http protocol handler
  46  * as far as how proxy is set
  47  *
  48  * Most of the implementation copied from the old http protocol handler
  49  *
  50  * Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort,
  51  * proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks.
  52  * NOTE: need to do gopher as well
  53  */
  54 public class DefaultProxySelector extends ProxySelector {
  55 
  56     /**
  57      * This is where we define all the valid System Properties we have to
  58      * support for each given protocol.
  59      * The format of this 2 dimensional array is :
  60      * - 1 row per protocol (http, ftp, ...)
  61      * - 1st element of each row is the protocol name
  62      * - subsequent elements are prefixes for Host & Port properties
  63      *   listed in order of priority.
  64      * Example:
  65      * {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
  66      * means for FTP we try in that oder:
  67      *          + ftp.proxyHost & ftp.proxyPort
  68      *          + ftpProxyHost & ftpProxyPort
  69      *          + proxyHost & proxyPort
  70      *          + socksProxyHost & socksProxyPort
  71      *
  72      * Note that the socksProxy should *always* be the last on the list
  73      */
  74     final static String[][] props = {
  75         /*
  76          * protocol, Property prefix 1, Property prefix 2, ...
  77          */
  78         {"http", "http.proxy", "proxy", "socksProxy"},
  79         {"https", "https.proxy", "proxy", "socksProxy"},
  80         {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
  81         {"gopher", "gopherProxy", "socksProxy"},
  82         {"socket", "socksProxy"}
  83     };
  84 
  85     private static final String SOCKS_PROXY_VERSION = "socksProxyVersion";
  86 
  87     private static boolean hasSystemProxies = false;
  88 
  89     static {
  90         final String key = "java.net.useSystemProxies";
  91         Boolean b = AccessController.doPrivileged(
  92             new PrivilegedAction<Boolean>() {
  93                 public Boolean run() {
  94                     return NetProperties.getBoolean(key);
  95                 }});
  96         if (b != null && b.booleanValue()) {
  97             java.security.AccessController.doPrivileged(
  98                 new java.security.PrivilegedAction<Void>() {
  99                     public Void run() {
 100                         System.loadLibrary("net");
 101                         return null;
 102                     }
 103                 });
 104             hasSystemProxies = init();
 105         }
 106     }
 107 
 108     /**
 109      * How to deal with "non proxy hosts":
 110      * since we do have to generate a RegexpPool we don't want to do that if
 111      * it's not necessary. Therefore we do cache the result, on a per-protocol
 112      * basis, and change it only when the "source", i.e. the system property,
 113      * did change.
 114      */
 115 
 116     static class NonProxyInfo {
 117         // Default value for nonProxyHosts, this provides backward compatibility
 118         // by excluding localhost and its litteral notations.
 119         static final String defStringVal = "localhost|127.*|[::1]|0.0.0.0|[::0]";
 120 
 121         String hostsSource;
 122         RegexpPool hostsPool;
 123         final String property;
 124         final String defaultVal;
 125         static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal);
 126         static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal);
 127 
 128         NonProxyInfo(String p, String s, RegexpPool pool, String d) {
 129             property = p;
 130             hostsSource = s;
 131             hostsPool = pool;
 132             defaultVal = d;
 133         }
 134     }
 135 
 136 
 137     /**
 138      * select() method. Where all the hard work is done.
 139      * Build a list of proxies depending on URI.
 140      * Since we're only providing compatibility with the system properties
 141      * from previous releases (see list above), that list will always
 142      * contain 1 single proxy, default being NO_PROXY.
 143      */
 144     public java.util.List<Proxy> select(URI uri) {
 145         if (uri == null) {
 146             throw new IllegalArgumentException("URI can't be null.");
 147         }
 148         String protocol = uri.getScheme();
 149         String host = uri.getHost();
 150 
 151         if (host == null) {
 152             // This is a hack to ensure backward compatibility in two
 153             // cases: 1. hostnames contain non-ascii characters,
 154             // internationalized domain names. in which case, URI will
 155             // return null, see BugID 4957669; 2. Some hostnames can
 156             // contain '_' chars even though it's not supposed to be
 157             // legal, in which case URI will return null for getHost,
 158             // but not for getAuthority() See BugID 4913253
 159             String auth = uri.getAuthority();
 160             if (auth != null) {
 161                 int i;
 162                 i = auth.indexOf('@');
 163                 if (i >= 0) {
 164                     auth = auth.substring(i+1);
 165                 }
 166                 i = auth.lastIndexOf(':');
 167                 if (i >= 0) {
 168                     auth = auth.substring(0,i);
 169                 }
 170                 host = auth;
 171             }
 172         }
 173 
 174         if (protocol == null || host == null) {
 175             throw new IllegalArgumentException("protocol = "+protocol+" host = "+host);
 176         }
 177         List<Proxy> proxyl = new ArrayList<Proxy>(1);
 178 
 179         NonProxyInfo pinfo = null;
 180 
 181         if ("http".equalsIgnoreCase(protocol)) {
 182             pinfo = NonProxyInfo.httpNonProxyInfo;
 183         } else if ("https".equalsIgnoreCase(protocol)) {
 184             // HTTPS uses the same property as HTTP, for backward
 185             // compatibility
 186             pinfo = NonProxyInfo.httpNonProxyInfo;
 187         } else if ("ftp".equalsIgnoreCase(protocol)) {
 188             pinfo = NonProxyInfo.ftpNonProxyInfo;
 189         }
 190 
 191         /**
 192          * Let's check the System properties for that protocol
 193          */
 194         final String proto = protocol;
 195         final NonProxyInfo nprop = pinfo;
 196         final String urlhost = host.toLowerCase();
 197 
 198         /**
 199          * This is one big doPrivileged call, but we're trying to optimize
 200          * the code as much as possible. Since we're checking quite a few
 201          * System properties it does help having only 1 call to doPrivileged.
 202          * Be mindful what you do in here though!
 203          */
 204         Proxy p = AccessController.doPrivileged(
 205             new PrivilegedAction<Proxy>() {
 206                 public Proxy run() {
 207                     int i, j;
 208                     String phost =  null;
 209                     int pport = 0;
 210                     String nphosts =  null;
 211                     InetSocketAddress saddr = null;
 212 
 213                     // Then let's walk the list of protocols in our array
 214                     for (i=0; i<props.length; i++) {
 215                         if (props[i][0].equalsIgnoreCase(proto)) {
 216                             for (j = 1; j < props[i].length; j++) {
 217                                 /* System.getProp() will give us an empty
 218                                  * String, "" for a defined but "empty"
 219                                  * property.
 220                                  */
 221                                 phost =  NetProperties.get(props[i][j]+"Host");
 222                                 if (phost != null && phost.length() != 0)
 223                                     break;
 224                             }
 225                             if (phost == null || phost.length() == 0) {
 226                                 /**
 227                                  * No system property defined for that
 228                                  * protocol. Let's check System Proxy
 229                                  * settings (Gnome & Windows) if we were
 230                                  * instructed to.
 231                                  */
 232                                 if (hasSystemProxies) {
 233                                     String sproto;
 234                                     if (proto.equalsIgnoreCase("socket"))
 235                                         sproto = "socks";
 236                                     else
 237                                         sproto = proto;
 238                                     Proxy sproxy = getSystemProxy(sproto, urlhost);
 239                                     if (sproxy != null) {
 240                                         return sproxy;
 241                                     }
 242                                 }
 243                                 return Proxy.NO_PROXY;
 244                             }
 245                             // If a Proxy Host is defined for that protocol
 246                             // Let's get the NonProxyHosts property
 247                             if (nprop != null) {
 248                                 nphosts = NetProperties.get(nprop.property);
 249                                 synchronized (nprop) {
 250                                     if (nphosts == null) {
 251                                         if (nprop.defaultVal != null) {
 252                                             nphosts = nprop.defaultVal;
 253                                         } else {
 254                                             nprop.hostsSource = null;
 255                                             nprop.hostsPool = null;
 256                                         }
 257                                     } else if (nphosts.length() != 0) {
 258                                         // add the required default patterns
 259                                         // but only if property no set. If it
 260                                         // is empty, leave empty.
 261                                         nphosts += "|" + NonProxyInfo
 262                                                          .defStringVal;
 263                                     }
 264                                     if (nphosts != null) {
 265                                         if (!nphosts.equals(nprop.hostsSource)) {
 266                                             RegexpPool pool = new RegexpPool();
 267                                             StringTokenizer st = new StringTokenizer(nphosts, "|", false);
 268                                             try {
 269                                                 while (st.hasMoreTokens()) {
 270                                                     pool.add(st.nextToken().toLowerCase(), Boolean.TRUE);
 271                                                 }
 272                                             } catch (sun.misc.REException ex) {
 273                                             }
 274                                             nprop.hostsPool = pool;
 275                                             nprop.hostsSource = nphosts;
 276                                         }
 277                                     }
 278                                     if (nprop.hostsPool != null &&
 279                                         nprop.hostsPool.match(urlhost) != null) {
 280                                         return Proxy.NO_PROXY;
 281                                     }
 282                                 }
 283                             }
 284                             // We got a host, let's check for port
 285 
 286                             pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue();
 287                             if (pport == 0 && j < (props[i].length - 1)) {
 288                                 // Can't find a port with same prefix as Host
 289                                 // AND it's not a SOCKS proxy
 290                                 // Let's try the other prefixes for that proto
 291                                 for (int k = 1; k < (props[i].length - 1); k++) {
 292                                     if ((k != j) && (pport == 0))
 293                                         pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue();
 294                                 }
 295                             }
 296 
 297                             // Still couldn't find a port, let's use default
 298                             if (pport == 0) {
 299                                 if (j == (props[i].length - 1)) // SOCKS
 300                                     pport = defaultPort("socket");
 301                                 else
 302                                     pport = defaultPort(proto);
 303                             }
 304                             // We did find a proxy definition.
 305                             // Let's create the address, but don't resolve it
 306                             // as this will be done at connection time
 307                             saddr = InetSocketAddress.createUnresolved(phost, pport);
 308                             // Socks is *always* the last on the list.
 309                             if (j == (props[i].length - 1)) {
 310                                 int version = NetProperties.getInteger(SOCKS_PROXY_VERSION, 5).intValue();
 311                                 return SocksProxy.create(saddr, version);
 312                             } else {
 313                                 return new Proxy(Proxy.Type.HTTP, saddr);
 314                             }
 315                         }
 316                     }
 317                     return Proxy.NO_PROXY;
 318                 }});
 319 
 320         proxyl.add(p);
 321 
 322         /*
 323          * If no specific property was set for that URI, we should be
 324          * returning an iterator to an empty List.
 325          */
 326         return proxyl;
 327     }
 328 
 329     public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
 330         if (uri == null || sa == null || ioe == null) {
 331             throw new IllegalArgumentException("Arguments can't be null.");
 332         }
 333         // ignored
 334     }
 335 
 336 
 337     private int defaultPort(String protocol) {
 338         if ("http".equalsIgnoreCase(protocol)) {
 339             return 80;
 340         } else if ("https".equalsIgnoreCase(protocol)) {
 341             return 443;
 342         } else if ("ftp".equalsIgnoreCase(protocol)) {
 343             return 80;
 344         } else if ("socket".equalsIgnoreCase(protocol)) {
 345             return 1080;
 346         } else if ("gopher".equalsIgnoreCase(protocol)) {
 347             return 80;
 348         } else {
 349             return -1;
 350         }
 351     }
 352 
 353     private native static boolean init();
 354     private native Proxy getSystemProxy(String protocol, String host);
 355 }