You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/10/15 07:54:58 UTC

(maven) branch maven-4.0.x updated: Treat all loopback hosts as local for external mirror rules (fix #8320) (#13496) (#13525)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/maven-4.0.x by this push:
     new 2c376b2ef0 Treat all loopback hosts as local for external mirror rules (fix #8320) (#13496) (#13525)
2c376b2ef0 is described below

commit 2c376b2ef0d6b185f0d2f42d9a5e50b68421a21f
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu Oct 15 09:54:51 2026 +0200

    Treat all loopback hosts as local for external mirror rules (fix #8320) (#13496) (#13525)
    
    external:* and external:http:* only treated localhost and 127.0.0.1 as local. Other addresses of the 127.0.0.0/8 range and IPv6 loopback addresses were considered external, and a bracketed IPv6 host was not even parsed. Recognise both, without resolving any name, in the v3 bridge and in the compat mirror selector.
    
    Fixes #8320
    
    (cherry picked from commit 7f2bc3a809d28c9f97b1ee31be8331cb75c4b74a)
    
    Co-authored-by: Arturo Bernal <ab...@apache.org>
---
 .../maven/repository/DefaultMirrorSelector.java    | 31 ++++++++++++++++++--
 .../repository/DefaultMirrorSelectorTest.java      | 33 ++++++++++++++++++++++
 .../apache/maven/bridge/MavenRepositorySystem.java | 31 ++++++++++++++++++--
 .../maven/bridge/MavenRepositorySystemTest.java    | 33 ++++++++++++++++++++++
 4 files changed, 124 insertions(+), 4 deletions(-)

diff --git a/compat/maven-compat/src/main/java/org/apache/maven/repository/DefaultMirrorSelector.java b/compat/maven-compat/src/main/java/org/apache/maven/repository/DefaultMirrorSelector.java
index 8d11bb1a3d..e87097455c 100644
--- a/compat/maven-compat/src/main/java/org/apache/maven/repository/DefaultMirrorSelector.java
+++ b/compat/maven-compat/src/main/java/org/apache/maven/repository/DefaultMirrorSelector.java
@@ -21,6 +21,8 @@
 import javax.inject.Named;
 import javax.inject.Singleton;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -129,7 +131,10 @@ else if (EXTERNAL_HTTP_WILDCARD.equals(repo) && isExternalHttpRepo(originalRepos
      * external:*} and {@code external:http:*} mirror matching below.
      */
     private static final Pattern URL_PROTOCOL_AND_HOST =
-            Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*");
+            Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?(\\[[^\\]]*\\]|[^/:]+))?.*");
+
+    /** An IPv4 address of the loopback range 127.0.0.0/8. */
+    private static final Pattern IPV4_LOOPBACK = Pattern.compile("127(\\.(25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}");
 
     /**
      * Checks the URL to see if this repository refers to an external repository. A URL that
@@ -149,8 +154,30 @@ static boolean isExternalRepo(ArtifactRepository originalRepository) {
         return !(isLocal(m.group(5)) || "file".equalsIgnoreCase(m.group(1)));
     }
 
+    /**
+     * Checks whether a host names the local machine: {@code localhost}, an IPv4 address of the loopback
+     * range {@code 127.0.0.0/8} or an IPv6 loopback address in brackets. An IPv4-mapped IPv6 address is judged by its
+     * IPv4 address. The names are not resolved.
+     *
+     * @param host the host of a repository URL, may be {@code null}
+     * @return true if the host is local
+     */
     private static boolean isLocal(String host) {
-        return "localhost".equals(host) || "127.0.0.1".equals(host);
+        if (host == null) {
+            return false;
+        }
+        if ("localhost".equals(host) || IPV4_LOOPBACK.matcher(host).matches()) {
+            return true;
+        }
+        if (host.startsWith("[") && host.endsWith("]")) {
+            try {
+                // an IP literal is parsed without any name lookup
+                return InetAddress.getByName(host).isLoopbackAddress();
+            } catch (UnknownHostException e) {
+                return false;
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/compat/maven-compat/src/test/java/org/apache/maven/repository/DefaultMirrorSelectorTest.java b/compat/maven-compat/src/test/java/org/apache/maven/repository/DefaultMirrorSelectorTest.java
index 358669daef..e985f78f39 100644
--- a/compat/maven-compat/src/test/java/org/apache/maven/repository/DefaultMirrorSelectorTest.java
+++ b/compat/maven-compat/src/test/java/org/apache/maven/repository/DefaultMirrorSelectorTest.java
@@ -65,6 +65,39 @@ void testExternalRepoClassification() {
         assertFalse(DefaultMirrorSelector.isExternalRepo(repo("http://127.0.0.1/m2/")));
     }
 
+    @Test
+    void testLoopbackAddressesAreLocal() {
+        for (String url : new String[] {
+            "http://127.0.0.2/m2/",
+            "http://127.255.255.254:8080/m2/",
+            "http://[::1]/m2/",
+            "http://[::1]:8080/m2/",
+            "http://[0:0:0:0:0:0:0:1]:8080/m2/",
+            // an IPv4 loopback address written as IPv4-mapped IPv6 address
+            "http://[::ffff:127.0.0.1]/m2/",
+            "http://[::ffff:7f00:1]:8080/m2/"
+        }) {
+            assertFalse(DefaultMirrorSelector.isExternalRepo(repo(url)), url);
+            assertFalse(DefaultMirrorSelector.isExternalHttpRepo(repo(url)), url);
+        }
+    }
+
+    @Test
+    void testOtherAddressesAreExternal() {
+        for (String url : new String[] {
+            "http://128.0.0.1/m2/",
+            "http://127.0.0.1.example.com/m2/",
+            "http://127.example.com/m2/",
+            "http://[2001:db8::1]/m2/",
+            "http://[2001:db8::1]:8080/m2/",
+            // an IPv4-mapped IPv6 address which is not a loopback one
+            "http://[::ffff:8.8.8.8]/m2/"
+        }) {
+            assertTrue(DefaultMirrorSelector.isExternalRepo(repo(url)), url);
+            assertTrue(DefaultMirrorSelector.isExternalHttpRepo(repo(url)), url);
+        }
+    }
+
     private static ArtifactRepository repo(String url) {
         return new DefaultArtifactRepository("test", url, null);
     }
diff --git a/impl/maven-core/src/main/java/org/apache/maven/bridge/MavenRepositorySystem.java b/impl/maven-core/src/main/java/org/apache/maven/bridge/MavenRepositorySystem.java
index a4f7c1d7ce..00151e8652 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/bridge/MavenRepositorySystem.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/bridge/MavenRepositorySystem.java
@@ -23,6 +23,8 @@
 import javax.inject.Singleton;
 
 import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -777,7 +779,10 @@ else if (EXTERNAL_HTTP_WILDCARD.equals(repo) && isExternalHttpRepo(originalRepos
      * external:*} and {@code external:http:*} mirror matching below.
      */
     private static final Pattern URL_PROTOCOL_AND_HOST =
-            Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*");
+            Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?(\\[[^\\]]*\\]|[^/:]+))?.*");
+
+    /** An IPv4 address of the loopback range 127.0.0.0/8. */
+    private static final Pattern IPV4_LOOPBACK = Pattern.compile("127(\\.(25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}");
 
     /**
      * Checks the URL to see if this repository refers to an external repository. A URL that
@@ -797,8 +802,30 @@ static boolean isExternalRepo(ArtifactRepository originalRepository) {
         return !(isLocal(m.group(5)) || "file".equalsIgnoreCase(m.group(1)));
     }
 
+    /**
+     * Checks whether a host names the local machine: {@code localhost}, an IPv4 address of the loopback
+     * range {@code 127.0.0.0/8} or an IPv6 loopback address in brackets. An IPv4-mapped IPv6 address is judged by its
+     * IPv4 address. The names are not resolved.
+     *
+     * @param host the host of a repository URL, may be {@code null}
+     * @return true if the host is local
+     */
     private static boolean isLocal(String host) {
-        return "localhost".equals(host) || "127.0.0.1".equals(host);
+        if (host == null) {
+            return false;
+        }
+        if ("localhost".equals(host) || IPV4_LOOPBACK.matcher(host).matches()) {
+            return true;
+        }
+        if (host.startsWith("[") && host.endsWith("]")) {
+            try {
+                // an IP literal is parsed without any name lookup
+                return InetAddress.getByName(host).isLoopbackAddress();
+            } catch (UnknownHostException e) {
+                return false;
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/impl/maven-core/src/test/java/org/apache/maven/bridge/MavenRepositorySystemTest.java b/impl/maven-core/src/test/java/org/apache/maven/bridge/MavenRepositorySystemTest.java
index 76c06262c6..fcc1abb460 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/bridge/MavenRepositorySystemTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/bridge/MavenRepositorySystemTest.java
@@ -58,6 +58,39 @@ void testExternalRepoClassification() {
         assertFalse(MavenRepositorySystem.isExternalRepo(repo("http://127.0.0.1/m2/")));
     }
 
+    @Test
+    void testLoopbackAddressesAreLocal() {
+        for (String url : new String[] {
+            "http://127.0.0.2/m2/",
+            "http://127.255.255.254:8080/m2/",
+            "http://[::1]/m2/",
+            "http://[::1]:8080/m2/",
+            "http://[0:0:0:0:0:0:0:1]:8080/m2/",
+            // an IPv4 loopback address written as IPv4-mapped IPv6 address
+            "http://[::ffff:127.0.0.1]/m2/",
+            "http://[::ffff:7f00:1]:8080/m2/"
+        }) {
+            assertFalse(MavenRepositorySystem.isExternalRepo(repo(url)), url);
+            assertFalse(MavenRepositorySystem.isExternalHttpRepo(repo(url)), url);
+        }
+    }
+
+    @Test
+    void testOtherAddressesAreExternal() {
+        for (String url : new String[] {
+            "http://128.0.0.1/m2/",
+            "http://127.0.0.1.example.com/m2/",
+            "http://127.example.com/m2/",
+            "http://[2001:db8::1]/m2/",
+            "http://[2001:db8::1]:8080/m2/",
+            // an IPv4-mapped IPv6 address which is not a loopback one
+            "http://[::ffff:8.8.8.8]/m2/"
+        }) {
+            assertTrue(MavenRepositorySystem.isExternalRepo(repo(url)), url);
+            assertTrue(MavenRepositorySystem.isExternalHttpRepo(repo(url)), url);
+        }
+    }
+
     private static ArtifactRepository repo(String url) {
         return new MavenArtifactRepository("test", url, null, null, null);
     }