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