1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.http.apache;
17
18 import static software.amazon.awssdk.utils.StringUtils.isEmpty;
19
20 import java.net.URI;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.Set;
25 import java.util.stream.Collectors;
26 import software.amazon.awssdk.annotations.SdkPublicApi;
27 import software.amazon.awssdk.utils.ProxySystemSetting;
28 import software.amazon.awssdk.utils.StringUtils;
29 import software.amazon.awssdk.utils.ToString;
30 import software.amazon.awssdk.utils.Validate;
31 import software.amazon.awssdk.utils.builder.CopyableBuilder;
32 import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
33
34 /**
35  * Configuration that defines how to communicate via an HTTP proxy.
36  */

37 @SdkPublicApi
38 public final class ProxyConfiguration implements ToCopyableBuilder<ProxyConfiguration.Builder, ProxyConfiguration> {
39
40     private final URI endpoint;
41     private final String username;
42     private final String password;
43     private final String ntlmDomain;
44     private final String ntlmWorkstation;
45     private final Set<String> nonProxyHosts;
46     private final Boolean preemptiveBasicAuthenticationEnabled;
47     private final Boolean useSystemPropertyValues;
48     private final String host;
49     private final int port;
50     private final String scheme;
51
52     /**
53      * Initialize this configuration. Private to require use of {@link #builder()}.
54      */

55     private ProxyConfiguration(DefaultClientProxyConfigurationBuilder builder) {
56         this.endpoint = builder.endpoint;
57         this.username = builder.username;
58         this.password = builder.password;
59         this.ntlmDomain = builder.ntlmDomain;
60         this.ntlmWorkstation = builder.ntlmWorkstation;
61         this.nonProxyHosts = builder.nonProxyHosts;
62         this.preemptiveBasicAuthenticationEnabled = builder.preemptiveBasicAuthenticationEnabled == null ? Boolean.FALSE :
63                 builder.preemptiveBasicAuthenticationEnabled;
64         this.useSystemPropertyValues = builder.useSystemPropertyValues;
65         this.host = resolveHost();
66         this.port = resolvePort();
67         this.scheme = resolveScheme();
68     }
69
70     /**
71      * Returns the proxy host name either from the configured endpoint or
72      * from the "http.proxyHost" system property if {@link Builder#useSystemPropertyValues(Boolean)} is set to true.
73      */

74     public String host() {
75         return host;
76     }
77
78     /**
79      * Returns the proxy port either from the configured endpoint or
80      * from the "http.proxyPort" system property if {@link Builder#useSystemPropertyValues(Boolean)} is set to true.
81      *
82      * If no value is found in neither of the above options, the default value of 0 is returned.
83      */

84     public int port() {
85         return port;
86     }
87
88     /**
89      * Returns the {@link URI#scheme} from the configured endpoint. Otherwise return null.
90      */

91     public String scheme() {
92         return scheme;
93     }
94
95     /**
96      * The username to use when connecting through a proxy.
97      *
98      * @see Builder#password(String)
99      */

100     public String username() {
101         return resolveValue(username, ProxySystemSetting.PROXY_USERNAME);
102     }
103
104     /**
105      * The password to use when connecting through a proxy.
106      *
107      * @see Builder#password(String)
108      */

109     public String password() {
110         return resolveValue(password, ProxySystemSetting.PROXY_PASSWORD);
111     }
112
113     /**
114      * For NTLM proxies: The Windows domain name to use when authenticating with the proxy.
115      *
116      * @see Builder#ntlmDomain(String)
117      */

118     public String ntlmDomain() {
119         return ntlmDomain;
120     }
121
122     /**
123      * For NTLM proxies: The Windows workstation name to use when authenticating with the proxy.
124      *
125      * @see Builder#ntlmWorkstation(String)
126      */

127     public String ntlmWorkstation() {
128         return ntlmWorkstation;
129     }
130
131     /**
132      * The hosts that the client is allowed to access without going through the proxy.
133      *
134      * If the value is not set on the object, the value represent by "http.nonProxyHosts" system property is returned.
135      * If system property is also not set, an unmodifiable empty set is returned.
136      *
137      * @see Builder#nonProxyHosts(Set)
138      */

139     public Set<String> nonProxyHosts() {
140         Set<String> hosts = nonProxyHosts == null && useSystemPropertyValues ? parseNonProxyHostsProperty()
141                                                                              : nonProxyHosts;
142
143         return Collections.unmodifiableSet(hosts != null ? hosts : Collections.emptySet());
144     }
145
146     /**
147      * Whether to attempt to authenticate preemptively against the proxy server using basic authentication.
148      *
149      * @see Builder#preemptiveBasicAuthenticationEnabled(Boolean)
150      */

151     public Boolean preemptiveBasicAuthenticationEnabled() {
152         return preemptiveBasicAuthenticationEnabled;
153     }
154
155     @Override
156     public Builder toBuilder() {
157         return builder()
158                 .endpoint(endpoint)
159                 .username(username)
160                 .password(password)
161                 .ntlmDomain(ntlmDomain)
162                 .ntlmWorkstation(ntlmWorkstation)
163                 .nonProxyHosts(nonProxyHosts)
164                 .preemptiveBasicAuthenticationEnabled(preemptiveBasicAuthenticationEnabled);
165     }
166
167     /**
168      * Create a {@link Builder}, used to create a {@link ProxyConfiguration}.
169      */

170     public static Builder builder() {
171         return new DefaultClientProxyConfigurationBuilder();
172     }
173
174     @Override
175     public String toString() {
176         return ToString.builder("ProxyConfiguration")
177                        .add("endpoint", endpoint)
178                        .add("username", username)
179                        .add("ntlmDomain", ntlmDomain)
180                        .add("ntlmWorkstation", ntlmWorkstation)
181                        .add("nonProxyHosts", nonProxyHosts)
182                        .add("preemptiveBasicAuthenticationEnabled", preemptiveBasicAuthenticationEnabled)
183                        .build();
184     }
185
186
187     private String resolveHost() {
188         return endpoint != null ? endpoint.getHost()
189                                 : resolveValue(null, ProxySystemSetting.PROXY_HOST);
190     }
191
192     private int resolvePort() {
193         int port = 0;
194
195         if (endpoint != null) {
196             port = endpoint.getPort();
197         } else if (useSystemPropertyValues) {
198             port = ProxySystemSetting.PROXY_PORT.getStringValue()
199                                                 .map(Integer::parseInt)
200                                                 .orElse(0);
201         }
202
203         return port;
204     }
205
206     public String resolveScheme() {
207         return endpoint != null ? endpoint.getScheme() : null;
208     }
209
210     /**
211      * Uses the configuration options, system setting property and returns the final value of the given member.
212      */

213     private String resolveValue(String value, ProxySystemSetting systemSetting) {
214         return value == null && useSystemPropertyValues ? systemSetting.getStringValue().orElse(null)
215                                                         : value;
216     }
217
218     /**
219      * Returns the Java system property for nonProxyHosts as set of Strings.
220      * See http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html.
221      */

222     private Set<String> parseNonProxyHostsProperty() {
223         String nonProxyHosts = ProxySystemSetting.NON_PROXY_HOSTS.getStringValue().orElse(null);
224
225         if (!StringUtils.isEmpty(nonProxyHosts)) {
226             return Arrays.stream(nonProxyHosts.split("\\|"))
227                          .map(String::toLowerCase)
228                          .map(s -> s.replace("*"".*?"))
229                          .collect(Collectors.toSet());
230         }
231         return Collections.emptySet();
232     }
233
234     /**
235      * A builder for {@link ProxyConfiguration}.
236      *
237      * <p>All implementations of this interface are mutable and not thread safe.</p>
238      */

239     public interface Builder extends CopyableBuilder<Builder, ProxyConfiguration> {
240
241         /**
242          * Configure the endpoint of the proxy server that the SDK should connect through. Currently, the endpoint is limited to
243          * a host and port. Any other URI components will result in an exception being raised.
244          */

245         Builder endpoint(URI endpoint);
246
247         /**
248          * Configure the username to use when connecting through a proxy.
249          */

250         Builder username(String username);
251
252         /**
253          * Configure the password to use when connecting through a proxy.
254          */

255         Builder password(String password);
256
257         /**
258          * For NTLM proxies: Configure the Windows domain name to use when authenticating with the proxy.
259          */

260         Builder ntlmDomain(String proxyDomain);
261
262         /**
263          * For NTLM proxies: Configure the Windows workstation name to use when authenticating with the proxy.
264          */

265         Builder ntlmWorkstation(String proxyWorkstation);
266
267         /**
268          * Configure the hosts that the client is allowed to access without going through the proxy.
269          */

270         Builder nonProxyHosts(Set<String> nonProxyHosts);
271
272         /**
273          * Add a host that the client is allowed to access without going through the proxy.
274          *
275          * @see ProxyConfiguration#nonProxyHosts()
276          */

277         Builder addNonProxyHost(String nonProxyHost);
278
279         /**
280          * Configure whether to attempt to authenticate pre-emptively against the proxy server using basic authentication.
281          */

282         Builder preemptiveBasicAuthenticationEnabled(Boolean preemptiveBasicAuthenticationEnabled);
283
284         /**
285          * Option whether to use system property values from {@link ProxySystemSetting} if any of the config options are missing.
286          *
287          * This value is set to "true" by default which means SDK will automatically use system property values
288          * for options that are not provided during building the {@link ProxyConfiguration} object. To disable this behavior,
289          * set this value to "false".
290          */

291         Builder useSystemPropertyValues(Boolean useSystemPropertyValues);
292
293     }
294
295     /**
296      * An SDK-internal implementation of {@link Builder}.
297      */

298     private static final class DefaultClientProxyConfigurationBuilder implements Builder {
299
300         private URI endpoint;
301         private String username;
302         private String password;
303         private String ntlmDomain;
304         private String ntlmWorkstation;
305         private Set<String> nonProxyHosts;
306         private Boolean preemptiveBasicAuthenticationEnabled;
307         private Boolean useSystemPropertyValues = Boolean.TRUE;
308
309         @Override
310         public Builder endpoint(URI endpoint) {
311             if (endpoint != null) {
312                 Validate.isTrue(isEmpty(endpoint.getUserInfo()), "Proxy endpoint user info is not supported.");
313                 Validate.isTrue(isEmpty(endpoint.getPath()), "Proxy endpoint path is not supported.");
314                 Validate.isTrue(isEmpty(endpoint.getQuery()), "Proxy endpoint query is not supported.");
315                 Validate.isTrue(isEmpty(endpoint.getFragment()), "Proxy endpoint fragment is not supported.");
316             }
317
318             this.endpoint = endpoint;
319             return this;
320         }
321
322         public void setEndpoint(URI endpoint) {
323             endpoint(endpoint);
324         }
325
326         @Override
327         public Builder username(String username) {
328             this.username = username;
329             return this;
330         }
331
332         public void setUsername(String username) {
333             username(username);
334         }
335
336         @Override
337         public Builder password(String password) {
338             this.password = password;
339             return this;
340         }
341
342         public void setPassword(String password) {
343             password(password);
344         }
345
346         @Override
347         public Builder ntlmDomain(String proxyDomain) {
348             this.ntlmDomain = proxyDomain;
349             return this;
350         }
351
352         public void setNtlmDomain(String ntlmDomain) {
353             ntlmDomain(ntlmDomain);
354         }
355
356         @Override
357         public Builder ntlmWorkstation(String proxyWorkstation) {
358             this.ntlmWorkstation = proxyWorkstation;
359             return this;
360         }
361
362         public void setNtlmWorkstation(String ntlmWorkstation) {
363             ntlmWorkstation(ntlmWorkstation);
364         }
365
366         @Override
367         public Builder nonProxyHosts(Set<String> nonProxyHosts) {
368             this.nonProxyHosts = new HashSet<>(nonProxyHosts);
369             return this;
370         }
371
372         @Override
373         public Builder addNonProxyHost(String nonProxyHost) {
374             if (this.nonProxyHosts == null) {
375                 this.nonProxyHosts = new HashSet<>();
376             }
377             this.nonProxyHosts.add(nonProxyHost);
378             return this;
379         }
380
381         public void setNonProxyHosts(Set<String> nonProxyHosts) {
382             nonProxyHosts(nonProxyHosts);
383         }
384
385         @Override
386         public Builder preemptiveBasicAuthenticationEnabled(Boolean preemptiveBasicAuthenticationEnabled) {
387             this.preemptiveBasicAuthenticationEnabled = preemptiveBasicAuthenticationEnabled;
388             return this;
389         }
390
391         public void setPreemptiveBasicAuthenticationEnabled(Boolean preemptiveBasicAuthenticationEnabled) {
392             preemptiveBasicAuthenticationEnabled(preemptiveBasicAuthenticationEnabled);
393         }
394
395         @Override
396         public Builder useSystemPropertyValues(Boolean useSystemPropertyValues) {
397             this.useSystemPropertyValues = useSystemPropertyValues;
398             return this;
399         }
400
401         public void setUseSystemPropertyValues(Boolean useSystemPropertyValues) {
402             useSystemPropertyValues(useSystemPropertyValues);
403         }
404
405         @Override
406         public ProxyConfiguration build() {
407             return new ProxyConfiguration(this);
408         }
409     }
410 }
411