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     // Android-note: Integrated some upstream changes from beyond OpenJDK8u121-b13.
121     // This includes NonProxyInfo.pattern -> hostsPool and associated changes.
122     // See http://b/62368386
123     static class NonProxyInfo {
124         // Default value for nonProxyHosts, this provides backward compatibility
125         // by excluding localhost and its litteral notations.
126         static final String defStringVal = "localhost|127.*|[::1]|0.0.0.0|[::0]";
127 
128         String hostsSource;
129         Pattern pattern;
130         final String property;
131         final String defaultVal;
132         static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal);
133         static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal);
134         static NonProxyInfo socksNonProxyInfo = new NonProxyInfo("socksNonProxyHosts", null, null, defStringVal);
135         // Android-changed: Different NonProxyInfo flags for https hosts vs. http.
136         static NonProxyInfo httpsNonProxyInfo = new NonProxyInfo("https.nonProxyHosts", null, null, defStringVal);
137 
NonProxyInfo(String p, String s, Pattern pattern, String d)138         NonProxyInfo(String p, String s, Pattern pattern, String d) {
139             property = p;
140             hostsSource = s;
141             this.pattern = pattern;
142             defaultVal = d;
143         }
144     }
145 
146 
147     /**
148      * select() method. Where all the hard work is done.
149      * Build a list of proxies depending on URI.
150      * Since we're only providing compatibility with the system properties
151      * from previous releases (see list above), that list will always
152      * contain 1 single proxy, default being NO_PROXY.
153      */
select(URI uri)154     public java.util.List<Proxy> select(URI uri) {
155         if (uri == null) {
156             throw new IllegalArgumentException("URI can't be null.");
157         }
158         String protocol = uri.getScheme();
159         String host = uri.getHost();
160 
161         if (host == null) {
162             // This is a hack to ensure backward compatibility in two
163             // cases: 1. hostnames contain non-ascii characters,
164             // internationalized domain names. in which case, URI will
165             // return null, see BugID 4957669; 2. Some hostnames can
166             // contain '_' chars even though it's not supposed to be
167             // legal, in which case URI will return null for getHost,
168             // but not for getAuthority() See BugID 4913253
169             String auth = uri.getAuthority();
170             if (auth != null) {
171                 int i;
172                 i = auth.indexOf('@');
173                 if (i >= 0) {
174                     auth = auth.substring(i+1);
175                 }
176                 i = auth.lastIndexOf(':');
177                 if (i >= 0) {
178                     auth = auth.substring(0,i);
179                 }
180                 host = auth;
181             }
182         }
183 
184         if (protocol == null || host == null) {
185             throw new IllegalArgumentException("protocol = "+protocol+" host = "+host);
186         }
187         List<Proxy> proxyl = new ArrayList<Proxy>(1);
188 
189         NonProxyInfo pinfo = null;
190 
191         if ("http".equalsIgnoreCase(protocol)) {
192             pinfo = NonProxyInfo.httpNonProxyInfo;
193         } else if ("https".equalsIgnoreCase(protocol)) {
194             // HTTPS uses the same property as HTTP, for backward
195             // compatibility
196             // Android-changed: Different NonProxyInfo flags for https hosts vs. http.
197             // pinfo = NonProxyInfo.httpNonProxyInfo;
198             pinfo = NonProxyInfo.httpsNonProxyInfo;
199         } else if ("ftp".equalsIgnoreCase(protocol)) {
200             pinfo = NonProxyInfo.ftpNonProxyInfo;
201         } else if ("socket".equalsIgnoreCase(protocol)) {
202             pinfo = NonProxyInfo.socksNonProxyInfo;
203         }
204 
205         /**
206          * Let's check the System properties for that protocol
207          */
208         final String proto = protocol;
209         final NonProxyInfo nprop = pinfo;
210         final String urlhost = host.toLowerCase();
211 
212         /**
213          * This is one big doPrivileged call, but we're trying to optimize
214          * the code as much as possible. Since we're checking quite a few
215          * System properties it does help having only 1 call to doPrivileged.
216          * Be mindful what you do in here though!
217          */
218         Proxy p = AccessController.doPrivileged(
219             new PrivilegedAction<Proxy>() {
220                 public Proxy run() {
221                     int i, j;
222                     String phost =  null;
223                     int pport = 0;
224                     String nphosts =  null;
225                     InetSocketAddress saddr = null;
226 
227                     // Then let's walk the list of protocols in our array
228                     for (i=0; i<props.length; i++) {
229                         if (props[i][0].equalsIgnoreCase(proto)) {
230                             for (j = 1; j < props[i].length; j++) {
231                                 /* System.getProp() will give us an empty
232                                  * String, "" for a defined but "empty"
233                                  * property.
234                                  */
235                                 phost =  NetProperties.get(props[i][j]+"Host");
236                                 if (phost != null && phost.length() != 0)
237                                     break;
238                             }
239                             if (phost == null || phost.length() == 0) {
240                                 /**
241                                  * No system property defined for that
242                                  * protocol. Let's check System Proxy
243                                  * settings (Gnome & Windows) if we were
244                                  * instructed to.
245                                  */
246                                 // Android-removed: Dead code, hasSystemProxies is always false.
247                                 /*
248                                 if (hasSystemProxies) {
249                                     String sproto;
250                                     if (proto.equalsIgnoreCase("socket"))
251                                         sproto = "socks";
252                                     else
253                                         sproto = proto;
254                                     Proxy sproxy = getSystemProxy(sproto, urlhost);
255                                     if (sproxy != null) {
256                                         return sproxy;
257                                     }
258                                 }
259                                 */
260                                 return Proxy.NO_PROXY;
261                             }
262                             // If a Proxy Host is defined for that protocol
263                             // Let's get the NonProxyHosts property
264                             if (nprop != null) {
265                                 nphosts = NetProperties.get(nprop.property);
266                                 synchronized (nprop) {
267                                     if (nphosts == null) {
268                                         if (nprop.defaultVal != null) {
269                                             nphosts = nprop.defaultVal;
270                                         } else {
271                                             nprop.hostsSource = null;
272                                             nprop.pattern = null;
273                                         }
274                                     } else if (nphosts.length() != 0) {
275                                         // add the required default patterns
276                                         // but only if property no set. If it
277                                         // is empty, leave empty.
278                                         nphosts += "|" + NonProxyInfo
279                                                          .defStringVal;
280                                     }
281                                     if (nphosts != null) {
282                                         if (!nphosts.equals(nprop.hostsSource)) {
283                                             nprop.pattern = toPattern(nphosts);
284                                             nprop.hostsSource = nphosts;
285                                         }
286                                     }
287                                     if (shouldNotUseProxyFor(nprop.pattern, urlhost)) {
288                                         return Proxy.NO_PROXY;
289                                     }
290                                 }
291                             }
292                             // We got a host, let's check for port
293 
294                             pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue();
295                             if (pport == 0 && j < (props[i].length - 1)) {
296                                 // Can't find a port with same prefix as Host
297                                 // AND it's not a SOCKS proxy
298                                 // Let's try the other prefixes for that proto
299                                 for (int k = 1; k < (props[i].length - 1); k++) {
300                                     if ((k != j) && (pport == 0))
301                                         pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue();
302                                 }
303                             }
304 
305                             // Still couldn't find a port, let's use default
306                             if (pport == 0) {
307                                 if (j == (props[i].length - 1)) // SOCKS
308                                     pport = defaultPort("socket");
309                                 else
310                                     pport = defaultPort(proto);
311                             }
312                             // We did find a proxy definition.
313                             // Let's create the address, but don't resolve it
314                             // as this will be done at connection time
315                             saddr = InetSocketAddress.createUnresolved(phost, pport);
316                             // Socks is *always* the last on the list.
317                             if (j == (props[i].length - 1)) {
318                                 int version = NetProperties.getInteger(SOCKS_PROXY_VERSION, 5).intValue();
319                                 return SocksProxy.create(saddr, version);
320                             } else {
321                                 return new Proxy(Proxy.Type.HTTP, saddr);
322                             }
323                         }
324                     }
325                     return Proxy.NO_PROXY;
326                 }});
327 
328         proxyl.add(p);
329 
330         /*
331          * If no specific property was set for that URI, we should be
332          * returning an iterator to an empty List.
333          */
334         return proxyl;
335     }
336 
connectFailed(URI uri, SocketAddress sa, IOException ioe)337     public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
338         if (uri == null || sa == null || ioe == null) {
339             throw new IllegalArgumentException("Arguments can't be null.");
340         }
341         // ignored
342     }
343 
344 
defaultPort(String protocol)345     private int defaultPort(String protocol) {
346         if ("http".equalsIgnoreCase(protocol)) {
347             return 80;
348         } else if ("https".equalsIgnoreCase(protocol)) {
349             return 443;
350         } else if ("ftp".equalsIgnoreCase(protocol)) {
351             return 80;
352         } else if ("socket".equalsIgnoreCase(protocol)) {
353             return 1080;
354         } else if ("gopher".equalsIgnoreCase(protocol)) {
355             return 80;
356         } else {
357             return -1;
358         }
359     }
360 
361     // Android-removed: Native logic not available/used on Android.
362     /*
363     private native static boolean init();
364     private synchronized native Proxy getSystemProxy(String protocol, String host);
365     */
366 
367     /**
368      * @return {@code true} if given this pattern for non-proxy hosts and this
369      *         urlhost the proxy should NOT be used to access this urlhost
370      */
shouldNotUseProxyFor(Pattern pattern, String urlhost)371     static boolean shouldNotUseProxyFor(Pattern pattern, String urlhost) {
372         if (pattern == null || urlhost.isEmpty())
373             return false;
374         boolean matches = pattern.matcher(urlhost).matches();
375         return matches;
376     }
377 
378     /**
379      * @param mask non-null mask
380      * @return {@link java.util.regex.Pattern} corresponding to this mask
381      *         or {@code null} in case mask should not match anything
382      */
toPattern(String mask)383     static Pattern toPattern(String mask) {
384         boolean disjunctionEmpty = true;
385         StringJoiner joiner = new StringJoiner("|");
386         for (String disjunct : mask.split("\\|")) {
387             if (disjunct.isEmpty())
388                 continue;
389             disjunctionEmpty = false;
390             String regex = disjunctToRegex(disjunct.toLowerCase());
391             joiner.add(regex);
392         }
393         return disjunctionEmpty ? null : Pattern.compile(joiner.toString());
394     }
395 
396     /**
397      * @param disjunct non-null mask disjunct
398      * @return java regex string corresponding to this mask
399      */
disjunctToRegex(String disjunct)400     static String disjunctToRegex(String disjunct) {
401         String regex;
402         if (disjunct.startsWith("*")) {
403             regex = ".*" + quote(disjunct.substring(1));
404         } else if (disjunct.endsWith("*")) {
405             regex = quote(disjunct.substring(0, disjunct.length() - 1)) + ".*";
406         } else {
407             regex = quote(disjunct);
408         }
409         return regex;
410     }
411 }
412