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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git


The following commit(s) were added to refs/heads/master by this push:
     new 27ed1d246 [VFS-863] Content inside brackets in directory/file names is 
not decoded (#773)
27ed1d246 is described below

commit 27ed1d246ebb5b027f128efe22193302f7967f67
Author: Nick Tarallo <[email protected]>
AuthorDate: Wed Jul 15 06:59:43 2026 -0700

    [VFS-863] Content inside brackets in directory/file names is not decoded 
(#773)
    
    * [VFS-863] Decode content in brackets unless URI authority detected
    
    Only set ipv6Host to true in UriParser.decode() if it is in the
    authority of the URI. Otherwise, leave it false so that content inside
    of brackets in the path will still be decoded.
    
    * [VFS-863] Restrict authority detection to leading // or scheme-delimiter 
://
    
    Addresses review feedback:
    - Authority detection previously triggered on any "//" in the input,
      so a "//" later in a path (e.g. "file:/a//[inside%25text]") would
      incorrectly treat following brackets as an IPv6 host. Detection now
      requires a leading "//" (network-path reference) or "://" with no
      earlier "/" (scheme delimiter).
    - Close the Files.walk stream in UrlTests with try-with-resources.
    - Add regression tests for "//" mid-path, "://" embedded in a path
      after the authority, a network-path reference IPv6 host, and a
      single URI mixing an IPv6 zone ID with brackets in the path.
---
 .../apache/commons/vfs2/provider/UriParser.java    | 33 ++++++++++++++++++---
 .../commons/vfs2/provider/UriParserTest.java       | 30 +++++++++++++++++++
 .../commons/vfs2/provider/local/UrlTests.java      | 34 ++++++++++++++++++++++
 3 files changed, 93 insertions(+), 4 deletions(-)

diff --git 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java
index b67df24eb..9ca25eff3 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java
@@ -161,12 +161,37 @@ public final class UriParser {
         int index = offset;
         int count = length;
         boolean ipv6Host = false;
+        boolean authorityDetected = false;
+        boolean inAuthority = false;
+        boolean slashSeenBeforeColon = false;
+        // A network-path reference starts with "//" and its authority 
immediately follows.
+        if (count >= 2 && buffer.charAt(index) == '/' && buffer.charAt(index + 
1) == '/') {
+            authorityDetected = true;
+            inAuthority = true;
+            index += 2;
+            count -= 2;
+        }
         for (; count > 0; count--, index++) {
             final char ch = buffer.charAt(index);
-            if (ch == '[') {
-                ipv6Host = true;
-            }
-            if (ch == ']') {
+            if (!authorityDetected) {
+                if (ch == '/') {
+                    // In this case, the URI has no authority
+                    slashSeenBeforeColon = true;
+                } else if (ch == ':' && !slashSeenBeforeColon && count >= 3
+                        && buffer.charAt(index + 1) == '/' && 
buffer.charAt(index + 2) == '/') {
+                    authorityDetected = true;
+                    inAuthority = true;
+                    index += 2;
+                    count -= 2;
+                }
+            } else if (inAuthority) {
+                if (ch == '[') {
+                    ipv6Host = true;
+                    inAuthority = false;
+                } else if (ch == '/') {
+                    inAuthority = false;
+                }
+            } else if (ipv6Host && ch == ']') {
                 ipv6Host = false;
             }
             if (ch != '%' || ipv6Host) {
diff --git 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/UriParserTest.java
 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/UriParserTest.java
index 73e18917a..0bc4f88cf 100644
--- 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/UriParserTest.java
+++ 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/UriParserTest.java
@@ -60,6 +60,36 @@ public class UriParserTest {
         
UriParser.checkUriEncoding("http://[fe80::14b5:1204:5410:64ca%en1]:8080";);
     }
 
+    @Test
+    public void testDecodePercentInsideBracketsInPath() throws 
FileSystemException {
+        assertEquals("/outside%text[inside%text]tail",
+                UriParser.decode("/outside%25text[inside%25text]tail"));
+        assertEquals("file:///outside%text[inside%text]tail",
+                UriParser.decode("file:///outside%25text[inside%25text]tail"));
+        assertEquals("ftp://host/outside%text[inside%text]tail";,
+                
UriParser.decode("ftp://host/outside%25text[inside%25text]tail";));
+    }
+
+    @Test
+    public void testDecodePercentInsideBracketsAfterDoubleSlashInPath() throws 
FileSystemException {
+        assertEquals("file:/a//[inside%text]",
+                UriParser.decode("file:/a//[inside%25text]"));
+        assertEquals("/a//outside%text[inside%text]tail",
+                UriParser.decode("/a//outside%25text[inside%25text]tail"));
+        assertEquals("ftp://host/redirect=http://other/[inside%text]";,
+                
UriParser.decode("ftp://host/redirect=http://other/[inside%25text]";));
+    }
+
+    @Test
+    public void testDecodePreservesPercentInsideIPv6Host() throws 
FileSystemException {
+        assertEquals("ftp://[fe80::1%25eth0]/path";,
+                UriParser.decode("ftp://[fe80::1%25eth0]/path";));
+        assertEquals("//[fe80::1%25eth0]/path",
+                UriParser.decode("//[fe80::1%25eth0]/path"));
+        assertEquals("ftp://[fe80::1%25eth0]/[dir%name]";,
+                UriParser.decode("ftp://[fe80::1%25eth0]/[dir%25name]";));
+    }
+
     @Test
     public void testNormalScheme() {
         assertEquals("ftp", UriParser.extractScheme(schemes, 
"ftp://user:pass@host/some/path/some:file";));
diff --git 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/local/UrlTests.java
 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/local/UrlTests.java
index 09e6ea5c7..841ebbb5c 100644
--- 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/local/UrlTests.java
+++ 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/local/UrlTests.java
@@ -19,6 +19,12 @@ package org.apache.commons.vfs2.provider.local;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.stream.Stream;
+
 import org.apache.commons.vfs2.AbstractProviderTestCase;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSystemManager;
@@ -65,4 +71,32 @@ public class UrlTests extends AbstractProviderTestCase {
         assertEquals(file.toString(), 
UriParser.decode(file.getURL().toString()));
     }
 
+    /**
+     * Tests that getURL() round-trips correctly when a directory name 
contains brackets.
+     */
+    @Test
+    public void testGetUrlRoundTripWithBracketsInPath() throws Exception {
+        final Path tmp = Files.createTempDirectory("vfs-roundtrip");
+        try {
+            final Path child = tmp.resolve("outside%text[inside%text]tail");
+            Files.createDirectories(child);
+
+            final FileSystemManager mgr = VFS.getManager();
+            final FileObject a = mgr.resolveFile(child.toUri().toString());
+            final FileObject b = mgr.resolveFile(a.getURL().toString());
+
+            assertEquals(a.getName().getPath(), b.getName().getPath());
+        } finally {
+            try (Stream<Path> walk = Files.walk(tmp)) {
+                walk.sorted(Comparator.reverseOrder())
+                        .forEach(p -> {
+                            try {
+                                Files.delete(p);
+                            } catch (final IOException ignore) { // NOPMD
+                            }
+                        });
+            }
+        }
+    }
+
 }

Reply via email to