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.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Locale;
025import java.util.Map;
026import java.util.StringTokenizer;
027import java.util.concurrent.CopyOnWriteArrayList;
028import java.util.regex.Pattern;
029
030import org.eclipse.aether.repository.Proxy;
031import org.eclipse.aether.repository.ProxySelector;
032import org.eclipse.aether.repository.RemoteRepository;
033
034import static java.util.Objects.requireNonNull;
035
036/**
037 * A simple proxy selector that selects the first matching proxy from a list of configured proxies.
038 */
039public final class DefaultProxySelector implements ProxySelector {
040
041    private final List<ProxyDef> proxies = new CopyOnWriteArrayList<>();
042
043    /**
044     * Adds the specified proxy definition to the selector. Proxy definitions are ordered, the first matching proxy for
045     * a given repository will be used.
046     *
047     * @param proxy the proxy definition to add, must not be {@code null}
048     * @param nonProxyHosts the list of (case-insensitive) host names to exclude from proxying, may be {@code null}
049     * @return this proxy selector for chaining, never {@code null}
050     */
051    public DefaultProxySelector add(Proxy proxy, String nonProxyHosts) {
052        requireNonNull(proxy, "proxy cannot be null");
053        proxies.add(new ProxyDef(proxy, nonProxyHosts));
054
055        return this;
056    }
057
058    public Proxy getProxy(RemoteRepository repository) {
059        requireNonNull(repository, "repository cannot be null");
060        Map<String, ProxyDef> candidates = new HashMap<>();
061
062        String host = repository.getHost();
063        for (ProxyDef proxy : proxies) {
064            if (!proxy.nonProxyHosts.isNonProxyHost(host)) {
065                String key = proxy.proxy.getType().toLowerCase(Locale.ENGLISH);
066                if (!candidates.containsKey(key)) {
067                    candidates.put(key, proxy);
068                }
069            }
070        }
071
072        String protocol = repository.getProtocol().toLowerCase(Locale.ENGLISH);
073
074        if ("davs".equals(protocol)) {
075            protocol = "https";
076        } else if ("dav".equals(protocol)) {
077            protocol = "http";
078        } else if (protocol.startsWith("dav:")) {
079            protocol = protocol.substring("dav:".length());
080        }
081
082        ProxyDef proxy = candidates.get(protocol);
083        if (proxy == null && "https".equals(protocol)) {
084            proxy = candidates.get("http");
085        }
086
087        return (proxy != null) ? proxy.proxy : null;
088    }
089
090    static class NonProxyHosts {
091
092        private final Pattern[] patterns;
093
094        NonProxyHosts(String nonProxyHosts) {
095            List<Pattern> patterns = new ArrayList<>();
096            if (nonProxyHosts != null) {
097                for (StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|"); tokenizer.hasMoreTokens(); ) {
098                    String pattern = tokenizer.nextToken();
099                    pattern = pattern.replace(".", "\\.").replace("*", ".*");
100                    patterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
101                }
102            }
103            this.patterns = patterns.toArray(new Pattern[0]);
104        }
105
106        boolean isNonProxyHost(String host) {
107            if (host != null) {
108                for (Pattern pattern : patterns) {
109                    if (pattern.matcher(host).matches()) {
110                        return true;
111                    }
112                }
113            }
114            return false;
115        }
116    }
117
118    static class ProxyDef {
119
120        final Proxy proxy;
121
122        final NonProxyHosts nonProxyHosts;
123
124        ProxyDef(Proxy proxy, String nonProxyHosts) {
125            this.proxy = proxy;
126            this.nonProxyHosts = new NonProxyHosts(nonProxyHosts);
127        }
128    }
129}