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

ggregory 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 b1dd62d15 [VFS-861] Http5FileProvider Basic authentication fails: 
UserAuthenticationData.setData(Type, char[]) should clone its array input
b1dd62d15 is described below

commit b1dd62d1571c9ef68dece57b0e54a7467bb3b629
Author: Gary Gregory <[email protected]>
AuthorDate: Thu Mar 5 20:44:13 2026 -0500

    [VFS-861] Http5FileProvider Basic authentication fails:
    UserAuthenticationData.setData(Type, char[]) should clone its array
    input
---
 .../vfs2/provider/http4/Http4FileProvider.java     |  36 +++-----
 .../vfs2/provider/http5/Http5FileProvider.java     |  35 +++----
 .../commons/vfs2/util/UserAuthenticatorUtils.java  |   8 +-
 .../vfs2/provider/http4/Http4BasicAuthTest.java    | 101 +++++++++++++++++++++
 .../vfs2/provider/http5/Http5BasicAuthTest.java    | 101 +++++++++++++++++++++
 .../apache/commons/vfs2/util/NHttpFileServer.java  |  77 ++++++++++++----
 src/changes/changes.xml                            |   1 +
 7 files changed, 289 insertions(+), 70 deletions(-)

diff --git 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileProvider.java
 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileProvider.java
index 2f7da5307..6a64f0c2c 100644
--- 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileProvider.java
+++ 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileProvider.java
@@ -236,41 +236,30 @@ public class Http4FileProvider extends 
AbstractOriginatingFileProvider {
     protected HttpClientContext createHttpClientContext(final 
Http4FileSystemConfigBuilder builder,
             final GenericFileName rootName, final FileSystemOptions 
fileSystemOptions,
             final UserAuthenticationData authData) {
-
         final HttpClientContext clientContext = HttpClientContext.create();
         final CredentialsProvider credsProvider = new 
BasicCredentialsProvider();
         clientContext.setCredentialsProvider(credsProvider);
-
-        final String username = 
StringUtils.valueOf(UserAuthenticatorUtils.getData(authData,
-                UserAuthenticationData.USERNAME, 
CharSequenceUtils.toCharArray(rootName.getUserName())));
-        final String password = 
StringUtils.valueOf(UserAuthenticatorUtils.getData(authData,
-                UserAuthenticationData.PASSWORD, 
CharSequenceUtils.toCharArray(rootName.getPassword())));
-
-        if (!StringUtils.isEmpty(username)) {
-            credsProvider.setCredentials(new AuthScope(rootName.getHostName(), 
rootName.getPort()),
-                    new UsernamePasswordCredentials(username, password));
+        final String rootUserName = rootName.getUserName();
+        final String userName = 
StringUtils.valueOf(UserAuthenticatorUtils.getData(authData, 
UserAuthenticationData.USERNAME,
+                rootUserName != null ? 
CharSequenceUtils.toCharArray(rootUserName) : null));
+        if (!StringUtils.isEmpty(userName)) {
+            final String rootPassword = rootName.getPassword();
+            final String password = 
StringUtils.valueOf(UserAuthenticatorUtils.getData(authData, 
UserAuthenticationData.PASSWORD,
+                    rootPassword != null ? 
CharSequenceUtils.toCharArray(rootPassword) : null));
+            credsProvider.setCredentials(new AuthScope(rootName.getHostName(), 
rootName.getPort()), new UsernamePasswordCredentials(userName, password));
         }
-
         final HttpHost proxyHost = getProxyHttpHost(builder, 
fileSystemOptions);
-
         if (proxyHost != null) {
             final UserAuthenticator proxyAuth = 
builder.getProxyAuthenticator(fileSystemOptions);
-
             if (proxyAuth != null) {
                 final UserAuthenticationData proxyAuthData = 
UserAuthenticatorUtils.authenticate(proxyAuth,
-                    new UserAuthenticationData.Type[] 
{UserAuthenticationData.USERNAME, UserAuthenticationData.PASSWORD});
-
+                        new UserAuthenticationData.Type[] 
{UserAuthenticationData.USERNAME, UserAuthenticationData.PASSWORD});
                 if (proxyAuthData != null) {
                     final UsernamePasswordCredentials proxyCreds = new 
UsernamePasswordCredentials(
-                            StringUtils.valueOf(
-                                    
UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.USERNAME, 
null)),
-                            StringUtils.valueOf(
-                                    
UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.PASSWORD, 
null)));
-
-                    credsProvider.setCredentials(new 
AuthScope(proxyHost.getHostName(), proxyHost.getPort()),
-                            proxyCreds);
+                            
StringUtils.valueOf(UserAuthenticatorUtils.getData(proxyAuthData, 
UserAuthenticationData.USERNAME, null)),
+                            
StringUtils.valueOf(UserAuthenticatorUtils.getData(proxyAuthData, 
UserAuthenticationData.PASSWORD, null)));
+                    credsProvider.setCredentials(new 
AuthScope(proxyHost.getHostName(), proxyHost.getPort()), proxyCreds);
                 }
-
                 if (builder.isPreemptiveAuth(fileSystemOptions)) {
                     final AuthCache authCache = new BasicAuthCache();
                     final BasicScheme basicAuth = new BasicScheme();
@@ -279,7 +268,6 @@ public class Http4FileProvider extends 
AbstractOriginatingFileProvider {
                 }
             }
         }
