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.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
|
|
@ -0,0 +1,769 @@
|
|||
/*
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Tests for SimpleFileServer
|
||||
* @library /test/lib
|
||||
* @build jdk.test.lib.Platform jdk.test.lib.net.URIBuilder
|
||||
* @run junit/othervm SimpleFileServerTest
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpRequest.BodyPublishers;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import com.sun.net.httpserver.SimpleFileServer;
|
||||
import com.sun.net.httpserver.SimpleFileServer.OutputLevel;
|
||||
import jdk.test.lib.Platform;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
import jdk.test.lib.util.FileUtils;
|
||||
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
public class SimpleFileServerTest {
|
||||
|
||||
static final Class<NullPointerException> NPE = NullPointerException.class;
|
||||
static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
|
||||
static final Class<UncheckedIOException> UIOE = UncheckedIOException.class;
|
||||
|
||||
static final Path CWD = Path.of(".").toAbsolutePath();
|
||||
static final Path TEST_DIR = CWD.resolve("SimpleFileServerTest");
|
||||
|
||||
static final InetSocketAddress LOOPBACK_ADDR = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
|
||||
|
||||
static final boolean ENABLE_LOGGING = true;
|
||||
static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver");
|
||||
|
||||
static final String EXPECTED_LAST_MODIFIED_OF_FAVICON = "Mon, 23 May 1995 11:11:11 GMT";
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws IOException {
|
||||
if (ENABLE_LOGGING) {
|
||||
ConsoleHandler ch = new ConsoleHandler();
|
||||
LOGGER.setLevel(Level.ALL);
|
||||
ch.setLevel(Level.ALL);
|
||||
LOGGER.addHandler(ch);
|
||||
}
|
||||
if (Files.exists(TEST_DIR)) {
|
||||
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
|
||||
}
|
||||
Files.createDirectories(TEST_DIR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileGET() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testFileGET"));
|
||||
var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE);
|
||||
var lastModified = getLastModified(file);
|
||||
var expectedLength = Long.toString(Files.size(file));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("some text", response.body());
|
||||
assertEquals("text/plain", response.headers().firstValue("content-type").get());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(lastModified, response.headers().firstValue("last-modified").get());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryGET() throws Exception {
|
||||
var expectedBody = openHTML + """
|
||||
<h1>Directory listing for /</h1>
|
||||
<ul>
|
||||
<li><a href="aFile.txt">aFile.txt</a></li>
|
||||
</ul>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testDirectoryGET"));
|
||||
var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE);
|
||||
var lastModified = getLastModified(root);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(lastModified, response.headers().firstValue("last-modified").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFavIconGET() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testFavIconGET"));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
// expect built-in icon
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "favicon.ico")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("image/x-icon", response.headers().firstValue("content-type").get());
|
||||
assertEquals(EXPECTED_LAST_MODIFIED_OF_FAVICON, response.headers().firstValue("last-modified").get());
|
||||
|
||||
// expect custom (and broken) icon
|
||||
var file = Files.writeString(root.resolve("favicon.ico"), "broken icon", CREATE);
|
||||
try {
|
||||
var lastModified = getLastModified(file);
|
||||
var expectedLength = Long.toString(Files.size(file));
|
||||
response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("application/octet-stream", response.headers().firstValue("content-type").get());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(lastModified, response.headers().firstValue("last-modified").get());
|
||||
} finally {
|
||||
Files.delete(file);
|
||||
}
|
||||
|
||||
// expect built-in icon
|
||||
response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("image/x-icon", response.headers().firstValue("content-type").get());
|
||||
assertEquals(EXPECTED_LAST_MODIFIED_OF_FAVICON, response.headers().firstValue("last-modified").get());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFavIconHEAD() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testFavIconHEAD"));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "favicon.ico"))
|
||||
.method("HEAD", BodyPublishers.noBody()).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("image/x-icon", response.headers().firstValue("content-type").get());
|
||||
assertEquals(EXPECTED_LAST_MODIFIED_OF_FAVICON, response.headers().firstValue("last-modified").get());
|
||||
assertEquals("", response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileHEAD() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testFileHEAD"));
|
||||
var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE);
|
||||
var lastModified = getLastModified(file);
|
||||
var expectedLength = Long.toString(Files.size(file));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "aFile.txt"))
|
||||
.method("HEAD", BodyPublishers.noBody()).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("text/plain", response.headers().firstValue("content-type").get());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(lastModified, response.headers().firstValue("last-modified").get());
|
||||
assertEquals("", response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryHEAD() throws Exception {
|
||||
var expectedLength = Integer.toString(
|
||||
(openHTML + """
|
||||
<h1>Directory listing for /</h1>
|
||||
<ul>
|
||||
<li><a href="aFile.txt">aFile.txt</a></li>
|
||||
</ul>
|
||||
""" + closeHTML).getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testDirectoryHEAD"));
|
||||
var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE);
|
||||
var lastModified = getLastModified(root);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, ""))
|
||||
.method("HEAD", BodyPublishers.noBody()).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(lastModified, response.headers().firstValue("last-modified").get());
|
||||
assertEquals("", response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object[][] indexFiles() {
|
||||
var fileContent = openHTML + """
|
||||
<h1>This is an index file</h1>
|
||||
""" + closeHTML;
|
||||
var dirListing = openHTML + """
|
||||
<h1>Directory listing for /</h1>
|
||||
<ul>
|
||||
</ul>
|
||||
""" + closeHTML;
|
||||
return new Object[][] {
|
||||
{"1", "index.html", "text/html", "116", fileContent, true},
|
||||
{"2", "index.htm", "text/html", "116", fileContent, true},
|
||||
{"3", "index.txt", "text/html; charset=UTF-8", "134", dirListing, false}
|
||||
};
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("indexFiles")
|
||||
public void testDirectoryWithIndexGET(String id,
|
||||
String filename,
|
||||
String contentType,
|
||||
String contentLength,
|
||||
String expectedBody,
|
||||
boolean serveIndexFile) throws Exception {
|
||||
var root = Files.createDirectories(TEST_DIR.resolve("testDirectoryWithIndexGET"+id));
|
||||
var lastModified = getLastModified(root);
|
||||
if (serveIndexFile) {
|
||||
var file = Files.writeString(root.resolve(filename), expectedBody, CREATE);
|
||||
lastModified = getLastModified(file);
|
||||
}
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals(contentType, response.headers().firstValue("content-type").get());
|
||||
assertEquals(contentLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(lastModified, response.headers().firstValue("last-modified").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
if (serveIndexFile) {
|
||||
Files.delete(root.resolve(filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotReadableFileGET() throws Exception {
|
||||
Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows");
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/aFile.txt</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testNotReadableFileGET"));
|
||||
var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE);
|
||||
|
||||
file.toFile().setReadable(false, false);
|
||||
assert !Files.isReadable(file);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
file.toFile().setReadable(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotReadableSegmentGET() throws Exception {
|
||||
Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows");
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/dir/aFile.txt</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testNotReadableSegmentGET"));
|
||||
var dir = Files.createDirectory(root.resolve("dir"));
|
||||
var file = Files.writeString(dir.resolve("aFile.txt"), "some text", CREATE);
|
||||
|
||||
dir.toFile().setReadable(false, false);
|
||||
assert !Files.isReadable(dir);
|
||||
assert Files.isReadable(file);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "dir/aFile.txt")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
dir.toFile().setReadable(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidRequestURIGET() throws Exception {
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/aFile?#.txt</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testInvalidRequestURIGET"));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "aFile?#.txt")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotFoundGET() throws Exception {
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/doesNotExist.txt</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testNotFoundGET"));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotFoundHEAD() throws Exception {
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/doesNotExist.txt</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testNotFoundHEAD"));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt"))
|
||||
.method("HEAD", BodyPublishers.noBody()).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals("", response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymlinkGET() throws Exception {
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/symlink</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testSymlinkGET"));
|
||||
var symlink = root.resolve("symlink");
|
||||
var target = Files.writeString(root.resolve("target.txt"), "some text", CREATE);
|
||||
createSymLink(symlink, target);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "symlink")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymlinkSegmentGET() throws Exception {
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/symlink/aFile.txt</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testSymlinkSegmentGET"));
|
||||
var symlink = root.resolve("symlink");
|
||||
var target = Files.createDirectory(root.resolve("target"));
|
||||
Files.writeString(target.resolve("aFile.txt"), "some text", CREATE);
|
||||
createSymLink(symlink, target);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "symlink/aFile.txt")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createSymLink(Path symlink, Path target) {
|
||||
try {
|
||||
Files.createSymbolicLink(symlink, target);
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
Assumptions.abort("sym link creation not supported");
|
||||
} catch (IOException ioe) {
|
||||
Assumptions.abort("probably insufficient privileges to create sym links (Windows)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHiddenFileGET() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testHiddenFileGET"));
|
||||
var file = createHiddenFile(root);
|
||||
var fileName = file.getFileName().toString();
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/""" + fileName +
|
||||
"""
|
||||
</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, fileName)).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHiddenSegmentGET() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testHiddenSegmentGET"));
|
||||
var file = createFileInHiddenDirectory(root);
|
||||
var expectedBody = openHTML + """
|
||||
<h1>File not found</h1>
|
||||
<p>/.hiddenDirectory/aFile.txt</p>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, ".hiddenDirectory/aFile.txt")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
assertEquals(expectedBody, response.body());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path createHiddenFile(Path root) throws IOException {
|
||||
Path file;
|
||||
if (Platform.isWindows()) {
|
||||
file = Files.createFile(root.resolve("aFile.txt"));
|
||||
Files.setAttribute(file, "dos:hidden", true, LinkOption.NOFOLLOW_LINKS);
|
||||
} else {
|
||||
file = Files.writeString(root.resolve(".aFile.txt"), "some text", CREATE);
|
||||
}
|
||||
assertTrue(Files.isHidden(file));
|
||||
return file;
|
||||
}
|
||||
|
||||
private static Path createFileInHiddenDirectory(Path root) throws IOException {
|
||||
Path dir;
|
||||
Path file;
|
||||
if (Platform.isWindows()) {
|
||||
dir = Files.createDirectory(root.resolve("hiddenDirectory"));
|
||||
Files.setAttribute(dir, "dos:hidden", true, LinkOption.NOFOLLOW_LINKS);
|
||||
} else {
|
||||
dir = Files.createDirectory(root.resolve(".hiddenDirectory"));
|
||||
}
|
||||
file = Files.writeString(dir.resolve("aFile.txt"), "some text", CREATE);
|
||||
assertTrue(Files.isHidden(dir));
|
||||
assertFalse(Files.isHidden(file));
|
||||
return file;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMovedPermanently() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testMovedPermanently"));
|
||||
Files.createDirectory(root.resolve("aDirectory"));
|
||||
var expectedBody = openHTML + """
|
||||
<h1>Directory listing for /aDirectory/</h1>
|
||||
<ul>
|
||||
</ul>
|
||||
""" + closeHTML;
|
||||
var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length);
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
{
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY)
|
||||
.followRedirects(HttpClient.Redirect.NEVER).build();
|
||||
var uri = uri(server, "aDirectory");
|
||||
var request = HttpRequest.newBuilder(uri).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(301, response.statusCode());
|
||||
assertEquals("0", response.headers().firstValue("content-length").get());
|
||||
assertEquals("/aDirectory/", response.headers().firstValue("location").get());
|
||||
|
||||
// tests that query component is preserved during redirect
|
||||
var uri2 = uri(server, "aDirectory", "query");
|
||||
var req2 = HttpRequest.newBuilder(uri2).build();
|
||||
var res2 = client.send(req2, BodyHandlers.ofString());
|
||||
assertEquals(301, res2.statusCode());
|
||||
assertEquals("0", res2.headers().firstValue("content-length").get());
|
||||
assertEquals("/aDirectory/?query", res2.headers().firstValue("location").get());
|
||||
}
|
||||
|
||||
{ // tests that redirect to returned relative URI works
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY)
|
||||
.followRedirects(HttpClient.Redirect.ALWAYS).build();
|
||||
var uri = uri(server, "aDirectory");
|
||||
var request = HttpRequest.newBuilder(uri).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(200, response.statusCode());
|
||||
assertEquals(expectedBody, response.body());
|
||||
assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get());
|
||||
assertEquals(expectedLength, response.headers().firstValue("content-length").get());
|
||||
}
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNull() {
|
||||
final var addr = InetSocketAddress.createUnresolved("foo", 8080);
|
||||
final var path = Path.of("/tmp");
|
||||
final var levl = OutputLevel.INFO;
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileServer(null, null, null));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileServer(null, null, levl));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileServer(null, path, null));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileServer(null, path, levl));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileServer(addr, null, null));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileServer(addr, null, levl));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileServer(addr, path, null));
|
||||
|
||||
assertThrows(NPE, () -> SimpleFileServer.createFileHandler(null));
|
||||
|
||||
assertThrows(NPE, () -> SimpleFileServer.createOutputFilter(null, null));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createOutputFilter(null, levl));
|
||||
assertThrows(NPE, () -> SimpleFileServer.createOutputFilter(System.out, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialSlashContext() {
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, TEST_DIR, OutputLevel.INFO);
|
||||
server.removeContext("/"); // throws if no context
|
||||
server.stop(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBound() {
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, TEST_DIR, OutputLevel.INFO);
|
||||
var boundAddr = server.getAddress();
|
||||
server.stop(0);
|
||||
assertTrue(boundAddr.getAddress() != null);
|
||||
assertTrue(boundAddr.getPort() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalPath() throws Exception {
|
||||
var addr = LOOPBACK_ADDR;
|
||||
{ // not a directory
|
||||
Path p = Files.createFile(TEST_DIR.resolve("aFile"));
|
||||
assert !Files.isDirectory(p);
|
||||
var iae = assertThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO));
|
||||
assertTrue(iae.getMessage().contains("not a directory"));
|
||||
}
|
||||
{ // does not exist
|
||||
Path p = TEST_DIR.resolve("doesNotExist");
|
||||
assert !Files.exists(p);
|
||||
var iae = assertThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO));
|
||||
assertTrue(iae.getMessage().contains("does not exist"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonReadablePath() throws Exception {
|
||||
Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows");
|
||||
var addr = LOOPBACK_ADDR;
|
||||
Path p = Files.createDirectory(TEST_DIR.resolve("aDir"));
|
||||
p.toFile().setReadable(false, false);
|
||||
assert !Files.isReadable(p);
|
||||
try {
|
||||
var iae = assertThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO));
|
||||
assertTrue(iae.getMessage().contains("not readable"));
|
||||
} finally {
|
||||
p.toFile().setReadable(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUncheckedIOException() {
|
||||
var addr = InetSocketAddress.createUnresolved("foo", 8080);
|
||||
assertThrows(UIOE, () -> SimpleFileServer.createFileServer(addr, TEST_DIR, OutputLevel.INFO));
|
||||
assertThrows(UIOE, () -> SimpleFileServer.createFileServer(addr, TEST_DIR, OutputLevel.VERBOSE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXss() throws Exception {
|
||||
var root = Files.createDirectory(TEST_DIR.resolve("testXss"));
|
||||
|
||||
var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE);
|
||||
server.start();
|
||||
try {
|
||||
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
|
||||
var request = HttpRequest.newBuilder(uri(server, "beginDelim%3C%3EEndDelim")).build();
|
||||
var response = client.send(request, BodyHandlers.ofString());
|
||||
assertEquals(404, response.statusCode());
|
||||
assertTrue(response.body().contains("beginDelim%3C%3EEndDelim"));
|
||||
assertTrue(response.body().contains("File not found"));
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void teardown() throws IOException {
|
||||
if (Files.exists(TEST_DIR)) {
|
||||
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
static final String openHTML = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
</head>
|
||||
<body>
|
||||
""";
|
||||
|
||||
static final String closeHTML = """
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
static URI uri(HttpServer server, String path) {
|
||||
return URIBuilder.newBuilder()
|
||||
.host("localhost")
|
||||
.port(server.getAddress().getPort())
|
||||
.scheme("http")
|
||||
.path("/" + path)
|
||||
.buildUnchecked();
|
||||
}
|
||||
|
||||
static URI uri(HttpServer server, String path, String query) {
|
||||
return URIBuilder.newBuilder()
|
||||
.host("localhost")
|
||||
.port(server.getAddress().getPort())
|
||||
.scheme("http")
|
||||
.path("/" + path)
|
||||
.query(query)
|
||||
.buildUnchecked();
|
||||
}
|
||||
|
||||
static String getLastModified(Path path) throws IOException {
|
||||
return Files.getLastModifiedTime(path).toInstant().atZone(ZoneId.of("GMT"))
|
||||
.format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue