001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.util.repository;
020
021import java.net.Authenticator;
022import java.net.InetSocketAddress;
023import java.net.PasswordAuthentication;
024import java.net.SocketAddress;
025import java.net.URI;
026import java.net.URL;
027import java.util.List;
028import java.util.Map;
029import java.util.UUID;
030
031import org.eclipse.aether.repository.Authentication;
032import org.eclipse.aether.repository.AuthenticationContext;
033import org.eclipse.aether.repository.AuthenticationDigest;
034import org.eclipse.aether.repository.Proxy;
035import org.eclipse.aether.repository.ProxySelector;
036import org.eclipse.aether.repository.RemoteRepository;
037
038import static java.util.Objects.requireNonNull;
039
040/**
041 * A proxy selector that uses the {@link java.net.ProxySelector#getDefault() JRE's global proxy selector}. In
042 * combination with the system property {@code java.net.useSystemProxies}, this proxy selector can be employed to pick
043 * up the proxy configuration from the operating system, see <a
044 * href="http://docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html">Java Networking and Proxies</a> for
045 * details. The {@link java.net.Authenticator JRE's global authenticator} is used to look up credentials for a proxy
046 * when needed.
047 */
048public final class JreProxySelector implements ProxySelector {
049
050    /**
051     * Creates a new proxy selector that delegates to {@link java.net.ProxySelector#getDefault()}.
052     */
053    public JreProxySelector() {}
054
055    public Proxy getProxy(RemoteRepository repository) {
056        requireNonNull(repository, "repository cannot be null");
057        List<java.net.Proxy> proxies = null;
058        try {
059            URI uri = new URI(repository.getUrl()).parseServerAuthority();
060            proxies = java.net.ProxySelector.getDefault().select(uri);
061        } catch (Exception e) {
062            // URL invalid or not accepted by selector or no selector at all, simply use no proxy
063        }
064        if (proxies != null) {
065            for (java.net.Proxy proxy : proxies) {
066                if (java.net.Proxy.Type.DIRECT.equals(proxy.type())) {
067                    break;
068                }
069                if (java.net.Proxy.Type.HTTP.equals(proxy.type()) && isValid(proxy.address())) {
070                    InetSocketAddress addr = (InetSocketAddress) proxy.address();
071                    return new Proxy(
072                            Proxy.TYPE_HTTP, addr.getHostName(), addr.getPort(), JreProxyAuthentication.INSTANCE);
073                }
074            }
075        }
076        return null;
077    }
078
079    private static boolean isValid(SocketAddress address) {
080        if (address instanceof InetSocketAddress) {
081            /*
082             * NOTE: On some platforms with java.net.useSystemProxies=true, unconfigured proxies show up as proxy
083             * objects with empty host and port 0.
084             */
085            InetSocketAddress addr = (InetSocketAddress) address;
086            if (addr.getPort() <= 0) {
087                return false;
088            }
089            if (addr.getHostName() == null || addr.getHostName().isEmpty()) {
090                return false;
091            }
092            return true;
093        }
094        return false;
095    }
096
097    private static final class JreProxyAuthentication implements Authentication {
098
099        public static final Authentication INSTANCE = new JreProxyAuthentication();
100
101        public void fill(AuthenticationContext context, String key, Map<String, String> data) {
102            requireNonNull(context, "context cannot be null");
103            Proxy proxy = context.getProxy();
104            if (proxy == null) {
105                return;
106            }
107            if (!AuthenticationContext.USERNAME.equals(key) && !AuthenticationContext.PASSWORD.equals(key)) {
108                return;
109            }
110
111            try {
112                URL url;
113                try {
114                    url = new URL(context.getRepository().getUrl());
115                } catch (Exception e) {
116                    url = null;
117                }
118
119                PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(
120                        proxy.getHost(),
121                        null,
122                        proxy.getPort(),
123                        "http",
124                        "Credentials for proxy " + proxy,
125                        null,
126                        url,
127                        Authenticator.RequestorType.PROXY);
128                if (auth != null) {
129                    context.put(AuthenticationContext.USERNAME, auth.getUserName());
130                    context.put(AuthenticationContext.PASSWORD, auth.getPassword());
131                } else {
132                    context.put(AuthenticationContext.USERNAME, System.getProperty("http.proxyUser"));
133                    context.put(AuthenticationContext.PASSWORD, System.getProperty("http.proxyPassword"));
134                }
135            } catch (SecurityException e) {
136                // oh well, let's hope the proxy can do without auth
137            }
138        }
139
140        public void digest(AuthenticationDigest digest) {
141            requireNonNull(digest, "digest cannot be null");
142            // we don't know anything about the JRE's current authenticator, assume the worst (i.e. interactive)
143            digest.update(UUID.randomUUID().toString());
144        }
145
146        @Override
147        public boolean equals(Object obj) {
148            return this == obj || (obj != null && getClass().equals(obj.getClass()));
149        }
150
151        @Override
152        public int hashCode() {
153            return getClass().hashCode();
154        }
155    }
156}