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

ppkarwasz pushed a commit to branch feat/2.x/uniform-uri-conversion
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 23ab8ef8d02c75d3c568212ee90d0d9a4486033a
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sat Jun 27 13:42:52 2026 +0200

    Consolidate string-to-URI conversion in `NetUtils.toURI`
    
    Rewrite `NetUtils.toURI` around two rules:
    
    1. an absolute URI (a real scheme, not a Windows drive letter) is returned
       as commanded;
    2. otherwise the value is a file system path: an absolute path becomes a
       `file:` URI holding the full path, a relative one becomes a scheme-less
       URI to be resolved later as a file or a class path resource.
    
    The drive-letter and backslash special cases are isolated in a single
    `toRelativeUri` helper, and `parseURI` repairs blanks through an installed
    `URLStreamHandler` (e.g. Apache Commons VFS) while preserving user info,
    host, port and query.
    
    This changes six conversion outcomes; see the PR description for the
    before/after table.
    
    Assisted-By: Claude Opus 4.8 <[email protected]>
---
 .../apache/logging/log4j/core/util/NetUtils.java   | 98 +++++++++++++++++++---
 1 file changed, 85 insertions(+), 13 deletions(-)

diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java
index 92a85a6fa1..da92721873 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java
@@ -156,28 +156,100 @@ public final class NetUtils {
     }
 
     /**
-     * Converts a URI string or file path to a URI object.
+     * Converts a configuration location, expressed as either a {@link URI} or 
a file system path, into a {@link URI}.
      *
-     * @param path the URI string or path
-     * @return the URI object
+     * <p>The argument is classified by syntax:</p>
+     *
+     * <ul>
+     * <li>An <strong>absolute URI</strong> (one bearing a scheme) is returned 
unchanged.
+     * A single-letter scheme is the exception: it is read as a Windows drive 
letter, not a URI scheme.</li>
+     * <li>An <strong>absolute file system path</strong> for the current OS is 
converted to a {@code file:} URI.</li>
+     * <li>A <strong>relative</strong> path or URI ({@code log4j2.xml}, {@code 
config/log4j2.xml}) is returned as a
+     * relative URI and will require disambiguation later.</li>
+     * </ul>
+     *
+     * <p><em>Note:</em> URI parsing is performed leniently and might fix 
minor syntax errors.</p>
+     *
+     * @param path a URI string or a file system path
+     * @return the matching URI, never {@code null}
      */
     @SuppressFBWarnings(
             value = "PATH_TRAVERSAL_IN",
             justification = "Currently `path` comes from a configuration 
file.")
     public static URI toURI(final String path) {
+        final URI uri = parseURI(path);
+        // 1. A genuine absolute URI (a real scheme, not a Windows drive 
letter) is used as commanded.
+        if (uri != null && uri.isAbsolute() && 
!isWindowsDriveLetter(uri.getScheme())) {
+            return uri;
+        }
+        // 2. Otherwise, the value is a file system path:
+        //
+        // - an absolute path becomes a `file:` URI holding the full path,
+        // - a relative one becomes a scheme-less URI, to be resolved later as 
a file or a class path resource.
+        final File file = new File(path);
+        return file.isAbsolute() ? file.toURI() : toRelativeUri(file);
+    }
+
+    /**
+     * Converts a relative file system path into a scheme-less {@link URI}.
+     *
+     * <p>Characters illegal in a URI (such as {@code \} or blanks) are 
percent-encoded.
+     * A leading Windows drive letter is escaped too ({@code C:} becomes 
{@code C%3A}),
+     * so the result is a relative reference rather than a URI whose scheme is 
the drive letter.</p>
+     */
+    private static URI toRelativeUri(final File file) {
         try {
-            // Resolves absolute URI
-            return new URI(path);
+            final URI uri = new URI(null, null, file.toString(), null);
+            return uri.isAbsolute() ? 
URI.create(uri.toASCIIString().replaceFirst(":", "%3A")) : uri;
         } catch (final URISyntaxException e) {
-            // A file path or a Apache Commons VFS URL might contain blanks.
-            // A file path may start with a driver letter
-            try {
-                final URL url = new URL(path);
-                return new URI(url.getProtocol(), url.getHost(), 
url.getPath(), null);
-            } catch (MalformedURLException | URISyntaxException nestedEx) {
-                return new File(path).toURI();
-            }
+            // Unreachable: the multi-argument constructor escapes every 
character illegal in a URI.
+            throw new IllegalArgumentException("Cannot convert to a relative 
URI: " + file, e);
+        }
+    }
+
+    /**
+     * Leniently parses a string into a {@link URI}.
+     *
+     * <p>The string is first parsed with {@link URI#URI(String)}. If it is 
not valid URI syntax,
+     * it is parsed with {@link URL#URL(String)} and rebuilt into a properly 
encoded URI.</p>
+     *
+     * <p>The {@link URL} step consults the installed {@link 
java.net.URLStreamHandler URL stream handlers},
+     * so a scheme contributed by Apache Commons VFS (or any other {@link 
java.net.URLStreamHandlerFactory}) is
+     * recognized and its illegal characters are encoded.</p>
+     *
+     * @param value the value to parse
+     * @return the parsed {@link URI}, or {@code null} if neither {@link URI} 
nor {@link URL} can parse it
+     */
+    private static /*@Nullable*/ URI parseURI(final String value) {
+        try {
+            return new URI(value);
+        } catch (URISyntaxException e) {
+            LOGGER.trace("Could not parse value '{}' as URI. Falling back to 
URL parsing.", value, e);
+        }
+        try {
+            final URL url = new URL(value);
+            return new URI(
+                    url.getProtocol(),
+                    url.getUserInfo(),
+                    url.getHost(),
+                    url.getPort(),
+                    url.getPath(),
+                    url.getQuery(),
+                    null);
+        } catch (MalformedURLException | URISyntaxException e) {
+            LOGGER.trace("Could not parse value '{}' as URL.", value, e);
         }
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if the given URI scheme is in fact a Windows drive 
letter, that is, a single letter.
+     *
+     * <p>No registered URI scheme is a single letter, so a single-letter 
scheme always denotes the drive letter of a
+     * path such as {@code C:/dir/file} that happens to be valid URI 
syntax.</p>
+     */
+    private static boolean isWindowsDriveLetter(final /* @Nullable */ String 
scheme) {
+        return scheme != null && scheme.length() == 1 && 
Character.isLetter(scheme.charAt(0));
     }
 
     public static List<URI> toURIs(final String path) {

Reply via email to