diff --git a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HeaderUtils.java b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HeaderUtils.java index f35419fb64..bc4de628d8 100644 --- a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HeaderUtils.java +++ b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HeaderUtils.java @@ -46,12 +46,12 @@ import static io.servicetalk.http.api.HttpHeaderNames.TRANSFER_ENCODING; import static io.servicetalk.http.api.HttpHeaderNames.VARY; import static io.servicetalk.http.api.HttpHeaderValues.CHUNKED; -import static io.servicetalk.http.api.NetUtils.isValidIpV4Address; -import static io.servicetalk.http.api.NetUtils.isValidIpV6Address; import static io.servicetalk.http.api.UriUtils.TCHAR_HMASK; import static io.servicetalk.http.api.UriUtils.TCHAR_LMASK; import static io.servicetalk.http.api.UriUtils.isBitSet; import static io.servicetalk.utils.internal.CharsetUtils.standardCharsets; +import static io.servicetalk.utils.internal.NetworkUtils.isValidIpV4Address; +import static io.servicetalk.utils.internal.NetworkUtils.isValidIpV6Address; import static java.lang.Math.min; import static java.lang.System.lineSeparator; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/SingleAddressHttpClientBuilder.java b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/SingleAddressHttpClientBuilder.java index 368d3bf111..b7cbcd3e85 100644 --- a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/SingleAddressHttpClientBuilder.java +++ b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/SingleAddressHttpClientBuilder.java @@ -94,7 +94,7 @@ SingleAddressHttpClientBuilder enableWireLogging(String loggerName, SingleAddressHttpClientBuilder protocols(HttpProtocolConfig... protocols); /** - * Configures automatically setting {@code Host} headers by inferring from the address or {@link HttpMetaData}. + * Configures automatically setting {@code Host} headers by inferring from the address. *

* When {@code false} is passed, this setting disables the default filter such that no {@code Host} header will be * manipulated. diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/DefaultSingleAddressHttpClientBuilder.java b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/DefaultSingleAddressHttpClientBuilder.java index ea1f4e2aca..9c2c394e10 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/DefaultSingleAddressHttpClientBuilder.java +++ b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/DefaultSingleAddressHttpClientBuilder.java @@ -45,6 +45,7 @@ import io.servicetalk.http.api.StreamingHttpConnectionFilterFactory; import io.servicetalk.http.api.StreamingHttpRequest; import io.servicetalk.http.api.StreamingHttpRequestResponseFactory; +import io.servicetalk.http.utils.HostHeaderHttpRequesterFilter; import io.servicetalk.logging.api.LogLevel; import io.servicetalk.transport.api.ClientSslConfig; import io.servicetalk.transport.api.ExecutionStrategy; @@ -448,13 +449,22 @@ public DefaultSingleAddressHttpClientBuilder appendConnectionFilter( requireNonNull(factory); connectionFilterFactory = appendConnectionFilter(connectionFilterFactory, factory); strategyComputation.add(factory); + ifHostHeaderHttpRequesterFilter(factory); return this; } @Override public DefaultSingleAddressHttpClientBuilder appendConnectionFilter( final Predicate predicate, final StreamingHttpConnectionFilterFactory factory) { - return appendConnectionFilter(toConditionalConnectionFilterFactory(predicate, factory)); + appendConnectionFilter(toConditionalConnectionFilterFactory(predicate, factory)); + ifHostHeaderHttpRequesterFilter(factory); + return this; + } + + private void ifHostHeaderHttpRequesterFilter(final Object filter) { + if (filter instanceof HostHeaderHttpRequesterFilter) { + addHostHeaderFallbackFilter = false; + } } // Use another method to keep final references and avoid StackOverflowError @@ -491,7 +501,9 @@ public DefaultSingleAddressHttpClientBuilder appendClientFilter( ensureSingleRetryFilter(); retryingHttpRequesterFilter = (RetryingHttpRequesterFilter) factory; } - return appendClientFilter(toConditionalClientFilterFactory(predicate, factory)); + appendClientFilter(toConditionalClientFilterFactory(predicate, factory)); + ifHostHeaderHttpRequesterFilter(factory); + return this; } private void ensureSingleRetryFilter() { @@ -518,6 +530,7 @@ public DefaultSingleAddressHttpClientBuilder appendClientFilter( } clientFilterFactory = appendFilter(clientFilterFactory, factory); strategyComputation.add(factory); + ifHostHeaderHttpRequesterFilter(factory); return this; } diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClientConfig.java b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClientConfig.java index 842ece52fb..a6efeec79e 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClientConfig.java +++ b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpClientConfig.java @@ -22,9 +22,9 @@ import java.util.List; import javax.annotation.Nullable; -import static io.netty.util.NetUtil.isValidIpV4Address; -import static io.netty.util.NetUtil.isValidIpV6Address; import static io.servicetalk.http.netty.HttpServerConfig.httpAlpnProtocols; +import static io.servicetalk.utils.internal.NetworkUtils.isValidIpV4Address; +import static io.servicetalk.utils.internal.NetworkUtils.isValidIpV6Address; final class HttpClientConfig { diff --git a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HostHeaderHttpRequesterFilterTest.java b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HostHeaderHttpRequesterFilterTest.java index 8bec182469..8a731e214f 100644 --- a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HostHeaderHttpRequesterFilterTest.java +++ b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HostHeaderHttpRequesterFilterTest.java @@ -22,6 +22,7 @@ import io.servicetalk.http.api.HttpRequest; import io.servicetalk.http.api.HttpResponse; import io.servicetalk.http.api.ReservedBlockingHttpConnection; +import io.servicetalk.http.utils.HostHeaderHttpRequesterFilter; import io.servicetalk.transport.api.ServerContext; import org.junit.jupiter.params.ParameterizedTest; @@ -128,7 +129,6 @@ void clientBuilderAppendClientFilter(HttpVersionConfig httpVersionConfig) throws try (ServerContext context = buildServer(); BlockingHttpClient client = forSingleAddress(serverHostAndPort(context)) .protocols(httpVersionConfig.config()) - .hostHeaderFallback(false) // turn off the default .appendClientFilter(new HostHeaderHttpRequesterFilter("foo.bar:-1")) .buildBlocking()) { assertResponse(client, null, "foo.bar:-1"); @@ -142,7 +142,6 @@ void clientBuilderAppendConnectionFilter(HttpVersionConfig httpVersionConfig) th try (ServerContext context = buildServer(); BlockingHttpClient client = forSingleAddress(serverHostAndPort(context)) .protocols(httpVersionConfig.config()) - .hostHeaderFallback(false) // turn off the default .appendConnectionFilter(new HostHeaderHttpRequesterFilter("foo.bar:-1")) .buildBlocking()) { assertResponse(client, null, "foo.bar:-1"); @@ -156,7 +155,6 @@ void reserveConnection(HttpVersionConfig httpVersionConfig) throws Exception { try (ServerContext context = buildServer(); BlockingHttpClient client = HttpClients.forResolvedAddress(serverHostAndPort(context)) .protocols(httpVersionConfig.config()) - .hostHeaderFallback(false) // turn off the default .appendConnectionFilter(new HostHeaderHttpRequesterFilter("foo.bar:-1")) .buildBlocking(); ReservedBlockingHttpConnection conn = client.reserveConnection(client.get("/"))) { @@ -172,7 +170,6 @@ void clientBuilderAppendClientFilterExplicitHostHeader(HttpVersionConfig httpVer try (ServerContext context = buildServer(); BlockingHttpClient client = forSingleAddress(serverHostAndPort(context)) .protocols(httpVersionConfig.config()) - .hostHeaderFallback(false) // turn off the default .appendClientFilter(new HostHeaderHttpRequesterFilter("foo.bar:-1")) .buildBlocking()) { assertResponse(client, "bar.only:-1", "bar.only:-1"); diff --git a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/RedirectingClientAndConnectionFilterTest.java b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/RedirectingClientAndConnectionFilterTest.java index 0b897a3d4e..3e355ee60b 100644 --- a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/RedirectingClientAndConnectionFilterTest.java +++ b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/RedirectingClientAndConnectionFilterTest.java @@ -22,6 +22,7 @@ import io.servicetalk.http.api.HttpRequest; import io.servicetalk.http.api.HttpResponse; import io.servicetalk.http.netty.ConditionalFilterFactory.FilterFactory; +import io.servicetalk.http.utils.HostHeaderHttpRequesterFilter; import io.servicetalk.http.utils.RedirectingHttpRequesterFilter; import io.servicetalk.transport.api.HostAndPort; diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HostHeaderHttpRequesterFilter.java b/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/HostHeaderHttpRequesterFilter.java similarity index 84% rename from servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HostHeaderHttpRequesterFilter.java rename to servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/HostHeaderHttpRequesterFilter.java index ccb3596d5b..43dc3bf0e5 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HostHeaderHttpRequesterFilter.java +++ b/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/HostHeaderHttpRequesterFilter.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2021 Apple Inc. and the ServiceTalk project authors + * Copyright © 2018-2022 Apple Inc. and the ServiceTalk project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.servicetalk.http.netty; +package io.servicetalk.http.utils; import io.servicetalk.concurrent.api.Single; import io.servicetalk.http.api.FilterableStreamingHttpClient; @@ -21,6 +21,7 @@ import io.servicetalk.http.api.HttpExecutionStrategies; import io.servicetalk.http.api.HttpExecutionStrategy; import io.servicetalk.http.api.HttpHeaderNames; +import io.servicetalk.http.api.HttpRequestMetaData; import io.servicetalk.http.api.StreamingHttpClientFilter; import io.servicetalk.http.api.StreamingHttpClientFilterFactory; import io.servicetalk.http.api.StreamingHttpConnectionFilter; @@ -29,24 +30,26 @@ import io.servicetalk.http.api.StreamingHttpRequester; import io.servicetalk.http.api.StreamingHttpResponse; -import static io.netty.util.NetUtil.isValidIpV6Address; import static io.servicetalk.buffer.api.CharSequences.newAsciiString; import static io.servicetalk.concurrent.api.Single.defer; import static io.servicetalk.http.api.HttpHeaderNames.HOST; import static io.servicetalk.http.api.HttpProtocolVersion.HTTP_1_0; +import static io.servicetalk.utils.internal.NetworkUtils.isValidIpV6Address; /** - * A filter which will apply a fallback value for the {@link HttpHeaderNames#HOST} header if one is not present. + * A filter which will set a {@link HttpHeaderNames#HOST} header with the fallback value if the header is not already + * present in {@link HttpRequestMetaData}. */ -final class HostHeaderHttpRequesterFilter implements StreamingHttpClientFilterFactory, - StreamingHttpConnectionFilterFactory { +public final class HostHeaderHttpRequesterFilter implements StreamingHttpClientFilterFactory, + StreamingHttpConnectionFilterFactory { private final CharSequence fallbackHost; /** * Create a new instance. + * * @param fallbackHost The address to use as a fallback if a {@link HttpHeaderNames#HOST} header is not present. */ - HostHeaderHttpRequesterFilter(CharSequence fallbackHost) { + public HostHeaderHttpRequesterFilter(CharSequence fallbackHost) { this.fallbackHost = newAsciiString(isValidIpV6Address(fallbackHost) && fallbackHost.charAt(0) != '[' ? "[" + fallbackHost + "]" : fallbackHost.toString()); } diff --git a/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/BuilderUtils.java b/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/BuilderUtils.java index dacfeac5a4..a51e6d1c10 100644 --- a/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/BuilderUtils.java +++ b/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/BuilderUtils.java @@ -39,7 +39,6 @@ import io.netty.incubator.channel.uring.IOUringDatagramChannel; import io.netty.incubator.channel.uring.IOUringServerSocketChannel; import io.netty.incubator.channel.uring.IOUringSocketChannel; -import io.netty.util.NetUtil; import java.io.Closeable; import java.io.IOException; @@ -51,6 +50,7 @@ import javax.annotation.Nullable; import static io.netty.util.NetUtil.createByteArrayFromIpAddressString; +import static io.netty.util.NetUtil.toAddressString; import static io.servicetalk.transport.netty.internal.NativeTransportUtils.useEpoll; import static io.servicetalk.transport.netty.internal.NativeTransportUtils.useIoUring; import static io.servicetalk.transport.netty.internal.NativeTransportUtils.useKQueue; @@ -203,9 +203,9 @@ public static String formatCanonicalAddress(SocketAddress address) { if (inetAddress == null) { return address.toString(); } else if (inetAddress instanceof Inet6Address) { - return '[' + NetUtil.toAddressString(inetAddress) + "]:" + inetSocketAddress.getPort(); + return '[' + toAddressString(inetAddress) + "]:" + inetSocketAddress.getPort(); } else { - return NetUtil.toAddressString(inetAddress) + ':' + inetSocketAddress.getPort(); + return toAddressString(inetAddress) + ':' + inetSocketAddress.getPort(); } } return address.toString(); diff --git a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/NetUtils.java b/servicetalk-utils-internal/src/main/java/io/servicetalk/utils/internal/NetworkUtils.java similarity index 91% rename from servicetalk-http-api/src/main/java/io/servicetalk/http/api/NetUtils.java rename to servicetalk-utils-internal/src/main/java/io/servicetalk/utils/internal/NetworkUtils.java index 551588a512..8b801126df 100644 --- a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/NetUtils.java +++ b/servicetalk-utils-internal/src/main/java/io/servicetalk/utils/internal/NetworkUtils.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018, 2021 Apple Inc. and the ServiceTalk project authors + * Copyright © 2018, 2021-2022 Apple Inc. and the ServiceTalk project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,29 +28,37 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.servicetalk.http.api; +package io.servicetalk.utils.internal; import static io.servicetalk.buffer.api.CharSequences.indexOf; -final class NetUtils { +/** + * Network-related utilities. + *

+ * This class borrowed some of its methods from + * NetUtil class + * which was part of Netty. + */ +public final class NetworkUtils { - private NetUtils() { + private NetworkUtils() { // no instances } /** * Takes a string and parses it to see if it is a valid IPV4 address. * + * @param ip the IP-address to validate * @return true, if the string represents an IPV4 address in dotted notation, false otherwise. */ - static boolean isValidIpV4Address(final CharSequence ip) { + public static boolean isValidIpV4Address(final CharSequence ip) { return isValidIpV4Address(ip, 0, ip.length()); } private static boolean isValidIpV4Address(final CharSequence ip, int from, int toExclusive) { int len = toExclusive - from; int i; - return len <= 15 && len > 7 && + return len <= 15 && len >= 7 && (i = indexOf(ip, '.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) && (i = indexOf(ip, '.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && (i = indexOf(ip, '.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && @@ -60,9 +68,10 @@ private static boolean isValidIpV4Address(final CharSequence ip, int from, int t /** * Takes a string and parses it to see if it is a valid IPV6 address. * + * @param ip the IP-address to validate * @return true, if the string represents an IPV6 address */ - static boolean isValidIpV6Address(final CharSequence ip) { + public static boolean isValidIpV6Address(final CharSequence ip) { int end = ip.length(); if (end < 2) { return false; diff --git a/servicetalk-utils-internal/src/test/java/io/servicetalk/utils/internal/NetworkUtilsTest.java b/servicetalk-utils-internal/src/test/java/io/servicetalk/utils/internal/NetworkUtilsTest.java new file mode 100644 index 0000000000..0ad1442e91 --- /dev/null +++ b/servicetalk-utils-internal/src/test/java/io/servicetalk/utils/internal/NetworkUtilsTest.java @@ -0,0 +1,447 @@ +/* + * Copyright © 2022 Apple Inc. and the ServiceTalk project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.servicetalk.utils.internal; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static io.servicetalk.utils.internal.NetworkUtils.isValidIpV4Address; +import static io.servicetalk.utils.internal.NetworkUtils.isValidIpV6Address; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SuppressWarnings("PMD.AvoidUsingHardCodedIP") +class NetworkUtilsTest { + + private static final List validIpV4Hosts = asList( + "192.168.1.0", + "10.255.255.254", + "172.18.5.4", + "0.0.0.0", + "127.0.0.1", + "255.255.255.255", + "1.2.3.4"); + + private static final List invalidIpV4Hosts = asList( + "Garbage", + "1.256.3.4", + "256.0.0.1", + "1.1.1.1.1", + "x.255.255.255", + "0.1:0.0", + "0.1.0.0:", + "127.0.0.", + "1.2..4", + "192.0.1", + "192.0.1.1.1", + "192.0.1.a", + "19a.0.1.1", + "a.0.1.1", + ".0.1.1", + "127.0.0", + "192.0.1.256", + "0.0.200.259", + "1.1.-1.1", + "1.1. 1.1", + "1.1.1.1 ", + "1.1.+1.1", + "0.0x1.0.255", + "0.01x.0.255", + "0.x01.0.255", + "0.-.0.0", + "0..0.0", + "0.A.0.0", + "0.1111.0.0", + "..."); + + private static final List validIpV6Hosts = asList( + "::ffff:5.6.7.8", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "2001::1", + "2001:0000:4136:e378:8000:63bf:3fff:fdd2", + "2001:0002:6c::430", + "2001:10:240:ab::a", + "2002:cb0a:3cdd:1::1", + "2001:db8:8:4::2", + "ff01:0:0:0:0:0:0:2", + "[fdf8:f53b:82e4::53]", + "[fe80::200:5aee:feaa:20a2]", + "[2001::1]", + "[2001:0000:4136:e378:8000:63bf:3fff:fdd2]", + "0:1:2:3:4:5:6:789a", + "0:1:2:3::f", + "0:0:0:0:0:0:10.0.0.1", + "0:0:0:0:0::10.0.0.1", + "0:0:0:0::10.0.0.1", + "::0:0:0:0:0:10.0.0.1", + "0::0:0:0:0:10.0.0.1", + "0:0::0:0:0:10.0.0.1", + "0:0:0::0:0:10.0.0.1", + "0:0:0:0::0:10.0.0.1", + "0:0:0:0:0:ffff:10.0.0.1", + "::ffff:192.168.0.1", + // Test if various interface names after the percent sign are recognized. + "[::1%1]", + "[::1%eth0]", + "[::1%%]", + "0:0:0:0:0:ffff:10.0.0.1%", + "0:0:0:0:0:ffff:10.0.0.1%1", + "[0:0:0:0:0:ffff:10.0.0.1%1]", + "[0:0:0:0:0::10.0.0.1%1]", + "[::0:0:0:0:ffff:10.0.0.1%1]", + "::0:0:0:0:ffff:10.0.0.1%1", + "::1%1", + "::1%eth0", + "::1%%", + // Tests with leading or trailing compression + "0:0:0:0:0:0:0::", + "0:0:0:0:0:0::", + "0:0:0:0:0::", + "0:0:0:0::", + "0:0:0::", + "0:0::", + "0::", + "::", + "::0", + "::0:0", + "::0:0:0", + "::0:0:0:0", + "::0:0:0:0:0", + "::0:0:0:0:0:0", + "::0:0:0:0:0:0:0"); + + private static final List invalidIpV6Hosts = asList( + // Test method with garbage. + "Obvious Garbage", + // Test method with preferred style, too many : + "0:1:2:3:4:5:6:7:8", + // Test method with preferred style, not enough : + "0:1:2:3:4:5:6", + // Test method with preferred style, bad digits. + "0:1:2:3:4:5:6:x", + // Test method with preferred style, adjacent : + "0:1:2:3:4:5:6::7", + // Too many : separators trailing + "0:1:2:3:4:5:6:7::", + // Too many : separators leading + "::0:1:2:3:4:5:6:7", + // Too many : separators trailing + "1:2:3:4:5:6:7:", + // Too many : separators leading + ":1:2:3:4:5:6:7", + // Compression with : separators trailing + "0:1:2:3:4:5::7:", + "0:1:2:3:4::7:", + "0:1:2:3::7:", + "0:1:2::7:", + "0:1::7:", + "0::7:", + // Compression at start with : separators trailing + "::0:1:2:3:4:5:7:", + "::0:1:2:3:4:7:", + "::0:1:2:3:7:", + "::0:1:2:7:", + "::0:1:7:", + "::7:", + // The : separators leading and trailing + ":1:2:3:4:5:6:7:", + ":1:2:3:4:5:6:", + ":1:2:3:4:5:", + ":1:2:3:4:", + ":1:2:3:", + ":1:2:", + ":1:", + // Compression with : separators leading + ":1::2:3:4:5:6:7", + ":1::3:4:5:6:7", + ":1::4:5:6:7", + ":1::5:6:7", + ":1::6:7", + ":1::7", + ":1:2:3:4:5:6::7", + ":1:3:4:5:6::7", + ":1:4:5:6::7", + ":1:5:6::7", + ":1:6::7", + ":1::", + // Compression trailing with : separators leading + ":1:2:3:4:5:6:7::", + ":1:3:4:5:6:7::", + ":1:4:5:6:7::", + ":1:5:6:7::", + ":1:6:7::", + ":1:7::", + // Double compression + "1::2:3:4:5:6::", + "::1:2:3:4:5::6", + "::1:2:3:4:5:6::", + "::1:2:3:4:5::", + "::1:2:3:4::", + "::1:2:3::", + "::1:2::", + "::0::", + "12::0::12", + // Too many : separators leading 0 + "0::1:2:3:4:5:6:7", + // Test method with preferred style, too many digits. + "0:1:2:3:4:5:6:789abcdef", + // Test method with compressed style, bad digits. + "0:1:2:3::x", + // Test method with compressed style, too many adjacent : + "0:1:2:::3", + // Test method with compressed style, too many digits. + "0:1:2:3::abcde", + // Test method with compressed style, not enough : + "0:1", + // Test method with ipv4 style, bad ipv6 digits. + "0:0:0:0:0:x:10.0.0.1", + // Test method with ipv4 style, bad ipv4 digits. + "0:0:0:0:0:0:10.0.0.x", + // Test method with ipv4 style, too many ipv6 digits. + "0:0:0:0:0:00000:10.0.0.1", + // Test method with ipv4 style, too many : + "0:0:0:0:0:0:0:10.0.0.1", + // Test method with ipv4 style, not enough : + "0:0:0:0:0:10.0.0.1", + // Test method with ipv4 style, too many . + "0:0:0:0:0:0:10.0.0.0.1", + // Test method with ipv4 style, not enough . + "0:0:0:0:0:0:10.0.1", + // Test method with ipv4 style, adjacent . + "0:0:0:0:0:0:10..0.0.1", + // Test method with ipv4 style, leading . + "0:0:0:0:0:0:.0.0.1", + // Test method with ipv4 style, leading . + "0:0:0:0:0:0:.10.0.0.1", + // Test method with ipv4 style, trailing . + "0:0:0:0:0:0:10.0.0.", + // Test method with ipv4 style, trailing . + "0:0:0:0:0:0:10.0.0.1.", + // Test method with compressed ipv4 style, bad ipv6 digits. + "::fffx:192.168.0.1", + // Test method with compressed ipv4 style, bad ipv4 digits. + "::ffff:192.168.0.x", + // Test method with compressed ipv4 style, too many adjacent : + ":::ffff:192.168.0.1", + // Test method with compressed ipv4 style, too many ipv6 digits. + "::fffff:192.168.0.1", + // Test method with compressed ipv4 style, too many ipv4 digits. + "::ffff:1923.168.0.1", + // Test method with compressed ipv4 style, not enough : + ":ffff:192.168.0.1", + // Test method with compressed ipv4 style, too many . + "::ffff:192.168.0.1.2", + // Test method with compressed ipv4 style, not enough . + "::ffff:192.168.0", + // Test method with compressed ipv4 style, adjacent . + "::ffff:192.168..0.1", + // Test method, bad ipv6 digits. + "x:0:0:0:0:0:10.0.0.1", + // Test method, bad ipv4 digits. + "0:0:0:0:0:0:x.0.0.1", + // Test method, too many ipv6 digits. + "00000:0:0:0:0:0:10.0.0.1", + // Test method, too many ipv4 digits. + "0:0:0:0:0:0:10.0.0.1000", + // Test method, too many : + "0:0:0:0:0:0:0:10.0.0.1", + // Test method, not enough : + "0:0:0:0:0:10.0.0.1", + // Test method, out of order trailing : + "0:0:0:0:0:10.0.0.1:", + // Test method, out of order leading : + ":0:0:0:0:0:10.0.0.1", + // Test method, out of order leading : + "0:0:0:0::10.0.0.1:", + // Test method, out of order trailing : + ":0:0:0:0::10.0.0.1", + // Test method, too many . + "0:0:0:0:0:0:10.0.0.0.1", + // Test method, not enough . + "0:0:0:0:0:0:10.0.1", + // Test method, adjacent . + "0:0:0:0:0:0:10.0.0..1", + // Empty contents + "", + // Invalid single compression + ":", + ":::", + // Trailing : (max number of : = 8) + "2001:0:4136:e378:8000:63bf:3fff:fdd2:", + // Leading : (max number of : = 8) + ":aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", + // Invalid character + "1234:2345:3456:4567:5678:6789::X890", + // Trailing . in IPv4 + "::ffff:255.255.255.255.", + // To many characters in IPv4 + "::ffff:0.0.1111.0", + // Test method, adjacent . + "::ffff:0.0..0", + // Not enough IPv4 entries trailing . + "::ffff:127.0.0.", + // Invalid trailing IPv4 character + "::ffff:127.0.0.a", + // Invalid leading IPv4 character + "::ffff:a.0.0.1", + // Invalid middle IPv4 character + "::ffff:127.a.0.1", + // Invalid middle IPv4 character + "::ffff:127.0.a.1", + // Not enough IPv4 entries no trailing . + "::ffff:1.2.4", + // Extra IPv4 entry + "::ffff:192.168.0.1.255", + // Not enough IPv6 content + ":ffff:192.168.0.1.255", + // Intermixed IPv4 and IPv6 symbols + "::ffff:255.255:255.255.", + // Invalid IPv4 mapped address - invalid ipv4 separator + "0:0:0::0:0:00f.0.0.1", + // Invalid IPv4 mapped address - not enough f's + "0:0:0:0:0:fff:1.0.0.1", + // Invalid IPv4 mapped address - not IPv4 mapped, not IPv4 compatible + "0:0:0:0:0:ff00:1.0.0.1", + // Invalid IPv4 mapped address - not IPv4 mapped, not IPv4 compatible + "0:0:0:0:0:ff:1.0.0.1", + // Invalid IPv4 mapped address - too many f's + "0:0:0:0:0:fffff:1.0.0.1", + // Invalid IPv4 mapped address - too many bytes (too many 0's) + "0:0:0:0:0:0:ffff:1.0.0.1", + // Invalid IPv4 mapped address - too many bytes (too many 0's) + "::0:0:0:0:0:ffff:1.0.0.1", + // Invalid IPv4 mapped address - too many bytes (too many 0's) + "0:0:0:0:0:0::1.0.0.1", + // Invalid IPv4 mapped address - too many bytes (too many 0's) + "0:0:0:0:0:00000:1.0.0.1", + // Invalid IPv4 mapped address - too few bytes (not enough 0's) + "0:0:0:0:ffff:1.0.0.1", + // Invalid IPv4 mapped address - too few bytes (not enough 0's) + "ffff:192.168.0.1", + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:0:0:0:ffff::10.0.0.1", + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:0:0:ffff::10.0.0.1", + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:0:ffff::10.0.0.1", + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:ffff::10.0.0.1", + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:ffff::10.0.0.1", + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "ffff::10.0.0.1", + // Invalid IPv4 mapped address - not all 0's before the mapped separator + "1:0:0:0:0:ffff:10.0.0.1", + // Address that is similar to IPv4 mapped, but is invalid + "0:0:0:0:ffff:ffff:1.0.0.1", + // Valid number of separators, but invalid IPv4 format + "::1:2:3:4:5:6.7.8.9", + // Too many digits + "0:0:0:0:0:0:ffff:10.0.0.1", + // Invalid IPv4 format + ":1.2.3.4", + // Invalid IPv4 format + "::.2.3.4", + // Invalid IPv4 format + "::ffff:0.1.2."); + + @Test + void testIsValidIpV4Address() { + for (String host : validIpV4Hosts) { + assertTrue(isValidIpV4Address(host), host); + } + for (String host : invalidIpV4Hosts) { + assertFalse(isValidIpV4Address(host), host); + } + } + + @Test + void testIsValidIpV6Address() { + for (String host : validIpV6Hosts) { + assertTrue(isValidIpV6Address(host), host); + if (host.charAt(0) != '[' && !host.contains("%")) { + String hostMod = '[' + host + ']'; + assertTrue(isValidIpV6Address(hostMod), hostMod); + + hostMod = host + '%'; + assertTrue(isValidIpV6Address(hostMod), hostMod); + + hostMod = host + "%eth1"; + assertTrue(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "%]"; + assertTrue(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "%1]"; + assertTrue(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "]%"; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "]%1"; + assertFalse(isValidIpV6Address(hostMod), hostMod); + } + } + for (String host : invalidIpV6Hosts) { + assertFalse(isValidIpV6Address(host), host); + + String hostMod = '[' + host + ']'; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = host + '%'; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = host + "%eth1"; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "%]"; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "%1]"; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "]%"; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host + "]%1"; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = host + ']'; + assertFalse(isValidIpV6Address(hostMod), hostMod); + + hostMod = '[' + host; + assertFalse(isValidIpV6Address(hostMod), hostMod); + } + } +}