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

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


The following commit(s) were added to refs/heads/master by this push:
     new d28ff9f1 Prevent URL path traversal bypass via percent encoding in 
UrlValidator (#383)
d28ff9f1 is described below

commit d28ff9f1cbd45a5a4b35d7a2b7e79bb30263da5d
Author: sahvx655-wq <[email protected]>
AuthorDate: Fri Jun 19 17:23:04 2026 +0530

    Prevent URL path traversal bypass via percent encoding in UrlValidator 
(#383)
    
    
    ---------
    
    Co-authored-by: “sahvx655-wq” <“[email protected]”>
---
 .../commons/validator/routines/UrlValidator.java   | 23 +++++++-------
 .../validator/routines/UrlValidatorTest.java       | 37 ++++++++++++++++++++++
 2 files changed, 49 insertions(+), 11 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/validator/routines/UrlValidator.java 
b/src/main/java/org/apache/commons/validator/routines/UrlValidator.java
index cdaaa900..2a2e9fb7 100644
--- a/src/main/java/org/apache/commons/validator/routines/UrlValidator.java
+++ b/src/main/java/org/apache/commons/validator/routines/UrlValidator.java
@@ -17,8 +17,11 @@
 package org.apache.commons.validator.routines;
 
 import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Locale;
@@ -29,10 +32,8 @@ import java.util.regex.Pattern;
 import org.apache.commons.validator.GenericValidator;
 
 /**
- * <strong>URL Validation</strong> routines.
- * <p>
+ * <p><strong>URL Validation</strong> routines.</p>
  * Behavior of validation is modified by passing in options:
- * </p>
  * <ul>
  * <li>ALLOW_2_SLASHES - [FALSE]  Allows double '/' characters in the path
  * component.</li>
@@ -356,7 +357,7 @@ public class UrlValidator implements Serializable {
     }
 
     /**
-     * Checks if a field has a valid URL address.
+     * <p>Checks if a field has a valid URL address.</p>
      *
      * Note that the method calls #isValidAuthority()
      * which checks that the domain is valid.
@@ -488,19 +489,19 @@ public class UrlValidator implements Serializable {
         }
 
         try {
+            final String decodedPath = URLDecoder.decode(path, 
StandardCharsets.UTF_8.name());
             // Don't omit host otherwise leading path may be taken as host if 
it starts with //
-            final URI uri = new URI(null, "localhost", path, null);
+            final URI uri = new URI(null, "localhost", decodedPath, null);
             final String norm = uri.normalize().getPath();
             if (norm.startsWith("/../") // Trying to go via the parent dir
                     || norm.equals("/..")) { // Trying to go to the parent dir
                 return false;
             }
-        } catch (final URISyntaxException e) {
-            return false;
-        }
-
-        final int slash2Count = countToken("//", path);
-        if (isOff(ALLOW_2_SLASHES) && slash2Count > 0) {
+            final int slash2Count = countToken("//", decodedPath);
+            if (isOff(ALLOW_2_SLASHES) && slash2Count > 0) {
+                return false;
+            }
+        } catch (final UnsupportedEncodingException | IllegalArgumentException 
| URISyntaxException e) {
             return false;
         }
 
diff --git 
a/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java 
b/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java
index 25e0b5dc..0cb5d9ad 100644
--- a/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java
+++ b/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java
@@ -626,4 +626,41 @@ public class UrlValidatorTest {
         assertEquals("DomainValidator disagrees with ALLOW_LOCAL_URLS 
setting", thrown.getMessage());
     }
 
+    @Test
+    void testValidator383() {
+        final UrlValidator validator = new UrlValidator();
+
+        // Literal traversal checks (already rejected)
+        assertFalse(validator.isValid("http://example.com/../etc/passwd";));
+        assertFalse(validator.isValid("http://example.com/..";));
+        assertFalse(validator.isValid("http://example.com/../";));
+
+        // Percent-encoded traversal checks
+        assertFalse(validator.isValid("http://example.com/..%2fetc/passwd";));
+        assertFalse(validator.isValid("http://example.com/..%2Fetc/passwd";));
+        assertFalse(validator.isValid("http://example.com/%2e%2e/world";));
+        assertFalse(validator.isValid("http://example.com/%2e%2e%2fworld";));
+        assertFalse(validator.isValid("http://example.com/%2E%2e%2Fworld";));
+
+        // Consecutive slashes via percent encoding
+        final UrlValidator noDoubleSlashes = new UrlValidator();
+        
assertFalse(noDoubleSlashes.isValid("http://example.com/foo%2F%2Fbar";));
+        
assertFalse(noDoubleSlashes.isValid("http://example.com/foo%2f%2fbar";));
+        assertFalse(noDoubleSlashes.isValid("http://example.com/%2F%2Fbar";));
+
+        final UrlValidator allowDoubleSlashes = new 
UrlValidator(UrlValidator.ALLOW_2_SLASHES);
+        
assertTrue(allowDoubleSlashes.isValid("http://example.com/foo%2F%2Fbar";));
+        
assertTrue(allowDoubleSlashes.isValid("http://example.com/foo%2f%2fbar";));
+        assertTrue(allowDoubleSlashes.isValid("http://example.com/%2F%2Fbar";));
+
+        // Invalid percent-encoding handling
+        assertFalse(validator.isValid("http://example.com/foo%2";));
+        assertFalse(validator.isValid("http://example.com/foo%2G";));
+        assertFalse(validator.isValid("http://example.com/foo%";));
+
+        // Plus character preservation
+        assertTrue(validator.isValid("http://example.com/foo+bar";));
+        assertTrue(validator.isValid("http://example.com/foo+bar/baz+qux";));
+    }
+
 }

Reply via email to