001package org.eclipse.aether.util.repository;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Locale;
026import java.util.Map;
027import static java.util.Objects.requireNonNull;
028import java.util.StringTokenizer;
029import java.util.regex.Pattern;
030
031import org.eclipse.aether.repository.Proxy;
032import org.eclipse.aether.repository.ProxySelector;
033import org.eclipse.aether.repository.RemoteRepository;
034
035/**
036 * A simple proxy selector that selects the first matching proxy from a list of configured proxies.
037 */
038public final class DefaultProxySelector
039    implements ProxySelector
040{
041
042    private final List<ProxyDef> proxies = new ArrayList<>();
043
044    /**
045     * Adds the specified proxy definition to the selector. Proxy definitions are ordered, the first matching proxy for
046     * a given repository will be used.
047     * 
048     * @param proxy The proxy definition to add, must not be {@code null}.
049     * @param nonProxyHosts The list of (case-insensitive) host names to exclude from proxying, may be {@code null}.
050     * @return This proxy selector for chaining, never {@code null}.
051     */
052    public DefaultProxySelector add( Proxy proxy, String nonProxyHosts )
053    {
054        requireNonNull( proxy, "proxy cannot be null" );
055        proxies.add( new ProxyDef( proxy, nonProxyHosts ) );
056
057        return this;
058    }
059
060    public Proxy getProxy( RemoteRepository repository )
061    {
062        requireNonNull( repository, "repository cannot be null" );
063        Map<String, ProxyDef> candidates = new HashMap<>();
064
065        String host = repository.getHost();
066        for ( ProxyDef proxy : proxies )
067        {
068            if ( !proxy.nonProxyHosts.isNonProxyHost( host ) )
069            {
070                String key = proxy.proxy.getType().toLowerCase( Locale.ENGLISH );
071                if ( !candidates.containsKey( key ) )
072                {
073                    candidates.put( key, proxy );
074                }
075            }
076        }
077
078        String protocol = repository.getProtocol().toLowerCase( Locale.ENGLISH );
079
080        if ( "davs".equals( protocol ) )
081        {
082            protocol = "https";
083        }
084        else if ( "dav".equals( protocol ) )
085        {
086            protocol = "http";
087        }
088        else if ( protocol.startsWith( "dav:" ) )
089        {
090            protocol = protocol.substring( "dav:".length() );
091        }
092
093        ProxyDef proxy = candidates.get( protocol );
094        if ( proxy == null && "https".equals( protocol ) )
095        {
096            proxy = candidates.get( "http" );
097        }
098
099        return ( proxy != null ) ? proxy.proxy : null;
100    }
101
102    static class NonProxyHosts
103    {
104
105        private final Pattern[] patterns;
106
107        NonProxyHosts( String nonProxyHosts )
108        {
109            List<Pattern> patterns = new ArrayList<>();
110            if ( nonProxyHosts != null )
111            {
112                for ( StringTokenizer tokenizer = new StringTokenizer( nonProxyHosts, "|" );
113                      tokenizer.hasMoreTokens(); )
114                {
115                    String pattern = tokenizer.nextToken();
116                    pattern = pattern.replace( ".", "\\." ).replace( "*", ".*" );
117                    patterns.add( Pattern.compile( pattern, Pattern.CASE_INSENSITIVE ) );
118                }
119            }
120            this.patterns = patterns.toArray( new Pattern[patterns.size()] );
121        }
122
123        boolean isNonProxyHost( String host )
124        {
125            if ( host != null )
126            {
127                for ( Pattern pattern : patterns )
128                {
129                    if ( pattern.matcher( host ).matches() )
130                    {
131                        return true;
132                    }
133                }
134            }
135            return false;
136        }
137
138    }
139
140    static class ProxyDef
141    {
142
143        final Proxy proxy;
144
145        final NonProxyHosts nonProxyHosts;
146
147        ProxyDef( Proxy proxy, String nonProxyHosts )
148        {
149            this.proxy = proxy;
150            this.nonProxyHosts = new NonProxyHosts( nonProxyHosts );
151        }
152
153    }
154
155}