diff --git a/test/jdk/sun/net/www/protocol/file/NonLocalFtpFallback.java b/test/jdk/sun/net/www/protocol/file/NonLocalFtpFallback.java new file mode 100644 index 0000000000000..49d09cfb507a2 --- /dev/null +++ b/test/jdk/sun/net/www/protocol/file/NonLocalFtpFallback.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @test + * @bug 8353662 + * @summary Verify long-standing behavior of resolving non-local file URLs using FTP. + * @run junit NonLocalFtpFallback + */ + +public class NonLocalFtpFallback { + + // Port 21 may not be available, use an HTTP proxy with an ephemeral port + private HttpServer proxyServer; + + // The file requested in this test + private Path file; + + // FTP URIs requested by the proxy client + private Set uris = new HashSet<>(); + + /** + * Set up the HTTP proxy used for serving FTP in this test + * + * @throws IOException if an unexpected IO error occurs + */ + @BeforeEach + public void setup() throws IOException { + // Create a file with some random data + byte[] data = new byte[512]; + new Random().nextBytes(data); + file = Files.write(Path.of("ftp-file.txt"), data); + + // Set up an HTTP proxy server + proxyServer = HttpServer.create(); + // Bind to the loopback address with an ephemeral port + InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); + proxyServer.bind(new InetSocketAddress(loopbackAddress, 0), 0); + // Handler for the FTP proxy request + proxyServer.createContext("/", new HttpHandler() { + @Override + public void handle(HttpExchange exchange) throws IOException { + // Record the URI requested + uris.add(exchange.getRequestURI()); + // Send the data + exchange.sendResponseHeaders(200, Files.size(file)); + try (OutputStream out = exchange.getResponseBody()) { + Files.copy(file, out); + } + // Complete the exchange + exchange.close(); + } + }); + // Start the proxy server + proxyServer.start(); + } + + /** + * Shut down proxy server and clean up files created + * + * @throws IOException if an unexpected IO error occurs + */ + @AfterEach + public void destroy() throws IOException { + proxyServer.stop(2); + Files.delete(file); + } + + /** + * Verifies the long-standing and unspecified FTP fallback feature where the file + * URL scheme handler attempts an FTP connection for non-local files. + * + * The non-local file URL used here is of the form file://127.0.0.1/path. Since the + * host component here is not equal to "localhost", this is considered a non-local + * URL. + * + * @throws Exception + */ + @Test + public void verifyNonLocalFtpFallback() throws Exception { + URL localURL = file.toUri().toURL(); + // We can use a fake host name here, no actual FTP request will be made + String hostname = "remotehost"; + URL nonLocalURL = new URL("file", hostname, localURL.getFile()); + + // Open the non-local file: URL connection using a proxy + Proxy proxy = new Proxy(Proxy.Type.HTTP, + new InetSocketAddress(proxyServer.getAddress().getAddress(), + proxyServer.getAddress().getPort())); + URLConnection con = nonLocalURL.openConnection(proxy); + + // Assert that the expected file content is retrieved + try (InputStream in = con.getInputStream()) { + byte[] retrived = in.readAllBytes(); + assertArrayEquals(Files.readAllBytes(file), retrived); + } + + // Assert that the expected FTP URI was requested in the HTTP proxy + assertEquals(1, uris.size()); + URL ftpURL = new URL("ftp", hostname, localURL.getFile()); + assertEquals(ftpURL.toURI(), uris.iterator().next()); + } +}