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 
NonProxyInfo(String p, String s, RegexpPool pool, String d)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      */
select(URI uri)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 
connectFailed(URI uri, SocketAddress sa, IOException ioe)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 
defaultPort(String protocol)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