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