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