From b7c54e7fabc2b0985d17ba888020927890f7a911 Mon Sep 17 00:00:00 2001 From: Toan Le Date: Wed, 19 Aug 2026 10:01:47 +0700 Subject: [PATCH] HADOOP-19973. Hadoop file system can not browse the folder that has children folder with colon in Windows. ABFS built list-status entry paths with File.separator, which is a backslash on Windows. Path("\name:x") then treats "\name" as a URI scheme, so listing a directory whose children contain a colon failed with URISyntaxException. The separator here is part of an ABFS/URI path, not a local filesystem path, so use a forward slash unconditionally. Contains content generated by Claude. Generated-by: Claude Opus 5 Co-authored-by: Claude Opus 5 (1M context) --- .../fs/azurebfs/services/AbfsClient.java | 3 +- .../fs/azurebfs/services/TestAbfsClient.java | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsClient.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsClient.java index c13a50996654b5..106578ff5d3c9a 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsClient.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsClient.java @@ -19,7 +19,6 @@ package org.apache.hadoop.fs.azurebfs.services; import java.io.Closeable; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -1868,7 +1867,7 @@ protected VersionedFileStatus getVersionedFileStatusFromEntry( entry.lastModified()); } - Path entryPath = new Path(File.separator + entry.name()); + Path entryPath = new Path(AbfsHttpConstants.FORWARD_SLASH + entry.name()); if (uri != null) { entryPath = entryPath.makeQualified(uri, entryPath); } diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/services/TestAbfsClient.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/services/TestAbfsClient.java index 2f433b2b400a18..0630f11417cee2 100644 --- a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/services/TestAbfsClient.java +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/services/TestAbfsClient.java @@ -30,10 +30,12 @@ import org.apache.hadoop.fs.azurebfs.AbfsConfiguration; import org.apache.hadoop.fs.azurebfs.AbfsCountersImpl; import org.apache.hadoop.fs.azurebfs.MockIntercept; +import org.apache.hadoop.fs.azurebfs.contracts.services.DfsListResultEntrySchema; import org.apache.hadoop.fs.azurebfs.oauth2.AccessTokenProvider; import org.apache.hadoop.fs.azurebfs.utils.Base64; import org.apache.hadoop.fs.azurebfs.utils.MetricFormat; +import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.FORWARD_SLASH; import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_METRIC_ACCOUNT_KEY; import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_METRIC_ACCOUNT_NAME; import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_METRIC_FORMAT; @@ -123,6 +125,46 @@ public void testTimerInitializationWithMetricCollection() throws Exception { .isEqualTo(false); } + /** + * Test that {@link AbfsClient#getVersionedFileStatusFromEntry} always builds the + * entry path using a forward slash, regardless of the platform-dependent + * {@link java.io.File#separator}. On Windows, {@code File.separator} is a + * backslash, and using it here used to break browsing of directories whose + * child entry names contain a colon (e.g. "dir:name"), because + * "\dir:name" gets misparsed as a URI with scheme "\dir". + */ + @Test + public void testGetVersionedFileStatusFromEntryUsesForwardSlash() throws Exception { + final Configuration configuration = new Configuration(); + AbfsConfiguration abfsConfiguration = new AbfsConfiguration(configuration, ACCOUNT_NAME); + + AbfsCounters abfsCounters = Mockito.spy(new AbfsCountersImpl(new URI("abcd"))); + AbfsClientContext abfsClientContext = new AbfsClientContextBuilder().withAbfsCounters(abfsCounters).build(); + + // Get an instance of AbfsClient. + AbfsClient client = new AbfsDfsClient(new URL("https://azure.com"), + null, + abfsConfiguration, + (AccessTokenProvider) null, + null, + abfsClientContext); + + final String entryName = "dir:withColon"; + DfsListResultEntrySchema entry = new DfsListResultEntrySchema() + .withName(entryName) + .withIsDirectory(true); + + VersionedFileStatus status = client.getVersionedFileStatusFromEntry(entry, null); + + Assertions.assertThat(status.getPath().toUri().getPath()) + .describedAs("Entry path must be built with '/' regardless of the " + + "platform's File.separator, so folder names containing ':' " + + "are not misparsed as a URI scheme on Windows") + .isEqualTo(FORWARD_SLASH + entryName); + + client.close(); + } + /** * Check if a thread with the specified name is running. *