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