From 7b79426a1da5896b0f00cf6e5fb4d2e754149e54 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 8 May 2024 05:48:07 +0000 Subject: [PATCH] 8278353: Provide Duke as default favicon in Simple Web Server Reviewed-by: dfuchs --- make/modules/jdk.httpserver/Java.gmk | 4 +- .../simpleserver/FileServerHandler.java | 41 +++++++++++- .../simpleserver/resources/favicon.ico | Bin 0 -> 15086 bytes .../simpleserver/SimpleFileServerTest.java | 63 +++++++++++++++++- 4 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/favicon.ico 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 0000000000000000000000000000000000000000..f24905abf621fae487224136f14656387d19fc7a GIT binary patch literal 15086 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 HND%%FA}!mI literal 0 HcmV?d00001 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"));