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.internal.utils;
17 
18 import java.io.IOException;
19 import java.io.UncheckedIOException;
20 import org.apache.http.HttpEntity;
21 import org.apache.http.HttpHost;
22 import org.apache.http.auth.AuthScope;
23 import org.apache.http.auth.Credentials;
24 import org.apache.http.auth.NTCredentials;
25 import org.apache.http.client.AuthCache;
26 import org.apache.http.client.CredentialsProvider;
27 import org.apache.http.client.config.RequestConfig;
28 import org.apache.http.client.protocol.HttpClientContext;
29 import org.apache.http.entity.BufferedHttpEntity;
30 import org.apache.http.impl.auth.BasicScheme;
31 import org.apache.http.impl.client.BasicAuthCache;
32 import org.apache.http.impl.client.BasicCredentialsProvider;
33 import software.amazon.awssdk.annotations.SdkInternalApi;
34 import software.amazon.awssdk.http.apache.ProxyConfiguration;
35 import software.amazon.awssdk.utils.Logger;
36 import software.amazon.awssdk.utils.ReflectionMethodInvoker;
37 
38 @SdkInternalApi
39 public final class ApacheUtils {
40     private static final Logger logger = Logger.loggerFor(ApacheUtils.class);
41     private static final ReflectionMethodInvoker<RequestConfig.Builder, RequestConfig.Builder> NORMALIZE_URI_INVOKER;
42 
43     static {
44         // Attempt to initialize the invoker once on class-load. If it fails, it will not be attempted again, but we'll
45         // use that opportunity to log a warning.
46         NORMALIZE_URI_INVOKER =
47             new ReflectionMethodInvoker<>(RequestConfig.Builder.class,
48                                           RequestConfig.Builder.class,
49                                           "setNormalizeUri",
50                                           boolean.class);
51 
52         try {
NORMALIZE_URI_INVOKER.initialize()53             NORMALIZE_URI_INVOKER.initialize();
54         } catch (NoSuchMethodException ignored) {
55             noSuchMethodThrownByNormalizeUriInvoker();
56         }
57     }
58 
ApacheUtils()59     private ApacheUtils() {
60     }
61 
62     /**
63      * Utility function for creating a new BufferedEntity and wrapping any errors
64      * as a SdkClientException.
65      *
66      * @param entity The HTTP entity to wrap with a buffered HTTP entity.
67      * @return A new BufferedHttpEntity wrapping the specified entity.
68      */
newBufferedHttpEntity(HttpEntity entity)69     public static HttpEntity newBufferedHttpEntity(HttpEntity entity) {
70         try {
71             return new BufferedHttpEntity(entity);
72         } catch (IOException e) {
73             throw new UncheckedIOException("Unable to create HTTP entity: " + e.getMessage(), e);
74         }
75     }
76 
77     /**
78      * Returns a new HttpClientContext used for request execution.
79      */
newClientContext(ProxyConfiguration proxyConfiguration)80     public static HttpClientContext newClientContext(ProxyConfiguration proxyConfiguration) {
81         HttpClientContext clientContext = new HttpClientContext();
82         addPreemptiveAuthenticationProxy(clientContext, proxyConfiguration);
83 
84         RequestConfig.Builder builder = RequestConfig.custom();
85         disableNormalizeUri(builder);
86 
87         clientContext.setRequestConfig(builder.build());
88         return clientContext;
89 
90     }
91 
92     /**
93      * From Apache v4.5.8, normalization should be disabled or AWS requests with special characters in URI path will fail
94      * with Signature Errors.
95      * <p>
96      *    setNormalizeUri is added only in 4.5.8, so customers using the latest version of SDK with old versions (4.5.6 or less)
97      *    of Apache httpclient will see NoSuchMethodError. Hence this method will suppress the error.
98      *
99      *    Do not use Apache version 4.5.7 as it breaks URI paths with special characters and there is no option
100      *    to disable normalization.
101      * </p>
102      *
103      * For more information, See https://github.com/aws/aws-sdk-java/issues/1919
104      */
disableNormalizeUri(RequestConfig.Builder requestConfigBuilder)105     public static void disableNormalizeUri(RequestConfig.Builder requestConfigBuilder) {
106         // For efficiency, do not attempt to call the invoker again if it failed to initialize on class-load
107         if (NORMALIZE_URI_INVOKER.isInitialized()) {
108             try {
109                 NORMALIZE_URI_INVOKER.invoke(requestConfigBuilder, false);
110             } catch (NoSuchMethodException ignored) {
111                 noSuchMethodThrownByNormalizeUriInvoker();
112             }
113         }
114     }
115 
116     /**
117      * Returns a new Credentials Provider for use with proxy authentication.
118      */
newProxyCredentialsProvider(ProxyConfiguration proxyConfiguration)119     public static CredentialsProvider newProxyCredentialsProvider(ProxyConfiguration proxyConfiguration) {
120         CredentialsProvider provider = new BasicCredentialsProvider();
121         provider.setCredentials(newAuthScope(proxyConfiguration), newNtCredentials(proxyConfiguration));
122         return provider;
123     }
124 
125     /**
126      * Returns a new instance of NTCredentials used for proxy authentication.
127      */
newNtCredentials(ProxyConfiguration proxyConfiguration)128     private static Credentials newNtCredentials(ProxyConfiguration proxyConfiguration) {
129         return new NTCredentials(proxyConfiguration.username(),
130                                  proxyConfiguration.password(),
131                                  proxyConfiguration.ntlmWorkstation(),
132                                  proxyConfiguration.ntlmDomain());
133     }
134 
135     /**
136      * Returns a new instance of AuthScope used for proxy authentication.
137      */
newAuthScope(ProxyConfiguration proxyConfiguration)138     private static AuthScope newAuthScope(ProxyConfiguration proxyConfiguration) {
139         return new AuthScope(proxyConfiguration.host(), proxyConfiguration.port());
140     }
141 
addPreemptiveAuthenticationProxy(HttpClientContext clientContext, ProxyConfiguration proxyConfiguration)142     private static void addPreemptiveAuthenticationProxy(HttpClientContext clientContext,
143                                                          ProxyConfiguration proxyConfiguration) {
144 
145         if (proxyConfiguration.preemptiveBasicAuthenticationEnabled()) {
146             HttpHost targetHost = new HttpHost(proxyConfiguration.host(), proxyConfiguration.port());
147             CredentialsProvider credsProvider = newProxyCredentialsProvider(proxyConfiguration);
148             // Create AuthCache instance
149             AuthCache authCache = new BasicAuthCache();
150             // Generate BASIC scheme object and add it to the local auth cache
151             BasicScheme basicAuth = new BasicScheme();
152             authCache.put(targetHost, basicAuth);
153 
154             clientContext.setCredentialsProvider(credsProvider);
155             clientContext.setAuthCache(authCache);
156         }
157     }
158 
159     // Just log and then swallow the exception
noSuchMethodThrownByNormalizeUriInvoker()160     private static void noSuchMethodThrownByNormalizeUriInvoker() {
161         // setNormalizeUri method was added in httpclient 4.5.8
162         logger.warn(() -> "NoSuchMethodException was thrown when disabling normalizeUri. This indicates you are using "
163                  + "an old version (< 4.5.8) of Apache http client. It is recommended to use http client "
164                  + "version >= 4.5.9 to avoid the breaking change introduced in apache client 4.5.7 and "
165                  + "the latency in exception handling. See https://github.com/aws/aws-sdk-java/issues/1919"
166                  + " for more information");
167     }
168 }
169