1 package com.atlassian.httpclient.apache.httpcomponents.proxy;
2
3 import com.atlassian.fugue.Option;
4 import com.atlassian.fugue.Options;
5 import com.atlassian.httpclient.api.factory.Host;
6 import com.atlassian.httpclient.api.factory.Scheme;
7 import com.google.common.base.Preconditions;
8 import com.google.common.collect.Iterables;
9 import com.google.common.collect.Lists;
10 import org.apache.http.HttpHost;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 import javax.annotation.Nonnull;
15 import java.io.IOException;
16 import java.net.*;
17 import java.util.*;
18 import java.util.function.Predicate;
19 import java.util.regex.Pattern;
20 import java.util.regex.PatternSyntaxException;
21 import java.util.stream.Collectors;
22
23
24
25
26
27
28
29 public class ProvidedProxyConfig extends ProxyConfig {
30 private static final Logger log = LoggerFactory.getLogger(ProvidedProxyConfig.class);
31
32 private static final List<Proxy> NO_PROXIES = Collections.singletonList(Proxy.NO_PROXY);
33 private static final Iterable<String> SUPPORTED_SCHEMAS = Lists.newArrayList("http", "https");
34
35 private final Map<String, HttpHost> proxyHostMap;
36 private final Map<String, Predicate<String>> nonProxyHosts;
37
38 public ProvidedProxyConfig(@Nonnull final Map<Scheme, Host> proxyHostMap,
39 @Nonnull final Map<Scheme, List<String>> nonProxyHosts) {
40 Preconditions.checkNotNull(proxyHostMap);
41 Preconditions.checkNotNull(nonProxyHosts);
42 this.proxyHostMap = new HashMap<>(proxyHostMap.size());
43 for (Scheme s : proxyHostMap.keySet()) {
44 Host h = proxyHostMap.get(s);
45 this.proxyHostMap.put(s.schemeName(), new HttpHost(h.getHost(), h.getPort()));
46 }
47 this.nonProxyHosts = new HashMap<>(nonProxyHosts.size());
48 for (Scheme scheme : nonProxyHosts.keySet()) {
49 List<String> nonProxyHostList = nonProxyHosts.get(scheme);
50 if (nonProxyHostList != null) {
51 Pattern wildcardHostsPattern = getWildcardHostsPattern(nonProxyHostList);
52 Set<String> literalHosts = getLiteralHosts(nonProxyHostList);
53
54 this.nonProxyHosts.put(scheme.schemeName(), host ->
55 literalHosts.contains(host) ||
56 wildcardHostsPattern != null && wildcardHostsPattern.matcher(host).matches());
57 }
58 }
59 }
60
61 private Set<String> getLiteralHosts(List<String> nonProxyHosts) {
62 Set<String> literalHosts = nonProxyHosts.stream()
63 .filter(host -> host.indexOf('*') == -1)
64 .map(String::toLowerCase)
65 .collect(Collectors.toSet());
66 log.trace("Literal hosts for http.nonProxyHost: {}", literalHosts);
67 return literalHosts;
68 }
69
70 private Pattern getWildcardHostsPattern(List<String> nonProxyHosts) {
71 String compoundPattern = nonProxyHosts.stream()
72 .filter(host -> host.indexOf('*') != -1)
73 .map(String::toLowerCase)
74 .map(this::hostWildcardToPattern)
75 .filter(Objects::nonNull)
76 .map(subPattern -> "(:?" + subPattern + ")")
77 .collect(Collectors.joining("|"));
78 try {
79 if (compoundPattern.isEmpty()) {
80 return null;
81 }
82
83 log.trace("Compound pattern for http.nonProxyHost wildcard values {}: {}",
84 nonProxyHosts, compoundPattern);
85 return Pattern.compile(compoundPattern);
86 } catch (PatternSyntaxException e) {
87 log.warn("Ignoring http.nonProxyHost values \"{}\" because converting these to a regular expression failed",
88 nonProxyHosts, e);
89 return null;
90 }
91 }
92
93 @Override
94 Iterable<HttpHost> getProxyHosts() {
95 final Iterable<Option<HttpHost>> httpHosts = Iterables.transform(SUPPORTED_SCHEMAS,
96 schema -> Option.option(proxyHostMap.get(schema)));
97 return Options.flatten(Options.filterNone(httpHosts));
98 }
99
100 @Override
101 public Iterable<AuthenticationInfo> getAuthenticationInfo() {
102 log.info("Authentication info not supported for ProvidedProxyConfig");
103 return Collections.emptyList();
104 }
105
106 @Override
107 public ProxySelector toProxySelector() {
108 return new ProxySelector() {
109 @Override
110 public List<Proxy> select(URI uri) {
111 String scheme = uri.getScheme().toLowerCase();
112 String host = uri.getHost().toLowerCase();
113
114 HttpHost proxyHost = proxyHostMap.get(scheme);
115 if (proxyHost == null) {
116 return NO_PROXIES;
117 }
118
119 if (nonProxyMatch(scheme, host)) {
120 return NO_PROXIES;
121 }
122
123
124 return Collections.singletonList(new Proxy(Proxy.Type.HTTP,
125 InetSocketAddress.createUnresolved(proxyHost.getHostName(), proxyHost.getPort())));
126 }
127
128 @Override
129 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
130
131 }
132 };
133 }
134
135 @SuppressWarnings("ResultOfMethodCallIgnored")
136 private String compileHostPattern(String wildcardHost, String pattern) {
137 String regex = "^" + pattern + "$";
138 try {
139
140
141 Pattern.compile(regex);
142 } catch (PatternSyntaxException e) {
143 log.warn("Ignoring http.nonProxyHost \"{}\" because converting it to a regular expression failed", wildcardHost, e);
144 return null;
145 }
146 return regex;
147 }
148
149 private String hostWildcardToPattern(String wildcardHost) {
150
151
152 if (wildcardHost.startsWith("*")) {
153 return compileHostPattern(wildcardHost, ".*" + Pattern.quote(wildcardHost.substring(1)));
154 } else if (wildcardHost.endsWith("*")) {
155 return compileHostPattern(wildcardHost, Pattern.quote(wildcardHost.substring(0, wildcardHost.length() - 1)) + ".*");
156 } else {
157 return compileHostPattern(wildcardHost, Pattern.quote(wildcardHost));
158 }
159 }
160
161 private Boolean nonProxyMatch(String scheme, String host) {
162 return Optional.ofNullable(nonProxyHosts.get(scheme)).map(predicate -> predicate.test(host)).orElse(false);
163 }
164 }