diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java
index 67ac9e98e10..fb10ecb0755 100644
--- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java
+++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java
@@ -284,8 +284,8 @@ public abstract class HttpServer {
* then some implementations may use string prefix matching where
* this context path matches request paths {@code /foo},
* {@code /foo/bar}, or {@code /foobar}. Others may use path prefix
- * matching where {@code /foo} matches only request paths
- * {@code /foo} and {@code /foo/bar}, but not {@code /foobar}.
+ * matching where {@code /foo} matches request paths {@code /foo} and
+ * {@code /foo/bar}, but not {@code /foobar}.
*
* @implNote
* By default, the JDK built-in implementation uses path prefix matching.
diff --git a/src/jdk.httpserver/share/classes/module-info.java b/src/jdk.httpserver/share/classes/module-info.java
index 8f04cc4c7ce..bff221a920e 100644
--- a/src/jdk.httpserver/share/classes/module-info.java
+++ b/src/jdk.httpserver/share/classes/module-info.java
@@ -107,7 +107,7 @@ import com.sun.net.httpserver.*;
* {@code pathPrefix})
*
* The path matching scheme used to route requests to context handlers.
- * Following list of values are allowed by this property.
*@@ -117,7 +117,7 @@ import com.sun.net.httpserver.*; * would match request paths {@code /foo}, {@code /foo/}, and {@code /foo/bar}, * but not {@code /foobar}. *
- {@code stringPrefix}
- *- Request path string must begin with the context path string. For + *
- The request path string must begin with the context path string. For * instance, the context path {@code /foo} would match request paths * {@code /foo}, {@code /foo/}, {@code /foo/bar}, and {@code /foobar}. *
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java index 935416c81b7..6393ca34798 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ContextList.java @@ -40,6 +40,8 @@ class ContextList { // expects the protocol to be lower-cased using ROOT locale, hence: assert ctx.getProtocol().equals(ctx.getProtocol().toLowerCase(Locale.ROOT)); assert ctx.getPath() != null; + // `ContextPathMatcher` expects context paths to be non-empty: + assert !ctx.getPath().isEmpty(); if (contains(ctx)) { throw new IllegalArgumentException("cannot add context to list"); } @@ -199,10 +201,19 @@ class ContextList { } // Is it a path-prefix match? - if (contextPath.charAt(contextPathLength - 1) == '/' - || requestPath.charAt(contextPathLength) == '/') { - return true; - } + assert contextPathLength > 0; + return + // Case 1: The request path starts with the context + // path, but the context path has an extra path + // separator suffix. For instance, the context path is + // `/foo/` and the request path is `/foo/bar`. + contextPath.charAt(contextPathLength - 1) == '/' || + // Case 2: The request path starts with the + // context path, but the request path has an + // extra path separator suffix. For instance, + // context path is `/foo` and the request path + // is `/foo/` or `/foo/bar`. + requestPath.charAt(contextPathLength) == '/'; } diff --git a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java index 20b4f1cd8a3..e1cff62a45f 100644 --- a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java +++ b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java @@ -37,6 +37,7 @@ import static java.net.http.HttpClient.Builder.NO_PROXY; import org.junit.jupiter.api.AfterAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; @@ -72,7 +73,7 @@ import org.junit.jupiter.api.Test; public class ContextPathMatcherPathPrefixTest { - private static final HttpClient CLIENT = + protected static final HttpClient CLIENT = HttpClient.newBuilder().proxy(NO_PROXY).build(); @AfterAll @@ -80,6 +81,12 @@ public class ContextPathMatcherPathPrefixTest { CLIENT.shutdownNow(); } + @Test + void testContextPathOfEmptyString() { + var iae = assertThrows(IllegalArgumentException.class, () -> new Infra("")); + assertEquals("Illegal value for path or protocol", iae.getMessage()); + } + @Test void testContextPathAtRoot() throws Exception { try (var infra = new Infra("/")) { @@ -103,7 +110,7 @@ public class ContextPathMatcherPathPrefixTest { } } - public static final class Infra implements AutoCloseable { + protected static final class Infra implements AutoCloseable { private static final InetSocketAddress LO_SA_0 = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); @@ -114,14 +121,14 @@ public class ContextPathMatcherPathPrefixTest { private final String contextPath; - public Infra(String contextPath) throws IOException { + protected Infra(String contextPath) throws IOException { this.server = HttpServer.create(LO_SA_0, 10); server.createContext(contextPath, HANDLER); server.start(); this.contextPath = contextPath; } - public void expect(int statusCode, String... requestPaths) throws Exception { + protected void expect(int statusCode, String... requestPaths) throws Exception { for (String requestPath : requestPaths) { var requestURI = URI.create("http://%s:%s%s".formatted( server.getAddress().getHostString(), diff --git a/test/jdk/com/sun/net/httpserver/ContextPathMatcherStringPrefixTest.java b/test/jdk/com/sun/net/httpserver/ContextPathMatcherStringPrefixTest.java index 685b8f02be0..3f3008a8531 100644 --- a/test/jdk/com/sun/net/httpserver/ContextPathMatcherStringPrefixTest.java +++ b/test/jdk/com/sun/net/httpserver/ContextPathMatcherStringPrefixTest.java @@ -21,13 +21,8 @@ * questions. */ -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; -import java.net.http.HttpClient; - -import static java.net.http.HttpClient.Builder.NO_PROXY; - /* * @test * @bug 8272758 @@ -39,33 +34,28 @@ import static java.net.http.HttpClient.Builder.NO_PROXY; * ${test.main.class} */ -class ContextPathMatcherStringPrefixTest { - - private static final HttpClient CLIENT = - HttpClient.newBuilder().proxy(NO_PROXY).build(); - - @AfterAll - static void stopClient() { - CLIENT.shutdownNow(); - } +class ContextPathMatcherStringPrefixTest extends ContextPathMatcherPathPrefixTest { @Test + @Override void testContextPathAtRoot() throws Exception { - try (var infra = new ContextPathMatcherPathPrefixTest.Infra("/")) { + try (var infra = new Infra("/")) { infra.expect(200, "/foo", "/foo/", "/foo/bar", "/foobar"); } } @Test + @Override void testContextPathAtSubDir() throws Exception { - try (var infra = new ContextPathMatcherPathPrefixTest.Infra("/foo")) { + try (var infra = new Infra("/foo")) { infra.expect(200, "/foo", "/foo/", "/foo/bar", "/foobar"); } } @Test + @Override void testContextPathAtSubDirWithTrailingSlash() throws Exception { - try (var infra = new ContextPathMatcherPathPrefixTest.Infra("/foo/")) { + try (var infra = new Infra("/foo/")) { infra.expect(200, "/foo/", "/foo/bar"); infra.expect(404, "/foo", "/foobar"); }