-
         return clientContext;
     }
 
diff --git 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http5/Http5FileProvider.java
 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http5/Http5FileProvider.java
index aff0121fe..4ef767545 100644
--- 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http5/Http5FileProvider.java
+++ 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http5/Http5FileProvider.java
@@ -227,42 +227,32 @@ public class Http5FileProvider extends 
AbstractOriginatingFileProvider {
     protected HttpClientContext createHttpClientContext(final 
Http5FileSystemConfigBuilder builder,
             final GenericFileName rootName, final FileSystemOptions 
fileSystemOptions,
             final UserAuthenticationData authData) {
-
         final HttpClientContext clientContext = HttpClientContext.create();
         final BasicCredentialsProvider credsProvider = new 
BasicCredentialsProvider();
         clientContext.setCredentialsProvider(credsProvider);
-
-        final String username = 
StringUtils.valueOf(UserAuthenticatorUtils.getData(authData,
-                UserAuthenticationData.USERNAME, 
CharSequenceUtils.toCharArray(rootName.getUserName())));
-        final char[] password = UserAuthenticatorUtils.getData(authData,
-                UserAuthenticationData.PASSWORD, 
CharSequenceUtils.toCharArray(rootName.getPassword()));
-
-        if (!StringUtils.isEmpty(username)) {
-            // set root port
+        final String rootUser = rootName.getUserName();
+        final String userName = 
StringUtils.valueOf(UserAuthenticatorUtils.getData(authData, 
UserAuthenticationData.USERNAME,
+                rootUser != null ? CharSequenceUtils.toCharArray(rootUser) : 
null));
+        if (!StringUtils.isEmpty(userName)) {
+            final String rootPassword = rootName.getPassword();
+            final char[] password = UserAuthenticatorUtils.getData(authData, 
UserAuthenticationData.PASSWORD,
+                    rootPassword != null ? 
CharSequenceUtils.toCharArray(rootPassword) : null);
             credsProvider.setCredentials(new AuthScope(rootName.getHostName(), 
rootName.getPort()),
-                    new UsernamePasswordCredentials(username, password));
+                    new UsernamePasswordCredentials(userName, 
password.clone()));
         }
-
         final HttpHost proxyHost = getProxyHttpHost(builder, 
fileSystemOptions);
-
         if (proxyHost != null) {
             final UserAuthenticator proxyAuth = 
builder.getProxyAuthenticator(fileSystemOptions);
-
             if (proxyAuth != null) {
                 final UserAuthenticationData proxyAuthData = 
UserAuthenticatorUtils.authenticate(proxyAuth,
-                    new UserAuthenticationData.Type[] 
{UserAuthenticationData.USERNAME, UserAuthenticationData.PASSWORD});
-
+                        new UserAuthenticationData.Type[] 
{UserAuthenticationData.USERNAME, UserAuthenticationData.PASSWORD});
                 if (proxyAuthData != null) {
                     final UsernamePasswordCredentials proxyCreds = new 
UsernamePasswordCredentials(
-                            StringUtils.valueOf(
-                                    
UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.USERNAME, 
null)),
+                            
StringUtils.valueOf(UserAuthenticatorUtils.getData(proxyAuthData, 
UserAuthenticationData.USERNAME, null)),
                             UserAuthenticatorUtils.getData(proxyAuthData, 
UserAuthenticationData.PASSWORD, null));
-
                     // set proxy host port
-                    credsProvider.setCredentials(new 
AuthScope(proxyHost.getHostName(), proxyHost.getPort()),
-                            proxyCreds);
+                    credsProvider.setCredentials(new 
AuthScope(proxyHost.getHostName(), proxyHost.getPort()), proxyCreds);
                 }
-
                 if (builder.isPreemptiveAuth(fileSystemOptions)) {
                     final AuthCache authCache = new BasicAuthCache();
                     final BasicScheme basicAuth = new BasicScheme();
@@ -271,18 +261,15 @@ public class Http5FileProvider extends 
AbstractOriginatingFileProvider {
                 }
             }
         }
-
         return clientContext;
     }
 
     private HttpRoutePlanner createHttpRoutePlanner(final 
Http5FileSystemConfigBuilder builder,
             final FileSystemOptions fileSystemOptions) {
         final HttpHost proxyHost = getProxyHttpHost(builder, 
fileSystemOptions);
-
         if (proxyHost != null) {
             return new DefaultProxyRoutePlanner(proxyHost);
         }
-
         return new SystemDefaultRoutePlanner(ProxySelector.getDefault());
     }
 
