8355360: -d option of jwebserver command should accept relative paths

Reviewed-by: dfuchs, michaelm
This commit is contained in:
Volkan Yazici 2025-05-13 07:58:36 +00:00 committed by Michael McMahon
parent 526f543adf
commit ad161a4ef8
6 changed files with 144 additions and 76 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
@ -29,11 +29,13 @@ import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.SimpleFileServer;
import com.sun.net.httpserver.SimpleFileServer.OutputLevel;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Iterator;
@ -139,6 +141,7 @@ final class SimpleFileServerImpl {
// configure and start server
try {
root = realPath(root);
var socketAddr = new InetSocketAddress(addr, port);
var server = SimpleFileServer.createFileServer(socketAddr, root, outputLevel);
server.start();
@ -152,6 +155,25 @@ final class SimpleFileServerImpl {
return Startup.OK.statusCode;
}
private static Path realPath(Path root) {
// `toRealPath()` invocation below already checks if file exists, though
// there is no way to figure out if it fails due to a non-existent file.
// Hence, checking the existence here first to deliver the user a more
// descriptive message.
if (!Files.exists(root)) {
throw new IllegalArgumentException("Path does not exist: " + root);
}
// Obtain the real path
try {
return root.toRealPath();
} catch (IOException exception) {
throw new IllegalArgumentException("Path is invalid: " + root, exception);
}
}
private static final class Out {
private final PrintWriter writer;
private Out() { throw new AssertionError(); }

View File

@ -168,16 +168,6 @@ public class CommandLineNegativeTest {
@DataProvider
public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; }
@Test(dataProvider = "directoryOptions")
public void testRootNotAbsolute(String opt) throws Throwable {
out.println("\n--- testRootNotAbsolute, opt=\"%s\" ".formatted(opt));
var root = Path.of(".");
assertFalse(root.isAbsolute());
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, root.toString())
.shouldNotHaveExitValue(0)
.shouldContain("Error: server config failed: " + "Path is not absolute:");
}
@Test(dataProvider = "directoryOptions")
public void testRootNotADirectory(String opt) throws Throwable {
out.println("\n--- testRootNotADirectory, opt=\"%s\" ".formatted(opt));

View File

@ -31,6 +31,7 @@
*/
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
@ -52,19 +53,44 @@ public class CommandLinePositiveTest {
static final Path JAVA_HOME = Path.of(System.getProperty("java.home"));
static final String LOCALE_OPT = "-Duser.language=en -Duser.country=US";
static final String JAVA = getJava(JAVA_HOME);
static final Path CWD = Path.of(".").toAbsolutePath().normalize();
static final Path TEST_DIR = CWD.resolve("CommandLinePositiveTest");
static final Path TEST_FILE = TEST_DIR.resolve("file.txt");
static final String TEST_DIR_STR = TEST_DIR.toString();
/**
* The <b>real path</b> to the current working directory where
* <ol>
* <li>the web server process will be started in,</li>
* <li>and hence, unless given an explicit content root directory, the web
* server will be serving from.</li>
* </ol>
*/
private static final Path CWD;
static {
try {
CWD = Path.of(".").toRealPath();
} catch (IOException exception) {
throw new UncheckedIOException(exception);
}
}
private static final String CWD_STR = CWD.toString();
/**
* The <b>real path</b> to the web server content root directory, if one
* needs to be provided explicitly.
*/
private static final Path ROOT_DIR = CWD.resolve("www");
private static final String ROOT_DIR_STR = ROOT_DIR.toString();
static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress();
@BeforeTest
public void setup() throws IOException {
if (Files.exists(TEST_DIR)) {
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
if (Files.exists(ROOT_DIR)) {
FileUtils.deleteFileTreeWithRetry(ROOT_DIR);
}
Files.createDirectories(TEST_DIR);
Files.createFile(TEST_FILE);
Files.createDirectories(ROOT_DIR);
Files.createFile(ROOT_DIR.resolve("file.txt"));
}
static final int SIGTERM = 15;
@ -83,12 +109,23 @@ public class CommandLinePositiveTest {
public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; }
@Test(dataProvider = "directoryOptions")
public void testDirectory(String opt) throws Throwable {
out.println("\n--- testDirectory, opt=\"%s\" ".formatted(opt));
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, TEST_DIR_STR)
public void testAbsDirectory(String opt) throws Throwable {
out.printf("\n--- testAbsDirectory, opt=\"%s\"%n", opt);
testDirectory(opt, ROOT_DIR_STR);
}
@Test(dataProvider = "directoryOptions")
public void testRelDirectory(String opt) throws Throwable {
out.printf("\n--- testRelDirectory, opt=\"%s\"%n", opt);
Path rootRelDir = CWD.relativize(ROOT_DIR);
testDirectory(opt, rootRelDir.toString());
}
private static void testDirectory(String opt, String rootDir) throws Throwable {
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, rootDir)
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + ROOT_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -101,7 +138,7 @@ public class CommandLinePositiveTest {
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, "0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -155,12 +192,12 @@ public class CommandLinePositiveTest {
out.println("\n--- testBindAllInterfaces, opt=\"%s\" ".formatted(opt));
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, "0.0.0.0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("URL http://" + InetAddress.getLocalHost().getHostAddress());
if (IPSupport.hasIPv6()) {
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, "::0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("URL http://" + InetAddress.getLocalHost().getHostAddress());
}
}
@ -170,7 +207,7 @@ public class CommandLinePositiveTest {
out.println("\n--- testLastOneWinsBindAddress, opt=\"%s\" ".formatted(opt));
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, "123.4.5.6", opt, LOOPBACK_ADDR)
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -178,10 +215,10 @@ public class CommandLinePositiveTest {
@Test(dataProvider = "directoryOptions")
public void testLastOneWinsDirectory(String opt) throws Throwable {
out.println("\n--- testLastOneWinsDirectory, opt=\"%s\" ".formatted(opt));
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, TEST_DIR_STR, opt, TEST_DIR_STR)
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, ROOT_DIR_STR, opt, ROOT_DIR_STR)
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + ROOT_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -194,7 +231,7 @@ public class CommandLinePositiveTest {
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, "none", opt, "verbose")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -204,14 +241,14 @@ public class CommandLinePositiveTest {
simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, "-999", opt, "0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@AfterTest
public void teardown() throws IOException {
if (Files.exists(TEST_DIR)) {
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
if (Files.exists(ROOT_DIR)) {
FileUtils.deleteFileTreeWithRetry(ROOT_DIR);
}
}
@ -247,7 +284,7 @@ public class CommandLinePositiveTest {
StringBuffer sb = new StringBuffer(); // stdout & stderr
// start the process and await the waitForLine before returning
var p = ProcessTools.startProcess("simpleserver",
new ProcessBuilder(args).directory(TEST_DIR.toFile()),
new ProcessBuilder(args),
line -> sb.append(line + "\n"),
line -> line.startsWith(waitForLine.value),
30, // suitably high default timeout, not expected to timeout

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
@ -668,14 +668,6 @@ public class SimpleFileServerTest {
@Test
public void testIllegalPath() throws Exception {
var addr = LOOPBACK_ADDR;
{ // not absolute
Path p = Path.of(".");
assert Files.isDirectory(p);
assert Files.exists(p);
assert !p.isAbsolute();
var iae = expectThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO));
assertTrue(iae.getMessage().contains("is not absolute"));
}
{ // not a directory
Path p = Files.createFile(TEST_DIR.resolve("aFile"));
assert !Files.isDirectory(p);

View File

@ -168,16 +168,6 @@ public class CommandLineNegativeTest {
@DataProvider
public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; }
@Test(dataProvider = "directoryOptions")
public void testRootNotAbsolute(String opt) throws Throwable {
out.println("\n--- testRootNotAbsolute, opt=\"%s\" ".formatted(opt));
var root = Path.of(".");
assertFalse(root.isAbsolute());
simpleserver(JWEBSERVER, LOCALE_OPT, opt, root.toString())
.shouldNotHaveExitValue(0)
.shouldContain("Error: server config failed: " + "Path is not absolute:");
}
@Test(dataProvider = "directoryOptions")
public void testRootNotADirectory(String opt) throws Throwable {
out.println("\n--- testRootNotADirectory, opt=\"%s\" ".formatted(opt));

View File

@ -31,6 +31,7 @@
*/
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
@ -52,19 +53,44 @@ public class CommandLinePositiveTest {
static final Path JAVA_HOME = Path.of(System.getProperty("java.home"));
static final String LOCALE_OPT = "-J-Duser.language=en -J-Duser.country=US";
static final String JWEBSERVER = getJwebserver(JAVA_HOME);
static final Path CWD = Path.of(".").toAbsolutePath().normalize();
static final Path TEST_DIR = CWD.resolve("CommandLinePositiveTest");
static final Path TEST_FILE = TEST_DIR.resolve("file.txt");
static final String TEST_DIR_STR = TEST_DIR.toString();
/**
* The <b>real path</b> to the current working directory where
* <ol>
* <li>the web server process will be started in,</li>
* <li>and hence, unless given an explicit content root directory, the web
* server will be serving from.</li>
* </ol>
*/
private static final Path CWD;
static {
try {
CWD = Path.of(".").toRealPath();
} catch (IOException exception) {
throw new UncheckedIOException(exception);
}
}
private static final String CWD_STR = CWD.toString();
/**
* The <b>real path</b> to the web server content root directory, if one
* needs to be provided explicitly.
*/
private static final Path ROOT_DIR = CWD.resolve("www");
private static final String ROOT_DIR_STR = ROOT_DIR.toString();
static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress();
@BeforeTest
public void setup() throws IOException {
if (Files.exists(TEST_DIR)) {
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
if (Files.exists(ROOT_DIR)) {
FileUtils.deleteFileTreeWithRetry(ROOT_DIR);
}
Files.createDirectories(TEST_DIR);
Files.createFile(TEST_FILE);
Files.createDirectories(ROOT_DIR);
Files.createFile(ROOT_DIR.resolve("file.txt"));
}
static final int SIGTERM = 15;
@ -83,12 +109,23 @@ public class CommandLinePositiveTest {
public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; }
@Test(dataProvider = "directoryOptions")
public void testDirectory(String opt) throws Throwable {
out.println("\n--- testDirectory, opt=\"%s\" ".formatted(opt));
simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, TEST_DIR_STR)
public void testAbsDirectory(String opt) throws Throwable {
out.printf("\n--- testAbsDirectory, opt=\"%s\"%n", opt);
testDirectory(opt, ROOT_DIR_STR);
}
@Test(dataProvider = "directoryOptions")
public void testRelDirectory(String opt) throws Throwable {
out.printf("\n--- testRelDirectory, opt=\"%s\"%n", opt);
Path rootRelDir = CWD.relativize(ROOT_DIR);
testDirectory(opt, rootRelDir.toString());
}
private static void testDirectory(String opt, String rootDir) throws Throwable {
simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, rootDir)
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + ROOT_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -101,7 +138,7 @@ public class CommandLinePositiveTest {
simpleserver(JWEBSERVER, LOCALE_OPT, opt, "0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -155,12 +192,12 @@ public class CommandLinePositiveTest {
out.println("\n--- testBindAllInterfaces, opt=\"%s\" ".formatted(opt));
simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, "0.0.0.0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("URL http://" + InetAddress.getLocalHost().getHostAddress());
if (IPSupport.hasIPv6()) {
simpleserver(JWEBSERVER, LOCALE_OPT, opt, "::0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on 0.0.0.0 (all interfaces) port")
.shouldContain("URL http://" + InetAddress.getLocalHost().getHostAddress());
}
}
@ -170,7 +207,7 @@ public class CommandLinePositiveTest {
out.println("\n--- testLastOneWinsBindAddress, opt=\"%s\" ".formatted(opt));
simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, "123.4.5.6", opt, LOOPBACK_ADDR)
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -178,10 +215,10 @@ public class CommandLinePositiveTest {
@Test(dataProvider = "directoryOptions")
public void testLastOneWinsDirectory(String opt) throws Throwable {
out.println("\n--- testLastOneWinsDirectory, opt=\"%s\" ".formatted(opt));
simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, TEST_DIR_STR, opt, TEST_DIR_STR)
simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, CWD_STR, opt, CWD_STR)
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -194,7 +231,7 @@ public class CommandLinePositiveTest {
simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, "none", opt, "verbose")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@ -204,14 +241,14 @@ public class CommandLinePositiveTest {
simpleserver(JWEBSERVER, LOCALE_OPT, opt, "-999", opt, "0")
.shouldHaveExitValue(NORMAL_EXIT_CODE)
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
.shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("Serving " + CWD_STR + " and subdirectories on " + LOOPBACK_ADDR + " port")
.shouldContain("URL http://" + LOOPBACK_ADDR);
}
@AfterTest
public void teardown() throws IOException {
if (Files.exists(TEST_DIR)) {
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
if (Files.exists(ROOT_DIR)) {
FileUtils.deleteFileTreeWithRetry(ROOT_DIR);
}
}
@ -247,7 +284,7 @@ public class CommandLinePositiveTest {
StringBuffer sb = new StringBuffer(); // stdout & stderr
// start the process and await the waitForLine before returning
var p = ProcessTools.startProcess("simpleserver",
new ProcessBuilder(args).directory(TEST_DIR.toFile()),
new ProcessBuilder(args),
line -> sb.append(line + "\n"),
line -> line.startsWith(waitForLine.value),
30, // suitably high default timeout, not expected to timeout