diff --git a/make/modules/jdk.httpserver/Java.gmk b/make/modules/jdk.httpserver/Java.gmk index d7b2b2f70ca..799ff8695a7 100644 --- a/make/modules/jdk.httpserver/Java.gmk +++ b/make/modules/jdk.httpserver/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2024, 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 @@ -24,3 +24,5 @@ # DISABLED_WARNINGS_java += missing-explicit-ctor this-escape + +COPY += .ico diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java index 6d217174fdd..81ddb58eecf 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, 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 @@ -57,6 +57,9 @@ public final class FileServerHandler implements HttpHandler { private static final List SUPPORTED_METHODS = List.of("HEAD", "GET"); private static final List UNSUPPORTED_METHODS = List.of("CONNECT", "DELETE", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"); + private static final String FAVICON_RESOURCE_PATH = + "/sun/net/httpserver/simpleserver/resources/favicon.ico"; + private static final String FAVICON_LAST_MODIFIED = "Mon, 23 May 1995 11:11:11 GMT"; private final Path root; private final UnaryOperator mimeTable; @@ -250,6 +253,31 @@ public final class FileServerHandler implements HttpHandler { return Files.exists(html) ? html : Files.exists(htm) ? htm : null; } + private static boolean isFavIconRequest(HttpExchange exchange) { + return "/favicon.ico".equals(exchange.getRequestURI().getPath()); + } + + private void serveDefaultFavIcon(HttpExchange exchange, boolean writeBody) + throws IOException + { + var respHdrs = exchange.getResponseHeaders(); + try (var stream = getClass().getModule().getResourceAsStream(FAVICON_RESOURCE_PATH)) { + var bytes = stream.readAllBytes(); + respHdrs.set("Content-Type", "image/x-icon"); + respHdrs.set("Last-Modified", FAVICON_LAST_MODIFIED); + if (writeBody) { + exchange.sendResponseHeaders(200, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } else { + respHdrs.set("Content-Length", Integer.toString(bytes.length)); + exchange.sendResponseHeaders(200, -1); + } + } + } + + private void serveFile(HttpExchange exchange, Path path, boolean writeBody) throws IOException { @@ -371,17 +399,26 @@ public final class FileServerHandler implements HttpHandler { assert List.of("GET", "HEAD").contains(exchange.getRequestMethod()); try (exchange) { discardRequestBody(exchange); + boolean isHeadRequest = exchange.getRequestMethod().equals("HEAD"); Path path = mapToPath(exchange, root); if (path != null) { exchange.setAttribute("request-path", path.toString()); // store for OutputFilter if (!Files.exists(path) || !Files.isReadable(path) || isHiddenOrSymLink(path)) { handleNotFound(exchange); - } else if (exchange.getRequestMethod().equals("HEAD")) { + } else if (isHeadRequest) { handleHEAD(exchange, path); } else { handleGET(exchange, path); } } else { + if (isFavIconRequest(exchange)) { + try { + serveDefaultFavIcon(exchange, !isHeadRequest); + return; + } catch (IOException ignore) { + // fall through to send the not-found response + } + } exchange.setAttribute("request-path", "could not resolve request URI path"); handleNotFound(exchange); } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/favicon.ico b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/favicon.ico new file mode 100644 index 00000000000..f24905abf62 Binary files /dev/null and b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/favicon.ico differ diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java index b3134640925..3206b62188a 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -76,6 +76,8 @@ public class SimpleFileServerTest { 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"; + @BeforeTest public void setup() throws IOException { if (ENABLE_LOGGING) { @@ -142,6 +144,65 @@ public class SimpleFileServerTest { } } + @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(response.statusCode(), 200); + assertEquals(response.headers().firstValue("content-type").get(), "image/x-icon"); + assertEquals(response.headers().firstValue("last-modified").get(), EXPECTED_LAST_MODIFIED_OF_FAVICON); + + // 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(response.statusCode(), 200); + assertEquals(response.headers().firstValue("content-type").get(), "application/octet-stream"); + assertEquals(response.headers().firstValue("content-length").get(), expectedLength); + assertEquals(response.headers().firstValue("last-modified").get(), lastModified); + } finally { + Files.delete(file); + } + + // expect built-in icon + response = client.send(request, BodyHandlers.ofString()); + assertEquals(response.statusCode(), 200); + assertEquals(response.headers().firstValue("content-type").get(), "image/x-icon"); + assertEquals(response.headers().firstValue("last-modified").get(), EXPECTED_LAST_MODIFIED_OF_FAVICON); + } 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(response.statusCode(), 200); + assertEquals(response.headers().firstValue("content-type").get(), "image/x-icon"); + assertEquals(response.headers().firstValue("last-modified").get(), EXPECTED_LAST_MODIFIED_OF_FAVICON); + assertEquals(response.body(), ""); + } finally { + server.stop(0); + } + } + @Test public void testFileHEAD() throws Exception { var root = Files.createDirectory(TEST_DIR.resolve("testFileHEAD"));