diff --git 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/util/UserAuthenticatorUtils.java
 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/util/UserAuthenticatorUtils.java
index 07ce7f386..cf4c62a89 100644
--- 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/util/UserAuthenticatorUtils.java
+++ 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/util/UserAuthenticatorUtils.java
@@ -67,12 +67,12 @@ public final class UserAuthenticatorUtils {
      *
      * @param data            The UserAuthenticationData.
      * @param type            The type of the element to retrieve.
-     * @param overriddenValue The default value.
+     * @param overrideValue The override value.
      * @return The data of the given type as a character array or null if the 
data is not available.
      */
-    public static char[] getData(final UserAuthenticationData data, final 
UserAuthenticationData.Type type, final char[] overriddenValue) {
-        if (overriddenValue != null) {
-            return overriddenValue;
+    public static char[] getData(final UserAuthenticationData data, final 
UserAuthenticationData.Type type, final char[] overrideValue) {
+        if (overrideValue != null) {
+            return overrideValue;
         }
         return data != null ? data.getData(type) : null;
     }
diff --git 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http4/Http4BasicAuthTest.java
 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http4/Http4BasicAuthTest.java
new file mode 100644
index 000000000..8a3a56167
--- /dev/null
+++ 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http4/Http4BasicAuthTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.vfs2.provider.http4;
+
+import static org.apache.commons.vfs2.VfsTestUtils.getTestDirectory;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.vfs2.*;
+import org.apache.commons.vfs2.auth.StaticUserAuthenticator;
+import org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder;
+import org.apache.commons.vfs2.util.NHttpFileServer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests HTTP Basic Authentication 
https://issues.apache.org/jira/browse/VFS-861.
+ */
+public class Http4BasicAuthTest {
+
+    private static final String TEST_USERNAME = "USER";
+
+    private static final String TEST_PASSWORD = "PWD";
+
+    private static NHttpFileServer server;
+
+    private static String baseUri;
+
+    @BeforeAll
+    static void startServer() throws Exception {
+        server = NHttpFileServer.startWithBasicAuth(0, new 
File(getTestDirectory()), 5000, TEST_USERNAME, TEST_PASSWORD);
+        baseUri = AbstractProviderTestConfig.getLocalHostUriString("http4", 
server.getPort());
+    }
+
+    @AfterAll
+    static void stopServer() throws InterruptedException {
+        if (server != null) {
+            server.shutdown(5, TimeUnit.SECONDS);
+        }
+    }
+
+    private FileSystemOptions authOptions() throws FileSystemException {
+        final FileSystemOptions opts = new FileSystemOptions();
+        final StaticUserAuthenticator auth = new StaticUserAuthenticator(null, 
TEST_USERNAME, TEST_PASSWORD);
+        
DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, auth);
+        return opts;
+    }
+
+    @Test
+    void testResolveFile() throws FileSystemException {
+        final FileSystemOptions opts = authOptions();
+        try (FileObject file = VFS.getManager().resolveFile(baseUri + 
"/read-tests/file1.txt", opts)) {
+            assertNotNull(file);
+            assertTrue(file.exists(), "file1.txt should exist");
+            assertEquals(FileType.FILE, file.getType());
+            assertNotNull(file.getContent(), "Content should be readable when 
credentials are correct");
+        }
+    }
+
+    @Test
+    void testResolveFolder() throws FileSystemException {
+        final FileSystemOptions opts = authOptions();
+        try (FileObject folder = VFS.getManager().resolveFile(baseUri + 
"/read-tests", opts)) {
+            assertNotNull(folder);
+            assertTrue(folder.exists(), "read-tests folder should exist");
+        }
+    }
+
+    @Test
+    void testResolveFolderWithTrailingSlash() throws FileSystemException {
+        final FileSystemOptions opts = authOptions();
+        try (FileObject folder = VFS.getManager().resolveFile(baseUri + 
"/read-tests/", opts)) {
+            assertNotNull(folder);
+            assertTrue(folder.exists(), "read-tests/ folder should exist");
+        }
+    }
+
+    @Test
+    void testUnauthenticatedRequestReturns401() {
+        assertThrows(FileSystemException.class, () -> 
VFS.getManager().resolveFile(baseUri + "/read-tests/file1.txt", new 
FileSystemOptions()).exists(),
+                "Expected FileSystemException when accessing a resource 
without credentials");
+    }
+}
diff --git 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http5/Http5BasicAuthTest.java
 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http5/Http5BasicAuthTest.java
new file mode 100644
index 000000000..2dbe827f3
--- /dev/null
+++ 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http5/Http5BasicAuthTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.vfs2.provider.http5;
+
+import static org.apache.commons.vfs2.VfsTestUtils.getTestDirectory;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.vfs2.*;
+import org.apache.commons.vfs2.auth.StaticUserAuthenticator;
+import org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder;
+import org.apache.commons.vfs2.util.NHttpFileServer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests HTTP Basic Authentication 
https://issues.apache.org/jira/browse/VFS-861.
+ */
+public class Http5BasicAuthTest {
+
+    private static final String TEST_USERNAME = "USER";
+
+    private static final String TEST_PASSWORD = "PWD";
+
+    private static NHttpFileServer server;
+
+    private static String baseUri;
+
+    @BeforeAll
+    static void startServer() throws Exception {
+        server = NHttpFileServer.startWithBasicAuth(0, new 
File(getTestDirectory()), 5000, TEST_USERNAME, TEST_PASSWORD);
+        baseUri = AbstractProviderTestConfig.getLocalHostUriString("http5", 
server.getPort());
+    }
+
+    @AfterAll
+    static void stopServer() throws InterruptedException {
+        if (server != null) {
+            server.shutdown(5, TimeUnit.SECONDS);
+        }
+    }
+
+    private FileSystemOptions authOptions() throws FileSystemException {
+        final FileSystemOptions opts = new FileSystemOptions();
+        final StaticUserAuthenticator auth = new StaticUserAuthenticator(null, 
TEST_USERNAME, TEST_PASSWORD);
+        
DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, auth);
+        return opts;
+    }
+
+    @Test
+    void testResolveFile() throws FileSystemException {
+        final FileSystemOptions opts = authOptions();
+        try (FileObject file = VFS.getManager().resolveFile(baseUri + 
"/read-tests/file1.txt", opts)) {
+            assertNotNull(file);
+            assertTrue(file.exists(), "file1.txt should exist");
+            assertEquals(FileType.FILE, file.getType());
+            assertNotNull(file.getContent(), "Content should be readable when 
credentials are correct");
+        }
+    }
+
+    @Test
+    void testResolveFolder() throws FileSystemException {
+        final FileSystemOptions opts = authOptions();
+        try (FileObject folder = VFS.getManager().resolveFile(baseUri + 
"/read-tests", opts)) {
+            assertNotNull(folder);
+            assertTrue(folder.exists(), "read-tests folder should exist");
+        }
+    }
+
+    @Test
+    void testResolveFolderWithTrailingSlash() throws FileSystemException {
+        final FileSystemOptions opts = authOptions();
+        try (FileObject folder = VFS.getManager().resolveFile(baseUri + 
"/read-tests/", opts)) {
+            assertNotNull(folder);
+            assertTrue(folder.exists(), "read-tests/ folder should exist");
+        }
+    }
+
+    @Test
+    void testUnauthenticatedRequestReturns401() {
+        assertThrows(FileSystemException.class, () -> 
VFS.getManager().resolveFile(baseUri + "/read-tests/file1.txt", new 
FileSystemOptions()).exists(),
+                "Expected FileSystemException when accessing a resource 
without credentials");
+    }
+}
diff --git 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java
index bca332ab8..2bd3e26cd 100644
--- 
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java
+++ 
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java
@@ -23,11 +23,13 @@ import java.net.InetSocketAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.security.KeyManagementException;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
+import java.util.Base64;
 import java.util.Date;
 import java.util.Locale;
 import java.util.concurrent.ExecutionException;
@@ -56,12 +58,14 @@ import 
org.apache.hc.core5.http.nio.entity.AsyncEntityProducers;
 import org.apache.hc.core5.http.nio.entity.NoopEntityConsumer;
 import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
 import org.apache.hc.core5.http.nio.ssl.FixedPortStrategy;
+import org.apache.hc.core5.http.nio.support.AbstractAsyncServerAuthFilter;
 import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder;
 import org.apache.hc.core5.http.nio.support.BasicRequestConsumer;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
 import org.apache.hc.core5.http.protocol.HttpDateGenerator;
 import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.net.URIAuthority;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOReactorStatus;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
@@ -73,6 +77,38 @@ import org.apache.hc.core5.util.TimeValue;
  */
 public final class NHttpFileServer {
 
+    private static final class BasicAuthFilter extends 
AbstractAsyncServerAuthFilter<String> {
+
+        private final String expectedCredentials;
+
+        private final String realm;
+
+        BasicAuthFilter(final String username, final String password, final 
String realm) {
+            super(true);
+            this.expectedCredentials = username + ":" + password;
+            this.realm = realm;
+        }
+
+        @Override
+        protected boolean authenticate(final String challengeResponse, final 
URIAuthority authority, final String requestUri, final HttpContext context) {
+            return expectedCredentials.equals(challengeResponse);
+        }
+
+        @Override
+        protected String generateChallenge(final String challengeResponse, 
final URIAuthority authority, final String requestUri, final HttpContext 
context) {
+            return "Basic realm=\"" + realm + "\"";
+        }
+
+        @Override
+        protected String parseChallengeResponse(final String 
authorizationValue, final HttpContext context) throws HttpException {
+            if (authorizationValue == null || 
!authorizationValue.startsWith("Basic ")) {
+                return null;
+            }
+            final byte[] decoded = 
Base64.getDecoder().decode(authorizationValue.substring(6));
+            return new String(decoded, StandardCharsets.UTF_8);
+        }
+    }
+
     private static class HttpFileHandler implements 
AsyncServerRequestHandler<Message<HttpRequest, Void>> {
 
         private final File docRoot;
@@ -90,7 +126,6 @@ public final class NHttpFileServer {
             if (!method.equals("GET") && !method.equals("HEAD") && 
!method.equals("POST")) {
                 throw new MethodNotSupportedException(method + " method not 
supported");
             }
-
             final URI requestUri;
             try {
                 requestUri = request.getUri();
@@ -101,22 +136,18 @@ public final class NHttpFileServer {
             final File file = new File(docRoot, path);
             final ContentType mimeType = ContentType.TEXT_HTML;
             if (!file.exists()) {
-
                 final String msg = "File " + file.getPath() + " not found";
                 println(msg);
                 responseTrigger.submitResponse(
                         
AsyncResponseBuilder.create(HttpStatus.SC_NOT_FOUND).setEntity("<html><body><h1>"
 + msg + "</h1></body></html>", mimeType).build(),
                         context);
-
             } else if (!file.canRead()) {
                 final String msg = "Cannot read file " + file.getPath();
                 println(msg);
                 responseTrigger.submitResponse(
                         
AsyncResponseBuilder.create(HttpStatus.SC_FORBIDDEN).setEntity("<html><body><h1>"
 + msg + "</h1></body></html>", mimeType).build(),
                         context);
-
             } else {
-
                 ContentType contentType;
                 final String fileName = 
file.getName().toLowerCase(Locale.ROOT);
 // The following causes a failure on Linux and macOS in HttpProviderTestCase:
@@ -134,9 +165,7 @@ public final class NHttpFileServer {
                 contentType = ContentType.TEXT_HTML;
                 final HttpCoreContext coreContext = 
HttpCoreContext.adapt(context);
                 final EndpointDetails endpoint = 
coreContext.getEndpointDetails();
-
                 println(endpoint + " | serving file " + file.getPath());
-
                 // @formatter:off
                 responseTrigger.submitResponse(
                     AsyncResponseBuilder.create(HttpStatus.SC_OK)
@@ -154,7 +183,6 @@ public final class NHttpFileServer {
                 throws HttpException {
             return new BasicRequestConsumer<>(entityDetails != null ? new 
NoopEntityConsumer() : null);
         }
-
     }
 
     public static final boolean DEBUG = 
Boolean.getBoolean(NHttpFileServer.class.getSimpleName() + ".debug");
@@ -184,12 +212,24 @@ public final class NHttpFileServer {
         return new NHttpFileServer(port, docRoot).start();
     }
 
+    public static NHttpFileServer startWithBasicAuth(final int port, final 
File docRoot, final long waitMillis, final String username, final String 
password)
+            throws KeyManagementException, UnrecoverableKeyException, 
NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException,
+            InterruptedException, ExecutionException {
+        return new NHttpFileServer(port, docRoot).withBasicAuth(username, 
password).start();
+    }
+
     private final File docRoot;
 
     private ListenerEndpoint listenerEndpoint;
 
     private final int port;
 
+    private String authRealm;
+
+    private String authUsername;
+
+    private String authPassword;
+
     private HttpAsyncServer server;
 
     private NHttpFileServer(final int port, final File docRoot) {
@@ -222,7 +262,6 @@ public final class NHttpFileServer {
             server.initiateShutdown();
             server.awaitShutdown(TimeValue.of(gracePeriod, timeUnit));
         }
-
     }
 
     private NHttpFileServer start() throws KeyManagementException, 
UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, 
CertificateException,
@@ -240,23 +279,19 @@ public final class NHttpFileServer {
             sslContext = SSLContexts.custom().loadKeyMaterial(url, 
"nopassword".toCharArray(), "nopassword".toCharArray()).build();
             bootstrap.setTlsStrategy(new BasicServerTlsStrategy(sslContext, 
new FixedPortStrategy(port)));
         }
-
         // @formatter:off
         final IOReactorConfig config = IOReactorConfig.custom()
                 .setSoTimeout(15, TimeUnit.SECONDS)
                 .setTcpNoDelay(true)
                 .build();
         // @formatter:on
-
-        server = bootstrap
-                .setExceptionCallback(Exception::printStackTrace)
-                .setIOReactorConfig(config)
-                .register("*", new HttpFileHandler(docRoot)).create();
-
+        
bootstrap.setExceptionCallback(Exception::printStackTrace).setIOReactorConfig(config).register("*",
 new HttpFileHandler(docRoot));
+        if (authUsername != null) {
+            bootstrap.addFilterFirst("auth", new BasicAuthFilter(authUsername, 
authPassword, authRealm));
+        }
+        server = bootstrap.create();
         Runtime.getRuntime().addShutdownHook(new Thread(this::close));
-
         server.start();
-
         final Future<ListenerEndpoint> future = server.listen(new 
InetSocketAddress(port));
         listenerEndpoint = future.get();
         println("Serving " + docRoot + " on " + listenerEndpoint.getAddress()
@@ -264,4 +299,10 @@ public final class NHttpFileServer {
         return this;
     }
 
+    public NHttpFileServer withBasicAuth(final String username, final String 
password) {
+        this.authRealm = "restricted";
+        this.authUsername = username;
+        this.authPassword = password;
+        return this;
+    }
 }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 56fce4ff3..25082af56 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -63,6 +63,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action type="fix" dev="ggregory" due-to="Vaishnavi Kumbhar, Gary 
Gregory" issue="VFS-861">Http5FileProvider Basic authentication fails: 
UserAuthenticationData.setData(Type, char[]) should clone its array 
input.</action>
       <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
UserAuthenticatorUtils.toChar(String).</action>
       <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
UserAuthenticatorUtils.toString(String).</action>
+      <action type="fix" dev="ggregory" due-to="Vaishnavi Kumbhar, Gary 
Gregory" issue="VFS-861">Http5FileProvider Basic authentication fails: 
UserAuthenticationData.setData(Type, char[]) should clone its array 
input.</action>
       <!-- ADD -->
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
org.apache.commons.vfs2.provider.ftp.FTPClientWrapper.sendOptions(String, 
String).</action>
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
FtpFileSystemConfigBuilder.getControlEncodingCharset(FileSystemOptions) and 
deprecate getControlEncoding(FileSystemOptions).</action>

Reply via email to