001: /*
002: * Copyright 2003-2004 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.net.spi;
027:
028: import sun.net.www.http.*;
029: import sun.net.NetProperties;
030: import java.net.*;
031: import java.util.*;
032: import java.util.regex.*;
033: import java.io.*;
034: import sun.misc.RegexpPool;
035: import java.security.AccessController;
036: import java.security.PrivilegedAction;
037:
038: /**
039: * Supports proxy settings using system properties This proxy selector
040: * provides backward compatibility with the old http protocol handler
041: * as far as how proxy is set
042: *
043: * Most of the implementation copied from the old http protocol handler
044: *
045: * Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort,
046: * proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks.
047: * NOTE: need to do gopher as well
048: */
049: public class DefaultProxySelector extends ProxySelector {
050:
051: /**
052: * This is where we define all the valid System Properties we have to
053: * support for each given protocol.
054: * The format of this 2 dimensional array is :
055: * - 1 row per protocol (http, ftp, ...)
056: * - 1st element of each row is the protocol name
057: * - subsequent elements are prefixes for Host & Port properties
058: * listed in order of priority.
059: * Example:
060: * {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
061: * means for FTP we try in that oder:
062: * + ftp.proxyHost & ftp.proxyPort
063: * + ftpProxyHost & ftpProxyPort
064: * + proxyHost & proxyPort
065: * + socksProxyHost & socksProxyPort
066: *
067: * Note that the socksProxy should *always* be the last on the list
068: */
069: final static String[][] props = {
070: /*
071: * protocol, Property prefix 1, Property prefix 2, ...
072: */
073: { "http", "http.proxy", "proxy", "socksProxy" },
074: { "https", "https.proxy", "proxy", "socksProxy" },
075: { "ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy" },
076: { "gopher", "gopherProxy", "socksProxy" },
077: { "socket", "socksProxy" } };
078:
079: private static boolean hasSystemProxies = false;
080: private static Properties defprops = new Properties();
081:
082: static {
083: final String key = "java.net.useSystemProxies";
084: Boolean b = (Boolean) AccessController
085: .doPrivileged(new PrivilegedAction() {
086: public Object run() {
087: return NetProperties.getBoolean(key);
088: }
089: });
090: if (b != null && b.booleanValue()) {
091: java.security.AccessController
092: .doPrivileged(new sun.security.action.LoadLibraryAction(
093: "net"));
094: hasSystemProxies = init();
095: }
096: }
097:
098: /**
099: * How to deal with "non proxy hosts":
100: * since we do have to generate a RegexpPool we don't want to do that if
101: * it's not necessary. Therefore we do cache the result, on a per-protocol
102: * basis, and change it only when the "source", i.e. the system property,
103: * did change.
104: */
105:
106: static class NonProxyInfo {
107: String hostsSource;
108: RegexpPool hostsPool;
109: String property;
110:
111: NonProxyInfo(String p, String s, RegexpPool pool) {
112: property = p;
113: hostsSource = s;
114: hostsPool = pool;
115: }
116: }
117:
118: private static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo(
119: "ftp.nonProxyHosts", null, null);
120: private static NonProxyInfo httpNonProxyInfo = new NonProxyInfo(
121: "http.nonProxyHosts", null, null);
122:
123: /**
124: * select() method. Where all the hard work is done.
125: * Build a list of proxies depending on URI.
126: * Since we're only providing compatibility with the system properties
127: * from previous releases (see list above), that list will always
128: * contain 1 single proxy, default being NO_PROXY.
129: */
130: public java.util.List<Proxy> select(URI uri) {
131: if (uri == null) {
132: throw new IllegalArgumentException("URI can't be null.");
133: }
134: String protocol = uri.getScheme();
135: String host = uri.getHost();
136: int port = uri.getPort();
137:
138: if (host == null) {
139: // This is a hack to ensure backward compatibility in two
140: // cases: 1. hostnames contain non-ascii characters,
141: // internationalized domain names. in which case, URI will
142: // return null, see BugID 4957669; 2. Some hostnames can
143: // contain '_' chars even though it's not supposed to be
144: // legal, in which case URI will return null for getHost,
145: // but not for getAuthority() See BugID 4913253
146: String auth = uri.getAuthority();
147: if (auth != null) {
148: int i;
149: i = auth.indexOf('@');
150: if (i >= 0) {
151: auth = auth.substring(i + 1);
152: }
153: i = auth.lastIndexOf(':');
154: if (i >= 0) {
155: try {
156: port = Integer.parseInt(auth.substring(i + 1));
157: } catch (NumberFormatException e) {
158: port = -1;
159: }
160: auth = auth.substring(0, i);
161: }
162: host = auth;
163: }
164: }
165:
166: if (protocol == null || host == null) {
167: throw new IllegalArgumentException("protocol = " + protocol
168: + " host = " + host);
169: }
170: List<Proxy> proxyl = new ArrayList<Proxy>(1);
171:
172: // special case localhost and loopback addresses to
173: // not go through proxy
174: if (isLoopback(host)) {
175: proxyl.add(Proxy.NO_PROXY);
176: return proxyl;
177: }
178:
179: NonProxyInfo pinfo = null;
180:
181: if ("http".equalsIgnoreCase(protocol)) {
182: pinfo = httpNonProxyInfo;
183: } else if ("https".equalsIgnoreCase(protocol)) {
184: // HTTPS uses the same property as HTTP, for backward
185: // compatibility
186: pinfo = httpNonProxyInfo;
187: } else if ("ftp".equalsIgnoreCase(protocol)) {
188: pinfo = ftpNonProxyInfo;
189: }
190:
191: /**
192: * Let's check the System properties for that protocol
193: */
194: final String proto = protocol;
195: final NonProxyInfo nprop = pinfo;
196: final String urlhost = host.toLowerCase();
197:
198: /**
199: * This is one big doPrivileged call, but we're trying to optimize
200: * the code as much as possible. Since we're checking quite a few
201: * System properties it does help having only 1 call to doPrivileged.
202: * Be mindful what you do in here though!
203: */
204: Proxy p = (Proxy) AccessController
205: .doPrivileged(new PrivilegedAction() {
206: public Object run() {
207: int i, j;
208: String phost = null;
209: int pport = 0;
210: String nphosts = null;
211: InetSocketAddress saddr = null;
212:
213: // Then let's walk the list of protocols in our array
214: for (i = 0; i < props.length; i++) {
215: if (props[i][0].equalsIgnoreCase(proto)) {
216: for (j = 1; j < props[i].length; j++) {
217: /* System.getProp() will give us an empty
218: * String, "" for a defined but "empty"
219: * property.
220: */
221: phost = NetProperties
222: .get(props[i][j] + "Host");
223: if (phost != null
224: && phost.length() != 0)
225: break;
226: }
227: if (phost == null
228: || phost.length() == 0) {
229: /**
230: * No system property defined for that
231: * protocol. Let's check System Proxy
232: * settings (Gnome & Windows) if we were
233: * instructed to.
234: */
235: if (hasSystemProxies) {
236: String sproto;
237: if (proto
238: .equalsIgnoreCase("socket"))
239: sproto = "socks";
240: else
241: sproto = proto;
242: Proxy sproxy = getSystemProxy(
243: sproto, urlhost);
244: if (sproxy != null) {
245: return sproxy;
246: }
247: }
248: return Proxy.NO_PROXY;
249: }
250: // If a Proxy Host is defined for that protocol
251: // Let's get the NonProxyHosts property
252: if (nprop != null) {
253: nphosts = NetProperties
254: .get(nprop.property);
255: synchronized (nprop) {
256: if (nphosts == null) {
257: nprop.hostsSource = null;
258: nprop.hostsPool = null;
259: } else {
260: if (!nphosts
261: .equals(nprop.hostsSource)) {
262: RegexpPool pool = new RegexpPool();
263: StringTokenizer st = new StringTokenizer(
264: nphosts, "|",
265: false);
266: try {
267: while (st
268: .hasMoreTokens()) {
269: pool
270: .add(
271: st
272: .nextToken()
273: .toLowerCase(),
274: Boolean.TRUE);
275: }
276: } catch (sun.misc.REException ex) {
277: }
278: nprop.hostsPool = pool;
279: nprop.hostsSource = nphosts;
280: }
281: }
282: if (nprop.hostsPool != null
283: && nprop.hostsPool
284: .match(urlhost) != null) {
285: return Proxy.NO_PROXY;
286: }
287: }
288: }
289: // We got a host, let's check for port
290:
291: pport = NetProperties.getInteger(
292: props[i][j] + "Port", 0)
293: .intValue();
294: if (pport == 0
295: && 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
302: .getInteger(
303: props[i][k]
304: + "Port",
305: 0)
306: .intValue();
307: }
308: }
309:
310: // Still couldn't find a port, let's use default
311: if (pport == 0) {
312: if (j == (props[i].length - 1)) // SOCKS
313: pport = defaultPort("socket");
314: else
315: pport = defaultPort(proto);
316: }
317: // We did find a proxy definition.
318: // Let's create the address, but don't resolve it
319: // as this will be done at connection time
320: saddr = InetSocketAddress
321: .createUnresolved(phost, pport);
322: // Socks is *always* the last on the list.
323: if (j == (props[i].length - 1)) {
324: return new Proxy(Proxy.Type.SOCKS,
325: saddr);
326: } else {
327: return new Proxy(Proxy.Type.HTTP,
328: saddr);
329: }
330: }
331: }
332: return Proxy.NO_PROXY;
333: }
334: });
335:
336: proxyl.add(p);
337:
338: /*
339: * If no specific property was set for that URI, we should be
340: * returning an iterator to an empty List.
341: */
342: return proxyl;
343: }
344:
345: public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
346: if (uri == null || sa == null || ioe == null) {
347: throw new IllegalArgumentException(
348: "Arguments can't be null.");
349: }
350: // ignored
351: }
352:
353: private int defaultPort(String protocol) {
354: if ("http".equalsIgnoreCase(protocol)) {
355: return 80;
356: } else if ("https".equalsIgnoreCase(protocol)) {
357: return 443;
358: } else if ("ftp".equalsIgnoreCase(protocol)) {
359: return 80;
360: } else if ("socket".equalsIgnoreCase(protocol)) {
361: return 1080;
362: } else if ("gopher".equalsIgnoreCase(protocol)) {
363: return 80;
364: } else {
365: return -1;
366: }
367: }
368:
369: private static final Pattern p6 = Pattern
370: .compile("::1|(0:){7}1|(0:){1,6}:1");
371:
372: private boolean isLoopback(String host) {
373: if (host == null || host.length() == 0)
374: return false;
375:
376: if (host.equalsIgnoreCase("localhost"))
377: return true;
378:
379: /* The string could represent a numerical IP address.
380: * For IPv4 addresses, check whether it starts with 127.
381: * For IPv6 addresses, check whether it is ::1 or its equivalent.
382: * Don't check IPv4-mapped or IPv4-compatible addresses
383: */
384:
385: if (host.startsWith("127.")) {
386: // possible IPv4 loopback address
387: int p = 4;
388: int q;
389: int n = host.length();
390: // Per RFC2732: At most three digits per byte
391: // Further constraint: Each element fits in a byte
392: if ((q = scanByte(host, p, n)) <= p)
393: return false;
394: p = q;
395: if ((q = scan(host, p, n, '.')) <= p)
396: return q == n && number > 0;
397: p = q;
398: if ((q = scanByte(host, p, n)) <= p)
399: return false;
400: p = q;
401: if ((q = scan(host, p, n, '.')) <= p)
402: return q == n && number > 0;
403: p = q;
404: if ((q = scanByte(host, p, n)) <= p)
405: return false;
406: return q == n && number > 0;
407: }
408:
409: if (host.endsWith(":1")) {
410: return p6.matcher(host).matches();
411: }
412: return false;
413: }
414:
415: // Character-class masks, in reverse order from RFC2396 because
416: // initializers for static fields cannot make forward references.
417:
418: // Compute a low-order mask for the characters
419: // between first and last, inclusive
420: private static long lowMask(char first, char last) {
421: long m = 0;
422: int f = Math.max(Math.min(first, 63), 0);
423: int l = Math.max(Math.min(last, 63), 0);
424: for (int i = f; i <= l; i++)
425: m |= 1L << i;
426: return m;
427: }
428:
429: // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
430: // "8" | "9"
431: private static final long L_DIGIT = lowMask('0', '9');
432: private static final long H_DIGIT = 0L;
433:
434: // Scan a string of decimal digits whose value fits in a byte
435: //
436: private int number;
437:
438: private int scanByte(String input, int start, int n) {
439: int p = start;
440: int q = scan(input, p, n, L_DIGIT, H_DIGIT);
441: if (q <= p)
442: return q;
443: number = Integer.parseInt(input.substring(p, q));
444: if (number > 255)
445: return p;
446: return q;
447: }
448:
449: // Scan a specific char: If the char at the given start position is
450: // equal to c, return the index of the next char; otherwise, return the
451: // start position.
452: //
453: private int scan(String input, int start, int end, char c) {
454: if ((start < end) && (input.charAt(start) == c))
455: return start + 1;
456: return start;
457: }
458:
459: // Scan chars that match the given mask pair
460: //
461: private int scan(String input, int start, int n, long lowMask,
462: long highMask) {
463: int p = start;
464: while (p < n) {
465: char c = input.charAt(p);
466: if (match(c, lowMask, highMask)) {
467: p++;
468: continue;
469: }
470: break;
471: }
472: return p;
473: }
474:
475: // Tell whether the given character is permitted by the given mask pair
476: private boolean match(char c, long lowMask, long highMask) {
477: if (c < 64)
478: return ((1L << c) & lowMask) != 0;
479: if (c < 128)
480: return ((1L << (c - 64)) & highMask) != 0;
481: return false;
482: }
483:
484: private native static boolean init();
485:
486: private native Proxy getSystemProxy(String protocol, String host);
487: }
|