diff --git a/jooby/src/main/java/io/jooby/DefaultContext.java b/jooby/src/main/java/io/jooby/DefaultContext.java index 6b4893cb41..5005ab98ce 100644 --- a/jooby/src/main/java/io/jooby/DefaultContext.java +++ b/jooby/src/main/java/io/jooby/DefaultContext.java @@ -421,7 +421,7 @@ default int getServerPort() { @Override default int getPort() { var hostAndPort = getHostAndPort(); - int index = hostAndPort.indexOf(':'); + int index = portSeparator(hostAndPort); if (index > 0) { return Integer.parseInt(hostAndPort.substring(index + 1)); } @@ -431,8 +431,26 @@ default int getPort() { @Override default String getHost() { String hostAndPort = getHostAndPort(); - int index = hostAndPort.indexOf(':'); - return index > 0 ? hostAndPort.substring(0, index).trim() : hostAndPort; + int index = portSeparator(hostAndPort); + if (index > 0) { + return hostAndPort.substring(0, index).trim(); + } + if (!hostAndPort.startsWith("[") && hostAndPort.indexOf(':') != hostAndPort.lastIndexOf(':')) { + // IPv6 address without port, which getHostAndPort() returns without brackets + return "[" + hostAndPort + "]"; + } + return hostAndPort; + } + + private static int portSeparator(String hostAndPort) { + int index = hostAndPort.lastIndexOf(':'); + if (hostAndPort.startsWith("[")) { + // IPv6 address: the port follows the closing bracket, as in [::1]:8080 + int end = hostAndPort.indexOf(']'); + return end > 0 && index > end ? index : -1; + } + // more than one colon is an IPv6 address without brackets, so without port + return index == hostAndPort.indexOf(':') ? index : -1; } @Override diff --git a/jooby/src/test/java/io/jooby/DefaultContextTest.java b/jooby/src/test/java/io/jooby/DefaultContextTest.java index c1eae28b62..e31ef166ad 100644 --- a/jooby/src/test/java/io/jooby/DefaultContextTest.java +++ b/jooby/src/test/java/io/jooby/DefaultContextTest.java @@ -542,6 +542,44 @@ void hostAndPortIPv6() { doReturn(mockHostHeader).when(ctx).header("Host"); assertEquals("::1", ctx.getHostAndPort()); + assertEquals("[::1]", ctx.getHost()); + } + + @Test + void hostAndPortIPv6WithPort() { + Value mockHostHeader = mock(Value.class); + when(mockHostHeader.valueOrNull()).thenReturn("[2001:db8::1]:8080"); + doReturn(mockHostHeader).when(ctx).header("Host"); + + assertEquals("[2001:db8::1]:8080", ctx.getHostAndPort()); + assertEquals("[2001:db8::1]", ctx.getHost()); + assertEquals(8080, ctx.getPort()); + } + + @Test + void hostAndPortIPv6DefaultPort() { + doReturn(false).when(ctx).isSecure(); + + Value mockHostHeader = mock(Value.class); + when(mockHostHeader.valueOrNull()).thenReturn("[2001:db8::1]"); + doReturn(mockHostHeader).when(ctx).header("Host"); + + assertEquals("[2001:db8::1]", ctx.getHost()); + assertEquals(80, ctx.getPort()); + } + + @Test + void requestURLIPv6() { + doReturn("http").when(ctx).getScheme(); + doReturn("/").when(ctx).getContextPath(); + doReturn("/path").when(ctx).getRequestPath(); + doReturn("?q=1").when(ctx).queryString(); + + Value mockHostHeader = mock(Value.class); + when(mockHostHeader.valueOrNull()).thenReturn("[::1]:8080"); + doReturn(mockHostHeader).when(ctx).header("Host"); + + assertEquals("http://[::1]:8080/path?q=1", ctx.getRequestURL()); } @Test