1 /*
2  * Copyright (c) 2003, 2020, 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.Collections;
34 import java.util.List;
35 import java.io.IOException;
36 import java.security.AccessController;
37 import java.security.PrivilegedAction;
38 import java.util.StringJoiner;
39 import java.util.regex.Pattern;
40 import java.util.stream.Stream;
41 import sun.net.NetProperties;
42 import sun.net.SocksProxy;
43 import static java.util.regex.Pattern.quote;
44 import static java.util.stream.Collectors.collectingAndThen;
45 import static java.util.stream.Collectors.toList;
46 
47 /**
48  * Supports proxy settings using system properties This proxy selector
49  * provides backward compatibility with the old http protocol handler
50  * as far as how proxy is set
51  *
52  * Most of the implementation copied from the old http protocol handler
53  *
54  * Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort,
55  * proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks.
56  */
57 public class DefaultProxySelector extends ProxySelector {
58 
59     /**
60      * This is where we define all the valid System Properties we have to
61      * support for each given protocol.
62      * The format of this 2 dimensional array is :
63      * - 1 row per protocol (http, ftp, ...)
64      * - 1st element of each row is the protocol name
65      * - subsequent elements are prefixes for Host & Port properties
66      *   listed in order of priority.
67      * Example:
68      * {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
69      * means for FTP we try in that oder:
70      *          + ftp.proxyHost & ftp.proxyPort
71      *          + ftpProxyHost & ftpProxyPort
72      *          + proxyHost & proxyPort
73      *          + socksProxyHost & socksProxyPort
74      *
75      * Note that the socksProxy should *always* be the last on the list
76      */
77     static final String[][] props = {
78         /*
79          * protocol, Property prefix 1, Property prefix 2, ...
80          */
81         {"http", "http.proxy", "proxy", "socksProxy"},
82         {"https", "https.proxy", "proxy", "socksProxy"},
83         {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
84         {"socket", "socksProxy"}
85     };
86 
87     private static final String SOCKS_PROXY_VERSION = "socksProxyVersion";
88 
89     private static boolean hasSystemProxies = false;
90 
91     private static final List<Proxy> NO_PROXY_LIST = List.of(Proxy.NO_PROXY);
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             jdk.internal.loader.BootLoader.loadLibrary("net");
102             hasSystemProxies = init();
103         }
104     }
105 
socksProxyVersion()106     public static int socksProxyVersion() {
107         return AccessController.doPrivileged(
108                 new PrivilegedAction<Integer>() {
109                     @Override public Integer run() {
110                         return NetProperties.getInteger(SOCKS_PROXY_VERSION, 5);
111                     }
112                 });
113     }
114 
115     /**
116      * How to deal with "non proxy hosts":
117      * since we do have to generate a pattern we don't want to do that if
118      * it's not necessary. Therefore we do cache the result, on a per-protocol
119      * basis, and change it only when the "source", i.e. the system property,
120      * did change.
121      */
122 
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 
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 typically
150      * contain one single proxy, default being NO_PROXY.
151      * If we can get a system proxy it might contain more entries.
152      */
153     public java.util.List<Proxy> select(URI uri) {
154         if (uri == null) {
155             throw new IllegalArgumentException("URI can't be null.");
156         }
157         String protocol = uri.getScheme();
158         String host = uri.getHost();
159 
160         if (host == null) {
161             // This is a hack to ensure backward compatibility in two
162             // cases: 1. hostnames contain non-ascii characters,
163             // internationalized domain names. in which case, URI will
164             // return null, see BugID 4957669; 2. Some hostnames can
165             // contain '_' chars even though it's not supposed to be
166             // legal, in which case URI will return null for getHost,
167             // but not for getAuthority() See BugID 4913253
168             String auth = uri.getAuthority();
169             if (auth != null) {
170                 int i;
171                 i = auth.indexOf('@');
172                 if (i >= 0) {
173                     auth = auth.substring(i+1);
174                 }
175                 i = auth.lastIndexOf(':');
176                 if (i >= 0) {
177                     auth = auth.substring(0,i);
178                 }
179                 host = auth;
180             }
181         }
182 
183         if (protocol == null || host == null) {
184             throw new IllegalArgumentException("protocol = "+protocol+" host = "+host);
185         }
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             pinfo = NonProxyInfo.httpNonProxyInfo;
195         } else if ("ftp".equalsIgnoreCase(protocol)) {
196             pinfo = NonProxyInfo.ftpNonProxyInfo;
197         } else if ("socket".equalsIgnoreCase(protocol)) {
198             pinfo = NonProxyInfo.socksNonProxyInfo;
199         }
200 
201         /**
202          * Let's check the System properties for that protocol
203          */
204         final String proto = protocol;
205         final NonProxyInfo nprop = pinfo;
206         final String urlhost = host.toLowerCase();
207 
208         /**
209          * This is one big doPrivileged call, but we're trying to optimize
210          * the code as much as possible. Since we're checking quite a few
211          * System properties it does help having only 1 call to doPrivileged.
212          * Be mindful what you do in here though!
213          */
214         Proxy[] proxyArray = AccessController.doPrivileged(
215             new PrivilegedAction<Proxy[]>() {
216                 public Proxy[] run() {
217                     int i, j;
218                     String phost =  null;
219                     int pport = 0;
220                     String nphosts =  null;
221                     InetSocketAddress saddr = null;
222 
223                     // Then let's walk the list of protocols in our array
224                     for (i=0; i<props.length; i++) {
225                         if (props[i][0].equalsIgnoreCase(proto)) {
226                             for (j = 1; j < props[i].length; j++) {
227                                 /* System.getProp() will give us an empty
228                                  * String, "" for a defined but "empty"
229                                  * property.
230                                  */
231                                 phost =  NetProperties.get(props[i][j]+"Host");
232                                 if (phost != null && phost.length() != 0)
233                                     break;
234                             }
235                             if (phost == null || phost.isEmpty()) {
236                                 /**
237                                  * No system property defined for that
238                                  * protocol. Let's check System Proxy
239                                  * settings (Gnome, MacOsX & Windows) if
240                                  * we were instructed to.
241                                  */
242                                 if (hasSystemProxies) {
243                                     String sproto;
244                                     if (proto.equalsIgnoreCase("socket"))
245                                         sproto = "socks";
246                                     else
247                                         sproto = proto;
248                                     return getSystemProxies(sproto, urlhost);
249                                 }
250                                 return null;
251                             }
252                             // If a Proxy Host is defined for that protocol
253                             // Let's get the NonProxyHosts property
254                             if (nprop != null) {
255                                 nphosts = NetProperties.get(nprop.property);
256                                 synchronized (nprop) {
257                                     if (nphosts == null) {
258                                         if (nprop.defaultVal != null) {
259                                             nphosts = nprop.defaultVal;
260                                         } else {
261                                             nprop.hostsSource = null;
262                                             nprop.pattern = null;
263                                         }
264                                     } else if (!nphosts.isEmpty()) {
265                                         // add the required default patterns
266                                         // but only if property no set. If it
267                                         // is empty, leave empty.
268                                         nphosts += "|" + NonProxyInfo
269                                                          .defStringVal;
270                                     }
271                                     if (nphosts != null) {
272                                         if (!nphosts.equals(nprop.hostsSource)) {
273                                             nprop.pattern = toPattern(nphosts);
274                                             nprop.hostsSource = nphosts;
275                                         }
276                                     }
277                                     if (shouldNotUseProxyFor(nprop.pattern, urlhost)) {
278                                         return null;
279                                     }
280                                 }
281                             }
282                             // We got a host, let's check for port
283 
284                             pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue();
285                             if (pport == 0 && j < (props[i].length - 1)) {
286                                 // Can't find a port with same prefix as Host
287                                 // AND it's not a SOCKS proxy
288                                 // Let's try the other prefixes for that proto
289                                 for (int k = 1; k < (props[i].length - 1); k++) {
290                                     if ((k != j) && (pport == 0))
291                                         pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue();
292                                 }
293                             }
294 
295                             // Still couldn't find a port, let's use default
296                             if (pport == 0) {
297                                 if (j == (props[i].length - 1)) // SOCKS
298                                     pport = defaultPort("socket");
299                                 else
300                                     pport = defaultPort(proto);
301                             }
302                             // We did find a proxy definition.
303                             // Let's create the address, but don't resolve it
304                             // as this will be done at connection time
305                             saddr = InetSocketAddress.createUnresolved(phost, pport);
306                             // Socks is *always* the last on the list.
307                             if (j == (props[i].length - 1)) {
308                                 return new Proxy[] {SocksProxy.create(saddr, socksProxyVersion())};
309                             }
310                             return new Proxy[] {new Proxy(Proxy.Type.HTTP, saddr)};
311                         }
312                     }
313                     return null;
314                 }});
315 
316 
317         if (proxyArray != null) {
318             // Remove duplicate entries, while preserving order.
319             return Stream.of(proxyArray).distinct().collect(
320                     collectingAndThen(toList(), Collections::unmodifiableList));
321         }
322 
323         // If no specific proxy was found, return a standard list containing
324         // only one NO_PROXY entry.
325         return NO_PROXY_LIST;
326     }
327 
328     public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
329         if (uri == null || sa == null || ioe == null) {
330             throw new IllegalArgumentException("Arguments can't be null.");
331         }
332         // ignored
333     }
334 
335 
336     private int defaultPort(String protocol) {
337         if ("http".equalsIgnoreCase(protocol)) {
338             return 80;
339         } else if ("https".equalsIgnoreCase(protocol)) {
340             return 443;
341         } else if ("ftp".equalsIgnoreCase(protocol)) {
342             return 80;
343         } else if ("socket".equalsIgnoreCase(protocol)) {
344             return 1080;
345         } else {
346             return -1;
347         }
348     }
349 
350     private static native boolean init();
351     private synchronized native Proxy[] getSystemProxies(String protocol, String host);
352 
353     /**
354      * @return {@code true} if given this pattern for non-proxy hosts and this
355      *         urlhost the proxy should NOT be used to access this urlhost
356      */
357     static boolean shouldNotUseProxyFor(Pattern pattern, String urlhost) {
358         if (pattern == null || urlhost.isEmpty())
359             return false;
360         boolean matches = pattern.matcher(urlhost).matches();
361         return matches;
362     }
363 
364     /**
365      * @param mask non-null mask
366      * @return {@link java.util.regex.Pattern} corresponding to this mask
367      *         or {@code null} in case mask should not match anything
368      */
369     static Pattern toPattern(String mask) {
370         boolean disjunctionEmpty = true;
371         StringJoiner joiner = new StringJoiner("|");
372         for (String disjunct : mask.split("\\|")) {
373             if (disjunct.isEmpty())
374                 continue;
375             disjunctionEmpty = false;
376             String regex = disjunctToRegex(disjunct.toLowerCase());
377             joiner.add(regex);
378         }
379         return disjunctionEmpty ? null : Pattern.compile(joiner.toString());
380     }
381 
382     /**
383      * @param disjunct non-null mask disjunct
384      * @return java regex string corresponding to this mask
385      */
386     static String disjunctToRegex(String disjunct) {
387         String regex;
388         if (disjunct.equals("*")) {
389             regex = ".*";
390         } else if (disjunct.startsWith("*") && disjunct.endsWith("*")) {
391             regex = ".*" + quote(disjunct.substring(1, disjunct.length() - 1)) + ".*";
392         } else if (disjunct.startsWith("*")) {
393             regex = ".*" + quote(disjunct.substring(1));
394         } else if (disjunct.endsWith("*")) {
395             regex = quote(disjunct.substring(0, disjunct.length() - 1)) + ".*";
396         } else {
397             regex = quote(disjunct);
398         }
399         return regex;
400     }
401 }
402