001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.net.InetSocketAddress; 008import java.net.Proxy; 009import java.net.Proxy.Type; 010import java.net.ProxySelector; 011import java.net.SocketAddress; 012import java.net.URI; 013import java.util.Arrays; 014import java.util.Collections; 015import java.util.HashSet; 016import java.util.List; 017import java.util.Set; 018import java.util.TreeSet; 019 020import org.openstreetmap.josm.spi.preferences.Config; 021import org.openstreetmap.josm.tools.Logging; 022import org.openstreetmap.josm.tools.Utils; 023 024/** 025 * This is the default proxy selector used in JOSM. 026 * @since 2641 027 */ 028public class DefaultProxySelector extends ProxySelector { 029 030 /** Property key for proxy policy */ 031 public static final String PROXY_POLICY = "proxy.policy"; 032 /** Property key for HTTP proxy host */ 033 public static final String PROXY_HTTP_HOST = "proxy.http.host"; 034 /** Property key for HTTP proxy port */ 035 public static final String PROXY_HTTP_PORT = "proxy.http.port"; 036 /** Property key for SOCKS proxy host */ 037 public static final String PROXY_SOCKS_HOST = "proxy.socks.host"; 038 /** Property key for SOCKS proxy port */ 039 public static final String PROXY_SOCKS_PORT = "proxy.socks.port"; 040 /** Property key for proxy username */ 041 public static final String PROXY_USER = "proxy.user"; 042 /** Property key for proxy password */ 043 public static final String PROXY_PASS = "proxy.pass"; 044 /** Property key for proxy exceptions list */ 045 public static final String PROXY_EXCEPTIONS = "proxy.exceptions"; 046 047 private static final List<Proxy> NO_PROXY_LIST = Collections.singletonList(Proxy.NO_PROXY); 048 049 private static final String IPV4_LOOPBACK = "127.0.0.1"; 050 private static final String IPV6_LOOPBACK = "::1"; 051 052 /** 053 * The {@link ProxySelector} provided by the JDK will retrieve proxy information 054 * from the system settings, if the system property <code>java.net.useSystemProxies</code> 055 * is defined <strong>at startup</strong>. It has no effect if the property is set 056 * later by the application. 057 * 058 * We therefore read the property at class loading time and remember it's value. 059 */ 060 private static boolean jvmWillUseSystemProxies; 061 static { 062 String v = Utils.getSystemProperty("java.net.useSystemProxies"); 063 if (v != null && v.equals(Boolean.TRUE.toString())) { 064 jvmWillUseSystemProxies = true; 065 } 066 } 067 068 /** 069 * The {@link ProxySelector} provided by the JDK will retrieve proxy information 070 * from the system settings, if the system property <code>java.net.useSystemProxies</code> 071 * is defined <strong>at startup</strong>. If the property is set later by the application, 072 * this has no effect. 073 * 074 * @return true, if <code>java.net.useSystemProxies</code> was set to true at class initialization time 075 * 076 */ 077 public static boolean willJvmRetrieveSystemProxies() { 078 return jvmWillUseSystemProxies; 079 } 080 081 private ProxyPolicy proxyPolicy; 082 private InetSocketAddress httpProxySocketAddress; 083 private InetSocketAddress socksProxySocketAddress; 084 private final ProxySelector delegate; 085 086 private final Set<String> errorResources = new HashSet<>(); 087 private final Set<String> errorMessages = new HashSet<>(); 088 private Set<String> proxyExceptions; 089 090 /** 091 * A typical example is: 092 * <pre> 093 * PropertySelector delegate = PropertySelector.getDefault(); 094 * PropertySelector.setDefault(new DefaultPropertySelector(delegate)); 095 * </pre> 096 * 097 * @param delegate the proxy selector to delegate to if system settings are used. Usually 098 * this is the proxy selector found by ProxySelector.getDefault() before this proxy 099 * selector is installed 100 */ 101 public DefaultProxySelector(ProxySelector delegate) { 102 this.delegate = delegate; 103 initFromPreferences(); 104 } 105 106 protected int parseProxyPortValue(String property, String value) { 107 if (value == null) return 0; 108 int port = 0; 109 try { 110 port = Integer.parseInt(value); 111 } catch (NumberFormatException e) { 112 Logging.error(tr("Unexpected format for port number in preference ''{0}''. Got ''{1}''.", property, value)); 113 Logging.error(tr("The proxy will not be used.")); 114 return 0; 115 } 116 if (port <= 0 || port > 65_535) { 117 Logging.error(tr("Illegal port number in preference ''{0}''. Got {1}.", property, port)); 118 Logging.error(tr("The proxy will not be used.")); 119 return 0; 120 } 121 return port; 122 } 123 124 /** 125 * Initializes the proxy selector from the setting in the preferences. 126 * 127 */ 128 public final void initFromPreferences() { 129 String value = Config.getPref().get(PROXY_POLICY); 130 if (value.isEmpty()) { 131 proxyPolicy = ProxyPolicy.NO_PROXY; 132 } else { 133 proxyPolicy = ProxyPolicy.fromName(value); 134 if (proxyPolicy == null) { 135 Logging.warn(tr("Unexpected value for preference ''{0}'' found. Got ''{1}''. Will use no proxy.", 136 PROXY_POLICY, value)); 137 proxyPolicy = ProxyPolicy.NO_PROXY; 138 } 139 } 140 String host = Config.getPref().get(PROXY_HTTP_HOST, null); 141 int port = parseProxyPortValue(PROXY_HTTP_PORT, Config.getPref().get(PROXY_HTTP_PORT, null)); 142 httpProxySocketAddress = null; 143 if (proxyPolicy == ProxyPolicy.USE_HTTP_PROXY) { 144 if (host != null && !host.trim().isEmpty() && port > 0) { 145 httpProxySocketAddress = new InetSocketAddress(host, port); 146 } else { 147 Logging.warn(tr("Unexpected parameters for HTTP proxy. Got host ''{0}'' and port ''{1}''.", host, port)); 148 Logging.warn(tr("The proxy will not be used.")); 149 } 150 } 151 152 host = Config.getPref().get(PROXY_SOCKS_HOST, null); 153 port = parseProxyPortValue(PROXY_SOCKS_PORT, Config.getPref().get(PROXY_SOCKS_PORT, null)); 154 socksProxySocketAddress = null; 155 if (proxyPolicy == ProxyPolicy.USE_SOCKS_PROXY) { 156 if (host != null && !host.trim().isEmpty() && port > 0) { 157 socksProxySocketAddress = new InetSocketAddress(host, port); 158 } else { 159 Logging.warn(tr("Unexpected parameters for SOCKS proxy. Got host ''{0}'' and port ''{1}''.", host, port)); 160 Logging.warn(tr("The proxy will not be used.")); 161 } 162 } 163 proxyExceptions = new HashSet<>( 164 Config.getPref().getList(PROXY_EXCEPTIONS, 165 Arrays.asList("localhost", IPV4_LOOPBACK, IPV6_LOOPBACK)) 166 ); 167 } 168 169 @Override 170 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { 171 // Just log something. The network stack will also throw an exception which will be caught somewhere else 172 Logging.error(tr("Connection to proxy ''{0}'' for URI ''{1}'' failed. Exception was: {2}", 173 sa.toString(), uri.toString(), ioe.toString())); 174 // Remember errors to give a friendly user message asking to review proxy configuration 175 errorResources.add(uri.toString()); 176 errorMessages.add(ioe.toString()); 177 } 178 179 /** 180 * Returns the set of current proxy resources that failed to be retrieved. 181 * @return the set of current proxy resources that failed to be retrieved 182 * @since 6523 183 */ 184 public final Set<String> getErrorResources() { 185 return new TreeSet<>(errorResources); 186 } 187 188 /** 189 * Returns the set of current proxy error messages. 190 * @return the set of current proxy error messages 191 * @since 6523 192 */ 193 public final Set<String> getErrorMessages() { 194 return new TreeSet<>(errorMessages); 195 } 196 197 /** 198 * Clear the sets of failed resources and error messages. 199 * @since 6523 200 */ 201 public final void clearErrors() { 202 errorResources.clear(); 203 errorMessages.clear(); 204 } 205 206 /** 207 * Determines if proxy errors have occurred. 208 * @return {@code true} if errors have occurred, {@code false} otherwise. 209 * @since 6523 210 */ 211 public final boolean hasErrors() { 212 return !errorResources.isEmpty(); 213 } 214 215 @Override 216 public List<Proxy> select(URI uri) { 217 if (uri != null && proxyExceptions.contains(uri.getHost())) { 218 return NO_PROXY_LIST; 219 } 220 switch(proxyPolicy) { 221 case USE_SYSTEM_SETTINGS: 222 if (!jvmWillUseSystemProxies) { 223 Logging.warn(tr("The JVM is not configured to lookup proxies from the system settings. "+ 224 "The property ''java.net.useSystemProxies'' was missing at startup time. Will not use a proxy.")); 225 return NO_PROXY_LIST; 226 } 227 // delegate to the former proxy selector 228 return delegate.select(uri); 229 case NO_PROXY: 230 return NO_PROXY_LIST; 231 case USE_HTTP_PROXY: 232 if (httpProxySocketAddress == null) 233 return NO_PROXY_LIST; 234 return Collections.singletonList(new Proxy(Type.HTTP, httpProxySocketAddress)); 235 case USE_SOCKS_PROXY: 236 if (socksProxySocketAddress == null) 237 return NO_PROXY_LIST; 238 return Collections.singletonList(new Proxy(Type.SOCKS, socksProxySocketAddress)); 239 } 240 // should not happen 241 return null; 242 } 243}