// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.io;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.gui.preferences.server.ProxyPreferencesPanel;
import org.openstreetmap.josm.gui.preferences.server.ProxyPreferencesPanel.ProxyPolicy;
/**
* This is the default proxy selector used in JOSM.
*
*/
public class DefaultProxySelector extends ProxySelector {
private static final List<Proxy> NO_PROXY_LIST = Collections.singletonList(Proxy.NO_PROXY);
private static final String IPV4_LOOPBACK = "127.0.0.1";
private static final String IPV6_LOOPBACK = "::1";
/**
* The {@link ProxySelector} provided by the JDK will retrieve proxy information
* from the system settings, if the system property <tt>java.net.useSystemProxies</tt>
* is defined <strong>at startup</strong>. It has no effect if the property is set
* later by the application.
*
* We therefore read the property at class loading time and remember it's value.
*/
private static boolean JVM_WILL_USE_SYSTEM_PROXIES;
static {
String v = System.getProperty("java.net.useSystemProxies");
if (v != null && v.equals(Boolean.TRUE.toString())) {
JVM_WILL_USE_SYSTEM_PROXIES = true;
}
}
/**
* The {@link ProxySelector} provided by the JDK will retrieve proxy information
* from the system settings, if the system property <tt>java.net.useSystemProxies</tt>
* is defined <strong>at startup</strong>. If the property is set later by the application,
* this has no effect.
*
* @return true, if <tt>java.net.useSystemProxies</tt> was set to true at class initialization time
*
*/
public static boolean willJvmRetrieveSystemProxies() {
return JVM_WILL_USE_SYSTEM_PROXIES;
}
private ProxyPolicy proxyPolicy;
private InetSocketAddress httpProxySocketAddress;
private InetSocketAddress socksProxySocketAddress;
private final ProxySelector delegate;
private final Set<String> errorResources = new HashSet<>();
private final Set<String> errorMessages = new HashSet<>();
private Set<String> proxyExceptions;
/**
* A typical example is:
* <pre>
* PropertySelector delegate = PropertySelector.getDefault();
* PropertySelector.setDefault(new DefaultPropertySelector(delegate));
* </pre>
*
* @param delegate the proxy selector to delegate to if system settings are used. Usually
* this is the proxy selector found by ProxySelector.getDefault() before this proxy
* selector is installed
*/
public DefaultProxySelector(ProxySelector delegate) {
this.delegate = delegate;
initFromPreferences();
}
protected int parseProxyPortValue(String property, String value) {
if (value == null) return 0;
int port = 0;
try {
port = Integer.parseInt(value);
} catch (NumberFormatException e) {
Main.error(tr("Unexpected format for port number in preference ''{0}''. Got ''{1}''.", property, value));
Main.error(tr("The proxy will not be used."));
return 0;
}
if (port <= 0 || port > 65_535) {
Main.error(tr("Illegal port number in preference ''{0}''. Got {1}.", property, port));
Main.error(tr("The proxy will not be used."));
return 0;
}
return port;
}
/**
* Initializes the proxy selector from the setting in the preferences.
*
*/
public final void initFromPreferences() {
String value = Main.pref.get(ProxyPreferencesPanel.PROXY_POLICY);
if (value.isEmpty()) {
proxyPolicy = ProxyPolicy.NO_PROXY;
} else {
proxyPolicy = ProxyPolicy.fromName(value);
if (proxyPolicy == null) {
Main.warn(tr("Unexpected value for preference ''{0}'' found. Got ''{1}''. Will use no proxy.",
ProxyPreferencesPanel.PROXY_POLICY, value));
proxyPolicy = ProxyPolicy.NO_PROXY;
}
}
String host = Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_HOST, null);
int port = parseProxyPortValue(ProxyPreferencesPanel.PROXY_HTTP_PORT, Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_PORT, null));
httpProxySocketAddress = null;
if (proxyPolicy.equals(ProxyPolicy.USE_HTTP_PROXY)) {
if (host != null && !host.trim().isEmpty() && port > 0) {
httpProxySocketAddress = new InetSocketAddress(host, port);
} else {
Main.warn(tr("Unexpected parameters for HTTP proxy. Got host ''{0}'' and port ''{1}''.", host, port));
Main.warn(tr("The proxy will not be used."));
}
}
host = Main.pref.get(ProxyPreferencesPanel.PROXY_SOCKS_HOST, null);
port = parseProxyPortValue(ProxyPreferencesPanel.PROXY_SOCKS_PORT, Main.pref.get(ProxyPreferencesPanel.PROXY_SOCKS_PORT, null));
socksProxySocketAddress = null;
if (proxyPolicy.equals(ProxyPolicy.USE_SOCKS_PROXY)) {
if (host != null && !host.trim().isEmpty() && port > 0) {
socksProxySocketAddress = new InetSocketAddress(host, port);
} else {
Main.warn(tr("Unexpected parameters for SOCKS proxy. Got host ''{0}'' and port ''{1}''.", host, port));
Main.warn(tr("The proxy will not be used."));
}
}
proxyExceptions = new HashSet<>(
Main.pref.getCollection(ProxyPreferencesPanel.PROXY_EXCEPTIONS,
Arrays.asList(new String[]{"localhost", IPV4_LOOPBACK, IPV6_LOOPBACK}))
);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
// Just log something. The network stack will also throw an exception which will be caught somewhere else
Main.error(tr("Connection to proxy ''{0}'' for URI ''{1}'' failed. Exception was: {2}", sa.toString(), uri.toString(), ioe.toString()));
// Remember errors to give a friendly user message asking to review proxy configuration
errorResources.add(uri.toString());
errorMessages.add(ioe.toString());
}
/**
* Returns the set of current proxy resources that failed to be retrieved.
* @return the set of current proxy resources that failed to be retrieved
* @since 6523
*/
public final Set<String> getErrorResources() {
return new TreeSet<>(errorResources);
}
/**
* Returns the set of current proxy error messages.
* @return the set of current proxy error messages
* @since 6523
*/
public final Set<String> getErrorMessages() {
return new TreeSet<>(errorMessages);
}
/**
* Clear the sets of failed resources and error messages.
* @since 6523
*/
public final void clearErrors() {
errorResources.clear();
errorMessages.clear();
}
/**
* Determines if proxy errors have occured.
* @return {@code true} if errors have occured, {@code false} otherwise.
* @since 6523
*/
public final boolean hasErrors() {
return !errorResources.isEmpty();
}
@Override
public List<Proxy> select(URI uri) {
if (uri != null && proxyExceptions.contains(uri.getHost())) {
return NO_PROXY_LIST;
}
switch(proxyPolicy) {
case USE_SYSTEM_SETTINGS:
if (!JVM_WILL_USE_SYSTEM_PROXIES) {
Main.warn(tr("The JVM is not configured to lookup proxies from the system settings. "+
"The property ''java.net.useSystemProxies'' was missing at startup time. Will not use a proxy."));
return NO_PROXY_LIST;
}
// delegate to the former proxy selector
return delegate.select(uri);
case NO_PROXY:
return NO_PROXY_LIST;
case USE_HTTP_PROXY:
if (httpProxySocketAddress == null)
return NO_PROXY_LIST;
return Collections.singletonList(new Proxy(Type.HTTP, httpProxySocketAddress));
case USE_SOCKS_PROXY:
if (socksProxySocketAddress == null)
return NO_PROXY_LIST;
return Collections.singletonList(new Proxy(Type.SOCKS, socksProxySocketAddress));
}
// should not happen
return null;
}
}