blob: a4fd7df23e15ede56b2a2795bb00681e4dc58566 [file] [log] [blame]
/*
* Copyright 2003-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package sun.net.spi;
import sun.net.www.http.*;
import sun.net.NetProperties;
import java.net.*;
import java.util.*;
import java.util.regex.*;
import java.io.*;
import sun.misc.RegexpPool;
import java.security.AccessController;
import java.security.PrivilegedAction;
/**
* Supports proxy settings using system properties This proxy selector
* provides backward compatibility with the old http protocol handler
* as far as how proxy is set
*
* Most of the implementation copied from the old http protocol handler
*
* Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort,
* proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks.
* NOTE: need to do gopher as well
*/
public class DefaultProxySelector extends ProxySelector {
/**
* This is where we define all the valid System Properties we have to
* support for each given protocol.
* The format of this 2 dimensional array is :
* - 1 row per protocol (http, ftp, ...)
* - 1st element of each row is the protocol name
* - subsequent elements are prefixes for Host & Port properties
* listed in order of priority.
* Example:
* {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
* means for FTP we try in that oder:
* + ftp.proxyHost & ftp.proxyPort
* + ftpProxyHost & ftpProxyPort
* + proxyHost & proxyPort
* + socksProxyHost & socksProxyPort
*
* Note that the socksProxy should *always* be the last on the list
*/
final static String[][] props = {
/*
* protocol, Property prefix 1, Property prefix 2, ...
*/
{"http", "http.proxy", "proxy", "socksProxy"},
{"https", "https.proxy", "proxy", "socksProxy"},
{"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
{"gopher", "gopherProxy", "socksProxy"},
{"socket", "socksProxy"}
};
private static boolean hasSystemProxies = false;
private static Properties defprops = new Properties();
static {
final String key = "java.net.useSystemProxies";
Boolean b = AccessController.doPrivileged(
new PrivilegedAction<Boolean>() {
public Boolean run() {
return NetProperties.getBoolean(key);
}});
if (b != null && b.booleanValue()) {
java.security.AccessController.doPrivileged(
new sun.security.action.LoadLibraryAction("net"));
hasSystemProxies = init();
}
}
/**
* How to deal with "non proxy hosts":
* since we do have to generate a RegexpPool we don't want to do that if
* it's not necessary. Therefore we do cache the result, on a per-protocol
* basis, and change it only when the "source", i.e. the system property,
* did change.
*/
static class NonProxyInfo {
String hostsSource;
RegexpPool hostsPool;
String property;
NonProxyInfo(String p, String s, RegexpPool pool) {
property = p;
hostsSource = s;
hostsPool = pool;
}
}
private static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null);
private static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null);
/**
* select() method. Where all the hard work is done.
* Build a list of proxies depending on URI.
* Since we're only providing compatibility with the system properties
* from previous releases (see list above), that list will always
* contain 1 single proxy, default being NO_PROXY.
*/
public java.util.List<Proxy> select(URI uri) {
if (uri == null) {
throw new IllegalArgumentException("URI can't be null.");
}
String protocol = uri.getScheme();
String host = uri.getHost();
int port = uri.getPort();
if (host == null) {
// This is a hack to ensure backward compatibility in two
// cases: 1. hostnames contain non-ascii characters,
// internationalized domain names. in which case, URI will
// return null, see BugID 4957669; 2. Some hostnames can
// contain '_' chars even though it's not supposed to be
// legal, in which case URI will return null for getHost,
// but not for getAuthority() See BugID 4913253
String auth = uri.getAuthority();
if (auth != null) {
int i;
i = auth.indexOf('@');
if (i >= 0) {
auth = auth.substring(i+1);
}
i = auth.lastIndexOf(':');
if (i >= 0) {
try {
port = Integer.parseInt(auth.substring(i+1));
} catch (NumberFormatException e) {
port = -1;
}
auth = auth.substring(0,i);
}
host = auth;
}
}
if (protocol == null || host == null) {
throw new IllegalArgumentException("protocol = "+protocol+" host = "+host);
}
List<Proxy> proxyl = new ArrayList<Proxy>(1);
// special case localhost and loopback addresses to
// not go through proxy
if (isLoopback(host)) {
proxyl.add(Proxy.NO_PROXY);
return proxyl;
}
NonProxyInfo pinfo = null;
if ("http".equalsIgnoreCase(protocol)) {
pinfo = httpNonProxyInfo;
} else if ("https".equalsIgnoreCase(protocol)) {
// HTTPS uses the same property as HTTP, for backward
// compatibility
pinfo = httpNonProxyInfo;
} else if ("ftp".equalsIgnoreCase(protocol)) {
pinfo = ftpNonProxyInfo;
}
/**
* Let's check the System properties for that protocol
*/
final String proto = protocol;
final NonProxyInfo nprop = pinfo;
final String urlhost = host.toLowerCase();
/**
* This is one big doPrivileged call, but we're trying to optimize
* the code as much as possible. Since we're checking quite a few
* System properties it does help having only 1 call to doPrivileged.
* Be mindful what you do in here though!
*/
Proxy p = AccessController.doPrivileged(
new PrivilegedAction<Proxy>() {
public Proxy run() {
int i, j;
String phost = null;
int pport = 0;
String nphosts = null;
InetSocketAddress saddr = null;
// Then let's walk the list of protocols in our array
for (i=0; i<props.length; i++) {
if (props[i][0].equalsIgnoreCase(proto)) {
for (j = 1; j < props[i].length; j++) {
/* System.getProp() will give us an empty
* String, "" for a defined but "empty"
* property.
*/
phost = NetProperties.get(props[i][j]+"Host");
if (phost != null && phost.length() != 0)
break;
}
if (phost == null || phost.length() == 0) {
/**
* No system property defined for that
* protocol. Let's check System Proxy
* settings (Gnome & Windows) if we were
* instructed to.
*/
if (hasSystemProxies) {
String sproto;
if (proto.equalsIgnoreCase("socket"))
sproto = "socks";
else
sproto = proto;
Proxy sproxy = getSystemProxy(sproto, urlhost);
if (sproxy != null) {
return sproxy;
}
}
return Proxy.NO_PROXY;
}
// If a Proxy Host is defined for that protocol
// Let's get the NonProxyHosts property
if (nprop != null) {
nphosts = NetProperties.get(nprop.property);
synchronized (nprop) {
if (nphosts == null) {
nprop.hostsSource = null;
nprop.hostsPool = null;
} else {
if (!nphosts.equals(nprop.hostsSource)) {
RegexpPool pool = new RegexpPool();
StringTokenizer st = new StringTokenizer(nphosts, "|", false);
try {
while (st.hasMoreTokens()) {
pool.add(st.nextToken().toLowerCase(), Boolean.TRUE);
}
} catch (sun.misc.REException ex) {
}
nprop.hostsPool = pool;
nprop.hostsSource = nphosts;
}
}
if (nprop.hostsPool != null &&
nprop.hostsPool.match(urlhost) != null) {
return Proxy.NO_PROXY;
}
}
}
// We got a host, let's check for port
pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue();
if (pport == 0 && j < (props[i].length - 1)) {
// Can't find a port with same prefix as Host
// AND it's not a SOCKS proxy
// Let's try the other prefixes for that proto
for (int k = 1; k < (props[i].length - 1); k++) {
if ((k != j) && (pport == 0))
pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue();
}
}
// Still couldn't find a port, let's use default
if (pport == 0) {
if (j == (props[i].length - 1)) // SOCKS
pport = defaultPort("socket");
else
pport = defaultPort(proto);
}
// We did find a proxy definition.
// Let's create the address, but don't resolve it
// as this will be done at connection time
saddr = InetSocketAddress.createUnresolved(phost, pport);
// Socks is *always* the last on the list.
if (j == (props[i].length - 1)) {
return new Proxy(Proxy.Type.SOCKS, saddr);
} else {
return new Proxy(Proxy.Type.HTTP, saddr);
}
}
}
return Proxy.NO_PROXY;
}});
proxyl.add(p);
/*
* If no specific property was set for that URI, we should be
* returning an iterator to an empty List.
*/
return proxyl;
}
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
if (uri == null || sa == null || ioe == null) {
throw new IllegalArgumentException("Arguments can't be null.");
}
// ignored
}
private int defaultPort(String protocol) {
if ("http".equalsIgnoreCase(protocol)) {
return 80;
} else if ("https".equalsIgnoreCase(protocol)) {
return 443;
} else if ("ftp".equalsIgnoreCase(protocol)) {
return 80;
} else if ("socket".equalsIgnoreCase(protocol)) {
return 1080;
} else if ("gopher".equalsIgnoreCase(protocol)) {
return 80;
} else {
return -1;
}
}
private static final Pattern p6 = Pattern.compile("::1|(0:){7}1|(0:){1,6}:1");
private boolean isLoopback(String host) {
if (host == null || host.length() == 0)
return false;
if (host.equalsIgnoreCase("localhost"))
return true;
/* The string could represent a numerical IP address.
* For IPv4 addresses, check whether it starts with 127.
* For IPv6 addresses, check whether it is ::1 or its equivalent.
* Don't check IPv4-mapped or IPv4-compatible addresses
*/
if (host.startsWith("127.")) {
// possible IPv4 loopback address
int p = 4;
int q;
int n = host.length();
// Per RFC2732: At most three digits per byte
// Further constraint: Each element fits in a byte
if ((q = scanByte(host, p, n)) <= p) return false; p = q;
if ((q = scan(host, p, n, '.')) <= p) return q == n && number > 0; p = q;
if ((q = scanByte(host, p, n)) <= p) return false; p = q;
if ((q = scan(host, p, n, '.')) <= p) return q == n && number > 0; p = q;
if ((q = scanByte(host, p, n)) <= p) return false;
return q == n && number > 0;
}
if (host.endsWith(":1")) {
return p6.matcher(host).matches();
}
return false;
}
// Character-class masks, in reverse order from RFC2396 because
// initializers for static fields cannot make forward references.
// Compute a low-order mask for the characters
// between first and last, inclusive
private static long lowMask(char first, char last) {
long m = 0;
int f = Math.max(Math.min(first, 63), 0);
int l = Math.max(Math.min(last, 63), 0);
for (int i = f; i <= l; i++)
m |= 1L << i;
return m;
}
// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
// "8" | "9"
private static final long L_DIGIT = lowMask('0', '9');
private static final long H_DIGIT = 0L;
// Scan a string of decimal digits whose value fits in a byte
//
private int number;
private int scanByte(String input, int start, int n)
{
int p = start;
int q = scan(input, p, n, L_DIGIT, H_DIGIT);
if (q <= p) return q;
number = Integer.parseInt(input.substring(p, q));
if (number > 255) return p;
return q;
}
// Scan a specific char: If the char at the given start position is
// equal to c, return the index of the next char; otherwise, return the
// start position.
//
private int scan(String input, int start, int end, char c) {
if ((start < end) && (input.charAt(start) == c))
return start + 1;
return start;
}
// Scan chars that match the given mask pair
//
private int scan(String input, int start, int n, long lowMask, long highMask)
{
int p = start;
while (p < n) {
char c = input.charAt(p);
if (match(c, lowMask, highMask)) {
p++;
continue;
}
break;
}
return p;
}
// Tell whether the given character is permitted by the given mask pair
private boolean match(char c, long lowMask, long highMask) {
if (c < 64)
return ((1L << c) & lowMask) != 0;
if (c < 128)
return ((1L << (c - 64)) & highMask) != 0;
return false;
}
private native static boolean init();
private native Proxy getSystemProxy(String protocol, String host);
}