All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.core.http.ProxyOptions Maven / Gradle / Ivy

There is a newer version: 1.54.1
Show newest version
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.http;

import com.azure.core.implementation.ImplUtils;
import com.azure.core.util.Configuration;
import com.azure.core.util.ConfigurationProperty;
import com.azure.core.util.ConfigurationPropertyBuilder;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * 

Represents the proxy configuration to be used in HTTP clients.

* *

This class encapsulates the proxy settings, including the proxy type, address, and optional credentials. It * provides methods to set and get these properties.

* *

This class is useful when you want to configure a proxy for an HTTP client. For example, you can use it to * create a proxy with specific credentials, or to specify hosts that should bypass the proxy.

* *

Note: This class provides a {@link Type} enum to represent the proxy type, which can be HTTP, SOCKS4, or SOCKS5.

* * @see Type * @see InetSocketAddress */ public class ProxyOptions { private static final ClientLogger LOGGER = new ClientLogger(ProxyOptions.class); private static final String INVALID_AZURE_PROXY_URL = "URL is invalid and is being ignored."; /* * This indicates whether system proxy configurations (HTTPS_PROXY, HTTP_PROXY) are allowed to be used. */ private static final String JAVA_SYSTEM_PROXY_PREREQUISITE = "java.net.useSystemProxies"; /* * Java environment variables related to proxies. The protocol is removed since these are the same for 'https' and * 'http', the exception is 'http.nonProxyHosts' as it is used for both. */ private static final String JAVA_PROXY_HOST = "proxyHost"; private static final String JAVA_PROXY_PORT = "proxyPort"; private static final String JAVA_PROXY_USER = "proxyUser"; private static final String JAVA_PROXY_PASSWORD = "proxyPassword"; private static final String JAVA_NON_PROXY_HOSTS = "http.nonProxyHosts"; private static final String HTTPS = "https"; private static final int DEFAULT_HTTPS_PORT = 443; private static final String HTTP = "http"; private static final int DEFAULT_HTTP_PORT = 80; /* * The 'http.nonProxyHosts' system property is expected to be delimited by '|', but don't split escaped '|'s. */ private static final Pattern HTTP_NON_PROXY_HOSTS_SPLIT = Pattern.compile("(? NON_PROXY_PROPERTY = ConfigurationPropertyBuilder.ofString(ConfigurationProperties.HTTP_PROXY_NON_PROXY_HOSTS) .shared(true) .logValue(true) .build(); private static final ConfigurationProperty HOST_PROPERTY = ConfigurationPropertyBuilder.ofString(ConfigurationProperties.HTTP_PROXY_HOST) .shared(true) .logValue(true) .build(); private static final ConfigurationProperty PORT_PROPERTY = ConfigurationPropertyBuilder.ofInteger(ConfigurationProperties.HTTP_PROXY_PORT) .shared(true) .defaultValue(DEFAULT_HTTPS_PORT) .build(); private static final ConfigurationProperty USER_PROPERTY = ConfigurationPropertyBuilder.ofString(ConfigurationProperties.HTTP_PROXY_USER) .shared(true) .logValue(true) .build(); private static final ConfigurationProperty PASSWORD_PROPERTY = ConfigurationPropertyBuilder.ofString(ConfigurationProperties.HTTP_PROXY_PASSWORD).shared(true).build(); private final InetSocketAddress address; private final Type type; private String username; private String password; private String nonProxyHosts; /** * Creates ProxyOptions. * * @param type the proxy type * @param address the proxy address (ip and port number) */ public ProxyOptions(Type type, InetSocketAddress address) { this.type = type; this.address = address; } /** * Set the proxy credentials. * * @param username proxy user name * @param password proxy password * @return the updated ProxyOptions object */ public ProxyOptions setCredentials(String username, String password) { this.username = Objects.requireNonNull(username, "'username' cannot be null."); this.password = Objects.requireNonNull(password, "'password' cannot be null."); return this; } /** * Sets the hosts which bypass the proxy. *

* The expected format of the passed string is a {@code '|'} delimited list of hosts which should bypass the proxy. * Individual host strings may contain regex characters such as {@code '*'}. * * @param nonProxyHosts Hosts that bypass the proxy. * @return the updated ProxyOptions object */ public ProxyOptions setNonProxyHosts(String nonProxyHosts) { this.nonProxyHosts = sanitizeJavaHttpNonProxyHosts(nonProxyHosts); return this; } /** * Gets the address of the proxy. * * @return the address of the proxy. */ public InetSocketAddress getAddress() { return address; } /** * Gets the type of the proxy. * * @return the type of the proxy. */ public Type getType() { return type; } /** * Gets the proxy username. * * @return the proxy username. */ public String getUsername() { return this.username; } /** * Gets the proxy password. * * @return the proxy password. */ public String getPassword() { return this.password; } /** * Gets the host that bypass the proxy. * * @return the hosts that bypass the proxy. */ public String getNonProxyHosts() { return this.nonProxyHosts; } /** * Attempts to load a proxy from the configuration. *

* If a proxy is found and loaded the proxy address is DNS resolved. *

* Environment configurations are loaded in this order: *

    *
  1. Azure HTTPS
  2. *
  3. Azure HTTP
  4. *
  5. Java HTTPS
  6. *
  7. Java HTTP
  8. *
* * Azure proxy configurations will be preferred over Java proxy configurations as they are more closely scoped to * the purpose of the SDK. Additionally, more secure protocols, HTTPS vs HTTP, will be preferred. * *

* {@code null} will be returned if no proxy was found in the environment. * * @param configuration The {@link Configuration} that is used to load proxy configurations from the environment. If * {@code null} is passed then {@link Configuration#getGlobalConfiguration()} will be used. * @return A {@link ProxyOptions} reflecting a proxy loaded from the environment, if no proxy is found {@code null} * will be returned. */ public static ProxyOptions fromConfiguration(Configuration configuration) { return fromConfiguration(configuration, false); } /** * Attempts to load a proxy from the environment. *

* If a proxy is found and loaded, the proxy address is DNS resolved based on {@code createUnresolved}. When {@code * createUnresolved} is true resolving {@link #getAddress()} may be required before using the address in network * calls. *

* Environment configurations are loaded in this order: *

    *
  1. Azure HTTPS
  2. *
  3. Azure HTTP
  4. *
  5. Java HTTPS
  6. *
  7. Java HTTP
  8. *
* * Azure proxy configurations will be preferred over Java proxy configurations as they are more closely scoped to * the purpose of the SDK. Additionally, more secure protocols, HTTPS vs HTTP, will be preferred. *

* {@code null} will be returned if no proxy was found in the environment. * * @param configuration The {@link Configuration} that is used to load proxy configurations from the environment. If * {@code null} is passed then {@link Configuration#getGlobalConfiguration()} will be used. If {@link * Configuration#NONE} is passed {@link IllegalArgumentException} will be thrown. * @param createUnresolved Flag determining whether the returned {@link ProxyOptions} is unresolved. * @return A {@link ProxyOptions} reflecting a proxy loaded from the environment, if no proxy is found {@code null} * will be returned. */ public static ProxyOptions fromConfiguration(Configuration configuration, boolean createUnresolved) { Configuration proxyConfiguration = (configuration == null) ? Configuration.getGlobalConfiguration() : configuration; return attemptToLoadProxy(proxyConfiguration, createUnresolved); } private static ProxyOptions attemptToLoadProxy(Configuration configuration, boolean createUnresolved) { if (configuration == Configuration.NONE) { return null; } ProxyOptions proxyOptions = null; // System proxy configuration is only possible through system properties. // Only use system proxies when the prerequisite property is 'true'. if (Boolean.parseBoolean(configuration.get(JAVA_SYSTEM_PROXY_PREREQUISITE))) { proxyOptions = attemptToLoadSystemProxy(configuration, createUnresolved, Configuration.PROPERTY_HTTPS_PROXY); if (proxyOptions != null) { LOGGER.verbose("Using proxy created from HTTPS_PROXY environment variable."); return proxyOptions; } proxyOptions = attemptToLoadSystemProxy(configuration, createUnresolved, Configuration.PROPERTY_HTTP_PROXY); if (proxyOptions != null) { LOGGER.verbose("Using proxy created from HTTP_PROXY environment variable."); return proxyOptions; } } proxyOptions = attemptToLoadAzureSdkProxy(configuration, createUnresolved); if (proxyOptions != null) { return proxyOptions; } proxyOptions = attemptToLoadJavaProxy(configuration, createUnresolved, HTTPS); if (proxyOptions != null) { LOGGER.verbose("Using proxy created from JVM HTTPS system properties."); return proxyOptions; } proxyOptions = attemptToLoadJavaProxy(configuration, createUnresolved, HTTP); if (proxyOptions != null) { LOGGER.verbose("Using proxy created from JVM HTTP system properties."); return proxyOptions; } return null; } private static ProxyOptions attemptToLoadSystemProxy(Configuration configuration, boolean createUnresolved, String proxyProperty) { String proxyConfiguration = configuration.get(proxyProperty); // No proxy configuration setup. if (CoreUtils.isNullOrEmpty(proxyConfiguration)) { return null; } try { // TODO (alzimmer): UrlBuilder needs to add support for userinfo // https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1 URL proxyUrl = ImplUtils.createUrl(proxyConfiguration); int port = (proxyUrl.getPort() == -1) ? proxyUrl.getDefaultPort() : proxyUrl.getPort(); InetSocketAddress socketAddress = (createUnresolved) ? InetSocketAddress.createUnresolved(proxyUrl.getHost(), port) : new InetSocketAddress(proxyUrl.getHost(), port); ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, socketAddress); String nonProxyHostsString = configuration.get(Configuration.PROPERTY_NO_PROXY); if (!CoreUtils.isNullOrEmpty(nonProxyHostsString)) { proxyOptions.nonProxyHosts = sanitizeNoProxy(nonProxyHostsString); LOGGER.atVerbose().addKeyValue("regex", proxyOptions.nonProxyHosts).log("Using non-proxy hosts"); } String userInfo = proxyUrl.getUserInfo(); if (userInfo != null) { String[] usernamePassword = userInfo.split(":", 2); if (usernamePassword.length == 2) { try { proxyOptions.setCredentials( URLDecoder.decode(usernamePassword[0], StandardCharsets.UTF_8.toString()), URLDecoder.decode(usernamePassword[1], StandardCharsets.UTF_8.toString())); } catch (UnsupportedEncodingException e) { return null; } } } return proxyOptions; } catch (MalformedURLException ex) { LOGGER.atWarning().addKeyValue("url", proxyProperty).log(INVALID_AZURE_PROXY_URL); return null; } } /* * Helper function that sanitizes 'NO_PROXY' into a Pattern safe string. */ static String sanitizeNoProxy(String noProxyString) { return sanitizeNonProxyHosts(NO_PROXY_SPLIT.split(noProxyString)); } private static ProxyOptions attemptToLoadJavaProxy(Configuration configuration, boolean createUnresolved, String type) { String host = configuration.get(type + "." + JAVA_PROXY_HOST); // No proxy configuration setup. if (CoreUtils.isNullOrEmpty(host)) { return null; } int port; try { port = Integer.parseInt(configuration.get(type + "." + JAVA_PROXY_PORT)); } catch (NumberFormatException ex) { port = HTTPS.equals(type) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT; } String nonProxyHostsString = configuration.get(JAVA_NON_PROXY_HOSTS); String username = configuration.get(type + "." + JAVA_PROXY_USER); String password = configuration.get(type + "." + JAVA_PROXY_PASSWORD); return createOptions(host, port, nonProxyHostsString, username, password, createUnresolved); } private static ProxyOptions attemptToLoadAzureSdkProxy(Configuration configuration, boolean createUnresolved) { String host = configuration.get(HOST_PROPERTY); // No proxy configuration setup. if (CoreUtils.isNullOrEmpty(host)) { return null; } int port = configuration.get(PORT_PROPERTY); String nonProxyHostsString = configuration.get(NON_PROXY_PROPERTY); String username = configuration.get(USER_PROPERTY); String password = configuration.get(PASSWORD_PROPERTY); return createOptions(host, port, nonProxyHostsString, username, password, createUnresolved); } private static ProxyOptions createOptions(String host, int port, String nonProxyHostsString, String username, String password, boolean createUnresolved) { InetSocketAddress socketAddress = (createUnresolved) ? InetSocketAddress.createUnresolved(host, port) : new InetSocketAddress(host, port); ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, socketAddress); if (!CoreUtils.isNullOrEmpty(nonProxyHostsString)) { proxyOptions.nonProxyHosts = sanitizeJavaHttpNonProxyHosts(nonProxyHostsString); LOGGER.atVerbose().addKeyValue("regex", proxyOptions.nonProxyHosts).log("Using non-proxy host regex"); } if (username != null && password != null) { proxyOptions.setCredentials(username, password); } return proxyOptions; } /* * Helper function that sanitizes 'http.nonProxyHosts' into a Pattern safe string. */ static String sanitizeJavaHttpNonProxyHosts(String nonProxyHostsString) { return sanitizeNonProxyHosts(HTTP_NON_PROXY_HOSTS_SPLIT.split(nonProxyHostsString)); } private static String sanitizeNonProxyHosts(String[] nonProxyHosts) { StringBuilder sanitizedBuilder = new StringBuilder(); for (int i = 0; i < nonProxyHosts.length; i++) { if (i > 0) { sanitizedBuilder.append("|"); } String prefixWildcard = ""; String suffixWildcard = ""; String sanitizedNonProxyHost = nonProxyHosts[i]; /* * If the non-proxy host begins with either '.', '*', '.*', or any of the previous with a trailing '?' * substring the non-proxy host and set the wildcard prefix. */ if (sanitizedNonProxyHost.startsWith(".")) { prefixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(1); } else if (sanitizedNonProxyHost.startsWith(".?")) { prefixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(2); } else if (sanitizedNonProxyHost.startsWith("*?")) { prefixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(2); } else if (sanitizedNonProxyHost.startsWith("*")) { prefixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(1); } else if (sanitizedNonProxyHost.startsWith(".*?")) { prefixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(3); } else if (sanitizedNonProxyHost.startsWith(".*")) { prefixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(2); } /* * Same with the ending of the non-proxy host, if it has a suffix wildcard trim the non-proxy host and * retain the suffix wildcard. */ if (sanitizedNonProxyHost.endsWith(".")) { suffixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(0, sanitizedNonProxyHost.length() - 2); } else if (sanitizedNonProxyHost.endsWith(".?")) { suffixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(0, sanitizedNonProxyHost.length() - 3); } else if (sanitizedNonProxyHost.endsWith("*?")) { suffixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(0, sanitizedNonProxyHost.length() - 3); } else if (sanitizedNonProxyHost.endsWith("*")) { suffixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(0, sanitizedNonProxyHost.length() - 2); } else if (sanitizedNonProxyHost.endsWith(".*?")) { suffixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(0, sanitizedNonProxyHost.length() - 4); } else if (sanitizedNonProxyHost.endsWith(".*")) { suffixWildcard = ".*?"; sanitizedNonProxyHost = sanitizedNonProxyHost.substring(0, sanitizedNonProxyHost.length() - 3); } try { // Sanitize the non-proxy host before any appending to prevent errant characters being added to the // final response if the non-proxy host isn't a valid Pattern. String attemptToSanitizeAsRegex = sanitizedNonProxyHost; attemptToSanitizeAsRegex = UNESCAPED_PERIOD.matcher(attemptToSanitizeAsRegex).replaceAll("\\\\."); attemptToSanitizeAsRegex = ANY.matcher(attemptToSanitizeAsRegex).replaceAll("\\.*?"); sanitizedNonProxyHost = Pattern.compile(attemptToSanitizeAsRegex).pattern(); } catch (PatternSyntaxException ex) { /* * Replace the non-proxy host with the sanitized value. * * The body of the non-proxy host is quoted to handle scenarios such a '127.0.0.1' or '*.azure.com' * where without quoting the '.' in the string would be treated as the match any character instead of * the literal '.' character. */ sanitizedNonProxyHost = Pattern.quote(sanitizedNonProxyHost); } sanitizedBuilder.append("(") .append(prefixWildcard) .append(sanitizedNonProxyHost) .append(suffixWildcard) .append(")"); } return sanitizedBuilder.toString(); } /** * The type of the proxy. */ public enum Type { /** * HTTP proxy type. */ HTTP(Proxy.Type.HTTP), /** * SOCKS4 proxy type. */ SOCKS4(Proxy.Type.SOCKS), /** * SOCKS5 proxy type. */ SOCKS5(Proxy.Type.SOCKS); private final Proxy.Type proxyType; Type(Proxy.Type proxyType) { this.proxyType = proxyType; } /** * Get the {@link Proxy.Type} equivalent of this type. * * @return the proxy type */ public Proxy.Type toProxyType() { return proxyType; } } /** * Lists available configuration property names for HTTP {@link ProxyOptions}. */ private static class ConfigurationProperties { /** * Represents a list of hosts that should be reached directly, bypassing the proxy. * This is a list of patterns separated by '|'. The patterns may start or end with a '*' for wildcards. * Any host matching one of these patterns will be reached through a direct connection instead of through a proxy. *

* Default value is {@code null} */ public static final String HTTP_PROXY_NON_PROXY_HOSTS = "http.proxy.non-proxy-hosts"; /** * The HTTP host name of the proxy server. *

* Default value is {@code null}. */ public static final String HTTP_PROXY_HOST = "http.proxy.hostname"; /** * The port number of the proxy server. *

* Default value is {@code 443}. */ public static final String HTTP_PROXY_PORT = "http.proxy.port"; /** * The HTTP proxy server user. * Default value is {@code null}. */ public static final String HTTP_PROXY_USER = "http.proxy.username"; /** * The HTTP proxy server password. * Default value is {@code null}. */ public static final String HTTP_PROXY_PASSWORD = "http.proxy.password"; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy