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 1c9b5850c Reject non-ascii hex digits in UriParser percent-decoding
(#774)
1c9b5850c is described below
commit 1c9b5850c1ef3f61dde411e2fe03153e0e7307cb
Author: Naveed Khan <[email protected]>
AuthorDate: Wed Jul 22 13:38:06 2026 +0000
Reject non-ascii hex digits in UriParser percent-decoding (#774)
* reject non-ascii hex digits in UriParser percent-decoding
* reuse CharUtils.isHex in hexDigit
---
.../apache/commons/vfs2/provider/UriParser.java | 24 ++++++++++++++++++----
.../commons/vfs2/provider/UriParserTest.java | 17 +++++++++++++++
2 files changed, 37 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 9ca25eff3..9133873f1 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
@@ -18,6 +18,7 @@ package org.apache.commons.vfs2.provider;
import java.util.Arrays;
+import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.vfs2.FileName;
import org.apache.commons.vfs2.FileSystemException;
@@ -47,6 +48,21 @@ public final class UriParser {
private static final char LOW_MASK = 0x0F;
+ /**
+ * Returns the value of an ASCII hexadecimal digit, or {@code -1} if the
character is not one.
+ * <p>
+ * Unlike {@link Character#digit(char, int)}, this only accepts the ASCII
digits {@code 0-9}, {@code a-f} and
+ * {@code A-F} that RFC 3986 permits in a percent-encoding, so Unicode
look-alikes such as the fullwidth digits are
+ * rejected instead of silently decoded.
+ * </p>
+ *
+ * @param ch the character to convert.
+ * @return the value 0-15, or {@code -1} if {@code ch} is not an ASCII
hexadecimal digit.
+ */
+ private static int hexDigit(final char ch) {
+ return CharUtils.isHex(ch) ? Character.digit(ch, HEX_BASE) : -1;
+ }
+
/**
* Encodes and appends a string to a StringBuilder.
*
@@ -88,8 +104,8 @@ public final class UriParser {
}
// Decode
- final int dig1 = Character.digit(buffer.charAt(index + 1),
HEX_BASE);
- final int dig2 = Character.digit(buffer.charAt(index + 2),
HEX_BASE);
+ final int dig1 = hexDigit(buffer.charAt(index + 1));
+ final int dig2 = hexDigit(buffer.charAt(index + 2));
if (dig1 == -1 || dig2 == -1) {
throw new
FileSystemException("vfs.provider/invalid-escape-sequence.error",
buffer.substring(index, index + 3));
@@ -204,8 +220,8 @@ public final class UriParser {
}
// Decode
- final int dig1 = Character.digit(buffer.charAt(index + 1),
HEX_BASE);
- final int dig2 = Character.digit(buffer.charAt(index + 2),
HEX_BASE);
+ final int dig1 = hexDigit(buffer.charAt(index + 1));
+ final int dig2 = hexDigit(buffer.charAt(index + 2));
if (dig1 == -1 || dig2 == -1) {
throw new
FileSystemException("vfs.provider/invalid-escape-sequence.error",
buffer.substring(index, index + 3));
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 5c72a3b10..4508ec1b7 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
@@ -18,6 +18,7 @@ package org.apache.commons.vfs2.provider;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import org.apache.commons.vfs2.FileSystemException;
@@ -75,6 +76,22 @@ public class UriParserTest {
UriParser.decode("ftp://host/outside%25text[inside%25text]tail"));
}
+ @Test
+ public void testDecodeAcceptsAsciiHexDigits() throws FileSystemException {
+ // Valid ASCII escapes still decode, both letter cases.
+ assertEquals("../", UriParser.decode("%2e%2E%2f"));
+ assertEquals("Az", UriParser.decode("%41%7a"));
+ }
+
+ @Test
+ public void testDecodeRejectsNonAsciiHexDigits() {
+ // RFC 3986 pct-encoding is ASCII HEXDIG only. Fullwidth "2e2e2f"
would otherwise decode to "../".
+ assertThrows(FileSystemException.class, () ->
UriParser.decode("%2e%2e%2f"));
+ // A single non-ASCII digit in either nibble is enough to reject:
arabic-indic five (U+0665).
+ assertThrows(FileSystemException.class, () -> UriParser.decode("%4٥"));
+ assertThrows(FileSystemException.class, () -> UriParser.decode("%٥5"));
+ }
+
@Test
public void testDecodePreservesPercentInsideIPv6Host() throws
FileSystemException {
assertEquals("ftp://[fe80::1%25eth0]/path",