/*
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.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
/**
* 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
/**
* 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.
/**
* 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.
/**
* 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);
}