diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java index 541c8364ed8e..b0c7d420811d 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java @@ -18,6 +18,8 @@ */ package org.apache.maven.internal.aether; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -27,6 +29,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -212,6 +215,7 @@ public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) sessionBuilder.setMirrorSelector(mirrorSelector); DefaultProxySelector proxySelector = new DefaultProxySelector(); + Set coveredProtocols = new HashSet<>(); for (Proxy proxy : request.getProxies()) { AuthenticationBuilder authBuilder = new AuthenticationBuilder(); authBuilder.addUsername(proxy.getUsername()).addPassword(proxy.getPassword()); @@ -219,7 +223,9 @@ public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) new org.eclipse.aether.repository.Proxy( proxy.getProtocol(), proxy.getHost(), proxy.getPort(), authBuilder.build()), proxy.getNonProxyHosts()); + coveredProtocols.add(proxy.getProtocol()); } + addProxiesFromEnvironment(proxySelector, coveredProtocols, mergedProps); sessionBuilder.setProxySelector(proxySelector); // Note: we do NOT use WagonTransportConfigurationKeys here as Maven Core does NOT depend on Wagon Transport @@ -509,6 +515,73 @@ private Map getPropertiesFromRequestedProfiles(MavenExecutionReq e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue()), (k1, k2) -> k2)); } + private void addProxiesFromEnvironment( + DefaultProxySelector proxySelector, Set coveredProtocols, Map mergedProps) { + // Convert NO_PROXY comma-separated list to Maven's pipe-separated nonProxyHosts + String noProxy = getEnvVar(mergedProps, "NO_PROXY", "no_proxy"); + String nonProxyHosts = noProxy != null ? noProxy.replace(",", "|") : null; + + if (!coveredProtocols.contains("http")) { + String httpProxy = getEnvVar(mergedProps, "HTTP_PROXY", "http_proxy"); + if (httpProxy == null) { + httpProxy = getEnvVar(mergedProps, "ALL_PROXY", "all_proxy"); + } + if (httpProxy != null) { + addProxyFromUrl(proxySelector, "http", httpProxy, nonProxyHosts); + } + } + + if (!coveredProtocols.contains("https")) { + String httpsProxy = getEnvVar(mergedProps, "HTTPS_PROXY", "https_proxy"); + if (httpsProxy == null) { + httpsProxy = getEnvVar(mergedProps, "ALL_PROXY", "all_proxy"); + } + if (httpsProxy != null) { + addProxyFromUrl(proxySelector, "https", httpsProxy, nonProxyHosts); + } + } + } + + /** Returns env var value: prefers lowercase key (Unix curl convention), falls back to uppercase. */ + private String getEnvVar(Map mergedProps, String upperCase, String lowerCase) { + String value = mergedProps.get("env." + lowerCase); + if (value == null) { + value = mergedProps.get("env." + upperCase); + } + return value; + } + + private void addProxyFromUrl( + DefaultProxySelector proxySelector, String protocol, String proxyUrl, String nonProxyHosts) { + try { + URI uri = new URI(proxyUrl); + String host = uri.getHost(); + if (host == null || host.isEmpty()) { + logger.warn("Ignoring invalid proxy URL from environment variable: {}", proxyUrl); + return; + } + int port = uri.getPort(); + if (port < 0) { + port = "https".equals(protocol) ? 443 : 80; + } + AuthenticationBuilder authBuilder = new AuthenticationBuilder(); + String userInfo = uri.getUserInfo(); + if (userInfo != null) { + int colonIdx = userInfo.indexOf(':'); + if (colonIdx >= 0) { + authBuilder.addUsername(userInfo.substring(0, colonIdx)); + authBuilder.addPassword(userInfo.substring(colonIdx + 1)); + } else { + authBuilder.addUsername(userInfo); + } + } + proxySelector.add( + new org.eclipse.aether.repository.Proxy(protocol, host, port, authBuilder.build()), nonProxyHosts); + } catch (URISyntaxException e) { + logger.warn("Failed to parse proxy URL from environment variable: {}", proxyUrl, e); + } + } + private String getUserAgent() { String version = runtimeInformation.getMavenVersion(); version = version.isEmpty() ? version : "/" + version; diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactoryTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactoryTest.java index 8b80dc0efd60..d0b1ac20d5bf 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactoryTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactoryTest.java @@ -34,13 +34,16 @@ import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.internal.impl.DefaultTypeRegistry; +import org.apache.maven.properties.internal.EnvironmentUtils; import org.apache.maven.rtinfo.RuntimeInformation; import org.apache.maven.settings.Server; import org.codehaus.plexus.configuration.PlexusConfiguration; import org.codehaus.plexus.testing.PlexusTest; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.eclipse.aether.ConfigurationProperties; +import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.collection.VersionFilter; +import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.util.graph.version.ChainedVersionFilter; import org.eclipse.aether.util.graph.version.ContextualSnapshotVersionFilter; @@ -48,6 +51,7 @@ import org.eclipse.aether.util.graph.version.LowestVersionFilter; import org.eclipse.aether.util.graph.version.PredicateVersionFilter; import org.eclipse.aether.version.VersionScheme; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import static org.codehaus.plexus.testing.PlexusExtension.getBasedir; @@ -448,6 +452,120 @@ void versionFilteringTest() throws InvalidRepositoryException { assertInstanceOf(ChainedVersionFilter.class, versionFilter); } + @Test + void proxyFromEnvVarsTest() throws InvalidRepositoryException { + DefaultRepositorySystemSessionFactory systemSessionFactory = new DefaultRepositorySystemSessionFactory( + aetherRepositorySystem, + eventSpyDispatcher, + information, + defaultTypeRegistry, + versionScheme, + Collections.emptyMap()); + + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + request.setLocalRepository(getLocalRepository()); + + Properties systemProps = new Properties(); + systemProps.setProperty("env.HTTP_PROXY", "http://proxy.example.com:3128"); + systemProps.setProperty("env.HTTPS_PROXY", "http://proxy.example.com:3128"); + systemProps.setProperty("env.NO_PROXY", "localhost,127.0.0.1"); + request.setSystemProperties(systemProps); + + RepositorySystemSession session = systemSessionFactory.newRepositorySession(request); + + RemoteRepository httpRepo = new RemoteRepository.Builder("test", "default", "http://repo.example.com/").build(); + org.eclipse.aether.repository.Proxy httpProxy = + session.getProxySelector().getProxy(httpRepo); + assertNotNull(httpProxy); + assertEquals("proxy.example.com", httpProxy.getHost()); + assertEquals(3128, httpProxy.getPort()); + + RemoteRepository httpsRepo = + new RemoteRepository.Builder("test2", "default", "https://repo.example.com/").build(); + org.eclipse.aether.repository.Proxy httpsProxy = + session.getProxySelector().getProxy(httpsRepo); + assertNotNull(httpsProxy); + assertEquals("proxy.example.com", httpsProxy.getHost()); + assertEquals(3128, httpsProxy.getPort()); + } + + @Test + void settingsProxyTakesPrecedenceOverEnvVarsTest() throws InvalidRepositoryException { + DefaultRepositorySystemSessionFactory systemSessionFactory = new DefaultRepositorySystemSessionFactory( + aetherRepositorySystem, + eventSpyDispatcher, + information, + defaultTypeRegistry, + versionScheme, + Collections.emptyMap()); + + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + request.setLocalRepository(getLocalRepository()); + + // Settings proxy for http + org.apache.maven.settings.Proxy settingsProxy = new org.apache.maven.settings.Proxy(); + settingsProxy.setProtocol("http"); + settingsProxy.setHost("settings-proxy.example.com"); + settingsProxy.setPort(8080); + request.addProxy(settingsProxy); + + // Env var also set (should be ignored for http, used for https) + Properties systemProps = new Properties(); + systemProps.setProperty("env.HTTP_PROXY", "http://env-proxy.example.com:3128"); + systemProps.setProperty("env.HTTPS_PROXY", "http://env-proxy.example.com:3128"); + request.setSystemProperties(systemProps); + + RepositorySystemSession session = systemSessionFactory.newRepositorySession(request); + + // Settings proxy wins for http + RemoteRepository httpRepo = new RemoteRepository.Builder("test", "default", "http://repo.example.com/").build(); + org.eclipse.aether.repository.Proxy httpProxy = + session.getProxySelector().getProxy(httpRepo); + assertNotNull(httpProxy); + assertEquals("settings-proxy.example.com", httpProxy.getHost()); + + // Env var used for https (not covered by settings) + RemoteRepository httpsRepo = + new RemoteRepository.Builder("test2", "default", "https://repo.example.com/").build(); + org.eclipse.aether.repository.Proxy httpsProxy = + session.getProxySelector().getProxy(httpsRepo); + assertNotNull(httpsProxy); + assertEquals("env-proxy.example.com", httpsProxy.getHost()); + } + + @Test + void proxyFromEnvVarsViaEnvironmentUtilsTest() throws InvalidRepositoryException { + // Skip if HTTPS_PROXY is not set in the real environment + String httpsProxy = System.getenv("HTTPS_PROXY"); + if (httpsProxy == null) { + httpsProxy = System.getenv("https_proxy"); + } + Assumptions.assumeTrue(httpsProxy != null, "HTTPS_PROXY env var not set; skipping e2e env var test"); + + DefaultRepositorySystemSessionFactory systemSessionFactory = new DefaultRepositorySystemSessionFactory( + aetherRepositorySystem, + eventSpyDispatcher, + information, + defaultTypeRegistry, + versionScheme, + Collections.emptyMap()); + + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + request.setLocalRepository(getLocalRepository()); + + // Simulate what BaseParser.populateSystemProperties() does — reads real env vars + Properties systemProps = new Properties(); + EnvironmentUtils.addEnvVars(systemProps); + request.setSystemProperties(systemProps); + + RepositorySystemSession session = systemSessionFactory.newRepositorySession(request); + + RemoteRepository httpsRepo = + new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build(); + org.eclipse.aether.repository.Proxy proxy = session.getProxySelector().getProxy(httpsRepo); + assertNotNull(proxy, "Expected proxy to be set from HTTPS_PROXY env var"); + } + protected ArtifactRepository getLocalRepository() throws InvalidRepositoryException { File repoDir = new File(getBasedir(), "target/local-repo").getAbsoluteFile();