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}