Copilot commented on code in PR #2188:
URL: https://github.com/apache/maven-resolver/pull/2188#discussion_r4250726019


##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java:
##########
@@ -364,24 +363,32 @@ private CachedPrefixes cachePrefixes(
     private static final PrefixTree ENABLED_NO_INPUT = new 
PrefixTree("enabled-no-input");
     private static final PrefixTree BROKEN = new PrefixTree("broken");
 
+    private enum VerificationOutcome {
+        SERVED,
+        ABSENT,
+        UNKNOWN
+    }
+
     /**
      * The cached per remote repository prefixes state: the effective {@link 
PrefixTree}, whether it was
      * auto-discovered (as only auto-discovered prefixes are subject to denied 
path verification, see
-     * {@link #CONFIG_PROP_VERIFY_DENIED}), whether verification happened 
already, and the denied path (if any)
-     * that verification proved the remote repository actually serves.
+     * {@link #CONFIG_PROP_VERIFY_DENIED}), per-path caching of paths verified 
as served, and per-path caching
+     * of definitively absent paths, with verification outcomes maintained 
independently for each path.
      */
     private static final class CachedPrefixes {
         private static final CachedPrefixes DISABLED_PREFIXES = new 
CachedPrefixes(DISABLED, false);
         private static final CachedPrefixes NO_INPUT_PREFIXES = new 
CachedPrefixes(ENABLED_NO_INPUT, false);
 
         private volatile PrefixTree prefixTree;
         private final boolean autoDiscovered;
-        private final AtomicBoolean verifyClaimed = new AtomicBoolean(false);
-        private volatile String verifiedServedPath;
+        private final Set<String> verifiedServedPaths;
+        private final Set<String> verifiedAbsentPaths;
 
         private CachedPrefixes(PrefixTree prefixTree, boolean autoDiscovered) {
             this.prefixTree = prefixTree;
             this.autoDiscovered = autoDiscovered;
+            this.verifiedServedPaths = autoDiscovered ? 
ConcurrentHashMap.newKeySet() : Collections.emptySet();
+            this.verifiedAbsentPaths = autoDiscovered ? 
ConcurrentHashMap.newKeySet() : Collections.emptySet();
         }

Review Comment:
   The per-repository/session `verifiedServedPaths` and especially 
`verifiedAbsentPaths` sets can grow without bound if many distinct denied paths 
are encountered (e.g., large builds, misconfigured repos, or adversarial inputs 
producing many 404 verifications). Consider bounding these caches (max size 
with eviction) to prevent unbounded memory growth while still keeping 
verification/probe costs under control.



##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java:
##########
@@ -628,21 +667,29 @@ private boolean 
isVerifyDeniedDropsTreeEnabled(RemoteRepository repository) {
 
         /**
          * Checks whether the remote repository actually serves given path, 
using a lightweight existence check
-         * (the transporter sits below the filtering connector, so no 
recursion can happen). Any failure (path
-         * not present, transport problem) yields {@code false}: the prefixes 
verdict is overridden (or, with
-         * {@link #CONFIG_PROP_VERIFY_DENIED_DROPS_TREE}, the whole file 
dropped) only when the remote repository
-         * provably serves the denied path.
+         * (the transporter sits below the filtering connector, so no 
recursion can happen). Only definitive
+         * {@link Transporter#ERROR_NOT_FOUND} is recorded as absent so 
transient transport or network failures
+         * do not permanently poison the negative cache.
          */
-        private boolean remoteRepositoryServesPath(RemoteRepository 
repository, String path) {
-            try (Transporter transporter = 
transporterProvider.newTransporter(session, repository)) {
+        private VerificationOutcome 
remoteRepositoryServesPath(RemoteRepository repository, String path) {
+            Transporter transporter = null;
+            try {
+                transporter = transporterProvider.newTransporter(session, 
repository);
                 transporter.peek(new PeekTask(new URI(null, null, path, 
null)));
-                return true;
+                return VerificationOutcome.SERVED;
             } catch (URISyntaxException e) {
                 logger.debug("Cannot construct URI for denied path {} of {}", 
path, repository, e);
-                return false;
+                return VerificationOutcome.ABSENT;
             } catch (Exception e) {
                 logger.debug("Verification of denied path {} against {} 
failed", path, repository, e);
-                return false;
+                if (transporter != null && transporter.classify(e) == 
Transporter.ERROR_NOT_FOUND) {
+                    return VerificationOutcome.ABSENT;
+                }
+                return VerificationOutcome.UNKNOWN;
+            } finally {
+                if (transporter != null) {
+                    transporter.close();
+                }
             }
         }

Review Comment:
   Returning `VerificationOutcome.ABSENT` on `URISyntaxException` is logically 
incorrect: URI construction failure does not prove the path is missing 
remotely, and it can cause a permanently negative-cached denial for paths 
containing characters requiring encoding. Treat this case as `UNKNOWN` (no 
negative caching), or ensure the `path` is encoded/constructed in a way that 
cannot throw `URISyntaxException` for valid repository paths.



##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java:
##########
@@ -590,12 +620,21 @@ private Result acceptPrefix(RemoteRepository repository, 
String path) {
                                     + " (verified served despite stale 
auto-discovered prefixes)");
                 }
             }
-            return result(
-                    accepted,
-                    NAME,
-                    accepted
-                            ? "Path " + path + " allowed from " + 
repository.getId()
-                            : "Path " + path + " NOT allowed from " + 
repository.getId());
+            return accepted
+                    ? result(true, NAME, "Path " + path + " allowed from " + 
repository.getId())
+                    : notAllowedResult(repository, path, cachedPrefixes);
+        }
+
+        private Result notAllowedResult(RemoteRepository repository, String 
path, CachedPrefixes cachedPrefixes) {
+            String reasoning = "Path " + path + " NOT allowed from " + 
repository.getId();
+            if (cachedPrefixes.autoDiscovered()) {
+                reasoning += " (auto-discovered prefix file appears 
incomplete). If this repository serves a "
+                        + "virtual aggregate with an incomplete prefix file, 
set -D"
+                        + CONFIG_PROP_VERIFY_DENIED + "." + repository.getId()
+                        + "=true or report this to the repository 
administrator. "
+                        + "See 
https://maven.apache.org/resolver/remote-repository-filtering.html";;

Review Comment:
   This message is emitted for any auto-discovered denial, including cases 
where verification is already enabled and/or the path was definitively verified 
as absent. In those cases, stating the prefix file 'appears incomplete' and 
suggesting `...verifyDenied.<repo>=true` is misleading/redundant. Consider 
tailoring the reasoning based on state (e.g., verification disabled → suggest 
enabling; verified absent → state it was checked and not found; verification 
outcome unknown → state verification failed transiently and will be retried).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to