java-topology/test/jdk/sun/net/www/protocol/file/NonLocalFtpFallback.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

187 lines
7 KiB
Java

/*
* 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.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @test
* @bug 8353662 8202708
* @summary Verify long-standing, disabled by default behavior of resolving non-local
* file URLs using FTP.
* @run junit/othervm -Djdk.net.file.ftpfallback=true 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<URI> 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://remotehost/path'. Since the
* host component 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());
}
/**
* Sanity check that a local file URL (with a host component equal to 'localhost')
* does not open any FtpURLConnection when the FTP fallback feature is enabled.
*
* @throws Exception
*/
@Test
public void verifyLocalFileURL() throws Exception {
URL localURL = file.toUri().toURL();
URL nonLocalURL = new URL("file", "localhost", localURL.getFile());
// Open the local file: URL connection supplying 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 read
try (InputStream in = con.getInputStream()) {
byte[] retrived = in.readAllBytes();
assertArrayEquals(Files.readAllBytes(file), retrived);
}
// Assert that no FTP URIs were requested in the HTTP proxy
assertEquals(0, uris.size());
}
/**
* Verify that opening a stream on a non-proxy URLConnection for a non-local
* file URL with an unknown host fails with UnknownHostException
* when the fallback FtpURLConnection attempts to connect to the non-existing
* FTP server.
*/
@Test
public void verifyFtpUnknownHost() throws IOException {
URL url = new URL("file://nonexistinghost/not-exist.txt");
assertThrows(UnknownHostException.class, () -> {
InputStream in = url.openConnection(Proxy.NO_PROXY).getInputStream();
});
}
}