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