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.

+ * The property can be configured with one of the following values:

* *
*
@@ -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"); }