/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.awssdk.http.apache; import static software.amazon.awssdk.utils.StringUtils.isEmpty; import static software.amazon.awssdk.utils.http.SdkHttpUtils.parseNonProxyHostsProperty; import java.net.URI; import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.utils.ProxySystemSetting; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; /** * Configuration that defines how to communicate via an HTTP or HTTPS proxy. */ @SdkPublicApi public final class ProxyConfiguration implements ToCopyableBuilder { private static final String HTTPS = "https"; private final URI endpoint; private final String username; private final String password; private final String ntlmDomain; private final String ntlmWorkstation; private final Set nonProxyHosts; private final Boolean preemptiveBasicAuthenticationEnabled; private final Boolean useSystemPropertyValues; private final String host; private final int port; private final String scheme; /** * Initialize this configuration. Private to require use of {@link #builder()}. */ private ProxyConfiguration(DefaultClientProxyConfigurationBuilder builder) { this.endpoint = builder.endpoint; this.username = builder.username; this.password = builder.password; this.ntlmDomain = builder.ntlmDomain; this.ntlmWorkstation = builder.ntlmWorkstation; this.nonProxyHosts = builder.nonProxyHosts; this.preemptiveBasicAuthenticationEnabled = builder.preemptiveBasicAuthenticationEnabled == null ? Boolean.FALSE : builder.preemptiveBasicAuthenticationEnabled; this.useSystemPropertyValues = builder.useSystemPropertyValues; this.scheme = resolveScheme(); this.host = resolveHost(scheme); this.port = resolvePort(scheme); } /** * Returns the proxy host name from the configured endpoint if set, else from the "https.proxyHost" or "http.proxyHost" system * property, based on the scheme used, if {@link Builder#useSystemPropertyValues(Boolean)} is set to true. */ public String host() { return host; } /** * Returns the proxy port from the configured endpoint if set, else from the "https.proxyPort" or "http.proxyPort" system * property, based on the scheme used, if {@link Builder#useSystemPropertyValues(Boolean)} is set to true. * If no value is found in none of the above options, the default value of 0 is returned. */ public int port() { return port; } /** * Returns the {@link URI#scheme} from the configured endpoint. Otherwise return null. */ public String scheme() { return scheme; } /** * The username to use when connecting through a proxy. * * @see Builder#password(String) */ public String username() { if (Objects.equals(scheme(), HTTPS)) { return resolveValue(username, ProxySystemSetting.HTTPS_PROXY_USERNAME); } return resolveValue(username, ProxySystemSetting.PROXY_USERNAME); } /** * The password to use when connecting through a proxy. * * @see Builder#password(String) */ public String password() { if (Objects.equals(scheme(), HTTPS)) { return resolveValue(password, ProxySystemSetting.HTTPS_PROXY_PASSWORD); } return resolveValue(password, ProxySystemSetting.PROXY_PASSWORD); } /** * For NTLM proxies: The Windows domain name to use when authenticating with the proxy. * * @see Builder#ntlmDomain(String) */ public String ntlmDomain() { return ntlmDomain; } /** * For NTLM proxies: The Windows workstation name to use when authenticating with the proxy. * * @see Builder#ntlmWorkstation(String) */ public String ntlmWorkstation() { return ntlmWorkstation; } /** * The hosts that the client is allowed to access without going through the proxy. * If the value is not set on the object, the value represent by "http.nonProxyHosts" system property is returned. * If system property is also not set, an unmodifiable empty set is returned. * * @see Builder#nonProxyHosts(Set) */ public Set nonProxyHosts() { Set hosts = nonProxyHosts == null && useSystemPropertyValues ? parseNonProxyHostsProperty() : nonProxyHosts; return Collections.unmodifiableSet(hosts != null ? hosts : Collections.emptySet()); } /** * Whether to attempt to authenticate preemptively against the proxy server using basic authentication. * * @see Builder#preemptiveBasicAuthenticationEnabled(Boolean) */ public Boolean preemptiveBasicAuthenticationEnabled() { return preemptiveBasicAuthenticationEnabled; } @Override public Builder toBuilder() { return builder() .endpoint(endpoint) .username(username) .password(password) .ntlmDomain(ntlmDomain) .ntlmWorkstation(ntlmWorkstation) .nonProxyHosts(nonProxyHosts) .preemptiveBasicAuthenticationEnabled(preemptiveBasicAuthenticationEnabled) .useSystemPropertyValues(useSystemPropertyValues); } /** * Create a {@link Builder}, used to create a {@link ProxyConfiguration}. */ public static Builder builder() { return new DefaultClientProxyConfigurationBuilder(); } @Override public String toString() { return ToString.builder("ProxyConfiguration") .add("endpoint", endpoint) .add("username", username) .add("ntlmDomain", ntlmDomain) .add("ntlmWorkstation", ntlmWorkstation) .add("nonProxyHosts", nonProxyHosts) .add("preemptiveBasicAuthenticationEnabled", preemptiveBasicAuthenticationEnabled) .build(); } private String resolveHost(String scheme) { if (endpoint != null) { return endpoint.getHost(); } if (Objects.equals(scheme, HTTPS)) { return resolveValue(null, ProxySystemSetting.HTTPS_PROXY_HOST); } return resolveValue(null, ProxySystemSetting.PROXY_HOST); } private int resolvePort(String scheme) { int port = 0; if (endpoint != null) { port = endpoint.getPort(); } else if (useSystemPropertyValues) { if (Objects.equals(scheme, HTTPS)) { port = ProxySystemSetting.HTTPS_PROXY_PORT.getStringValue() .map(Integer::parseInt) .orElse(0); } else { port = ProxySystemSetting.PROXY_PORT.getStringValue() .map(Integer::parseInt) .orElse(0); } } return port; } public String resolveScheme() { return endpoint != null ? endpoint.getScheme() : null; } /** * Uses the configuration options, system setting property and returns the final value of the given member. */ private String resolveValue(String value, ProxySystemSetting systemSetting) { return value == null && useSystemPropertyValues ? systemSetting.getStringValue().orElse(null) : value; } /** * A builder for {@link ProxyConfiguration}. * *

All implementations of this interface are mutable and not thread safe.

*/ public interface Builder extends CopyableBuilder { /** * Configure the endpoint of the proxy server that the SDK should connect through. Currently, the endpoint is limited to * a host and port. Any other URI components will result in an exception being raised. */ Builder endpoint(URI endpoint); /** * Configure the username to use when connecting through a proxy. */ Builder username(String username); /** * Configure the password to use when connecting through a proxy. */ Builder password(String password); /** * For NTLM proxies: Configure the Windows domain name to use when authenticating with the proxy. */ Builder ntlmDomain(String proxyDomain); /** * For NTLM proxies: Configure the Windows workstation name to use when authenticating with the proxy. */ Builder ntlmWorkstation(String proxyWorkstation); /** * Configure the hosts that the client is allowed to access without going through the proxy. */ Builder nonProxyHosts(Set nonProxyHosts); /** * Add a host that the client is allowed to access without going through the proxy. * * @see ProxyConfiguration#nonProxyHosts() */ Builder addNonProxyHost(String nonProxyHost); /** * Configure whether to attempt to authenticate pre-emptively against the proxy server using basic authentication. */ Builder preemptiveBasicAuthenticationEnabled(Boolean preemptiveBasicAuthenticationEnabled); /** * Option whether to use system property values from {@link ProxySystemSetting} if any of the config options are missing. * * This value is set to "true" by default which means SDK will automatically use system property values * for options that are not provided during building the {@link ProxyConfiguration} object. To disable this behavior, * set this value to "false". */ Builder useSystemPropertyValues(Boolean useSystemPropertyValues); } /** * An SDK-internal implementation of {@link Builder}. */ private static final class DefaultClientProxyConfigurationBuilder implements Builder { private URI endpoint; private String username; private String password; private String ntlmDomain; private String ntlmWorkstation; private Set nonProxyHosts; private Boolean preemptiveBasicAuthenticationEnabled; private Boolean useSystemPropertyValues = Boolean.TRUE; @Override public Builder endpoint(URI endpoint) { if (endpoint != null) { Validate.isTrue(isEmpty(endpoint.getUserInfo()), "Proxy endpoint user info is not supported."); Validate.isTrue(isEmpty(endpoint.getPath()), "Proxy endpoint path is not supported."); Validate.isTrue(isEmpty(endpoint.getQuery()), "Proxy endpoint query is not supported."); Validate.isTrue(isEmpty(endpoint.getFragment()), "Proxy endpoint fragment is not supported."); } this.endpoint = endpoint; return this; } public void setEndpoint(URI endpoint) { endpoint(endpoint); } @Override public Builder username(String username) { this.username = username; return this; } public void setUsername(String username) { username(username); } @Override public Builder password(String password) { this.password = password; return this; } public void setPassword(String password) { password(password); } @Override public Builder ntlmDomain(String proxyDomain) { this.ntlmDomain = proxyDomain; return this; } public void setNtlmDomain(String ntlmDomain) { ntlmDomain(ntlmDomain); } @Override public Builder ntlmWorkstation(String proxyWorkstation) { this.ntlmWorkstation = proxyWorkstation; return this; } public void setNtlmWorkstation(String ntlmWorkstation) { ntlmWorkstation(ntlmWorkstation); } @Override public Builder nonProxyHosts(Set nonProxyHosts) { this.nonProxyHosts = nonProxyHosts != null ? new HashSet<>(nonProxyHosts) : null; return this; } @Override public Builder addNonProxyHost(String nonProxyHost) { if (this.nonProxyHosts == null) { this.nonProxyHosts = new HashSet<>(); } this.nonProxyHosts.add(nonProxyHost); return this; } public void setNonProxyHosts(Set nonProxyHosts) { nonProxyHosts(nonProxyHosts); } @Override public Builder preemptiveBasicAuthenticationEnabled(Boolean preemptiveBasicAuthenticationEnabled) { this.preemptiveBasicAuthenticationEnabled = preemptiveBasicAuthenticationEnabled; return this; } public void setPreemptiveBasicAuthenticationEnabled(Boolean preemptiveBasicAuthenticationEnabled) { preemptiveBasicAuthenticationEnabled(preemptiveBasicAuthenticationEnabled); } @Override public Builder useSystemPropertyValues(Boolean useSystemPropertyValues) { this.useSystemPropertyValues = useSystemPropertyValues; return this; } public void setUseSystemPropertyValues(Boolean useSystemPropertyValues) { useSystemPropertyValues(useSystemPropertyValues); } @Override public ProxyConfiguration build() { return new ProxyConfiguration(this); } } }