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