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