You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2019/07/30 21:25:31 UTC
[tomcat] branch 8.5.x updated: Back-port IPv6Utils
This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/8.5.x by this push:
new 18bd651 Back-port IPv6Utils
18bd651 is described below
commit 18bd651018d35b36af9caa5845a4a41133f89b9c
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jul 30 22:13:50 2019 +0100
Back-port IPv6Utils
---
java/org/apache/tomcat/util/net/IPv6Utils.java | 252 +++++++++++++++++++++
test/org/apache/tomcat/util/net/IPv6UtilsTest.java | 141 ++++++++++++
2 files changed, 393 insertions(+)
diff --git a/java/org/apache/tomcat/util/net/IPv6Utils.java b/java/org/apache/tomcat/util/net/IPv6Utils.java
new file mode 100644
index 0000000..94b8c49
--- /dev/null
+++ b/java/org/apache/tomcat/util/net/IPv6Utils.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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
+ *
+ * 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.
+ */
+package org.apache.tomcat.util.net;
+
+/**
+ * <p>IPv6 utilities.
+ * <p>For the moment, it only contains function to canonicalize IPv6 address
+ * into RFC 5952 form.
+ */
+public class IPv6Utils {
+
+ private static final int MAX_NUMBER_OF_GROUPS = 8;
+ private static final int MAX_GROUP_LENGTH = 4;
+
+ /**
+ * <p>Convert IPv6 address into RFC 5952 form.
+ * E.g. 2001:db8:0:1:0:0:0:1 -> 2001:db8:0:1::1</p>
+ *
+ * <p>Method is null safe, and if IPv4 address or host name is passed to the
+ * method it is returned without any processing.</p>
+ *
+ * <p>Method also supports IPv4 in IPv6 (e.g. 0:0:0:0:0:ffff:192.0.2.1 ->
+ * ::ffff:192.0.2.1), and zone ID (e.g. fe80:0:0:0:f0f0:c0c0:1919:1234%4
+ * -> fe80::f0f0:c0c0:1919:1234%4).</p>
+ *
+ * <p>The behaviour of this method is undefined if an invalid IPv6 address
+ * is passed in as input.</p>
+ *
+ * @param ipv6Address String representing valid IPv6 address.
+ * @return String representing IPv6 in canonical form.
+ * @throws IllegalArgumentException if IPv6 format is unacceptable.
+ */
+ public static String canonize(String ipv6Address) throws IllegalArgumentException {
+
+ if (ipv6Address == null) {
+ return null;
+ }
+
+ // Definitely not an IPv6, return untouched input.
+ if (!mayBeIPv6Address(ipv6Address)) {
+ return ipv6Address;
+ }
+
+ // Length without zone ID (%zone) or IPv4 address
+ int ipv6AddressLength = ipv6Address.length();
+ if (ipv6Address.contains(".")) {
+ // IPv4 in IPv6
+ // e.g. 0:0:0:0:0:FFFF:127.0.0.1
+ int lastColonPos = ipv6Address.lastIndexOf(":");
+ int lastColonsPos = ipv6Address.lastIndexOf("::");
+ if (lastColonsPos >= 0 && lastColonPos == lastColonsPos + 1) {
+ /*
+ * IPv6 part ends with two consecutive colons,
+ * last colon is part of IPv6 format.
+ * e.g. ::127.0.0.1
+ */
+ ipv6AddressLength = lastColonPos + 1;
+ } else {
+ /*
+ * IPv6 part ends with only one colon,
+ * last colon is not part of IPv6 format.
+ * e.g. ::FFFF:127.0.0.1
+ */
+ ipv6AddressLength = lastColonPos;
+ }
+ } else if (ipv6Address.contains("%")) {
+ // Zone ID
+ // e.g. fe80:0:0:0:f0f0:c0c0:1919:1234%4
+ ipv6AddressLength = ipv6Address.lastIndexOf("%");
+ }
+
+ StringBuilder result = new StringBuilder();
+ char [][] groups = new char[MAX_NUMBER_OF_GROUPS][MAX_GROUP_LENGTH];
+ int groupCounter = 0;
+ int charInGroupCounter = 0;
+
+ // Index of the current zeroGroup, -1 means not found.
+ int zeroGroupIndex = -1;
+ int zeroGroupLength = 0;
+
+ // maximum length zero group, if there is more then one, then first one
+ int maxZeroGroupIndex = -1;
+ int maxZeroGroupLength = 0;
+
+ boolean isZero = true;
+ boolean groupStart = true;
+
+ /*
+ * Two consecutive colons, initial expansion.
+ * e.g. 2001:db8:0:0:1::1 -> 2001:db8:0:0:1:0:0:1
+ */
+
+ StringBuilder expanded = new StringBuilder(ipv6Address);
+ int colonsPos = ipv6Address.indexOf("::");
+ int length = ipv6AddressLength;
+ int change = 0;
+
+ if (colonsPos >= 0 && colonsPos < ipv6AddressLength - 2) {
+ int colonCounter = 0;
+ for (int i = 0; i < ipv6AddressLength; i++) {
+ if (ipv6Address.charAt(i) == ':') {
+ colonCounter++;
+ }
+ }
+
+ if (colonsPos == 0) {
+ expanded.insert(0, "0");
+ change = change + 1;
+ }
+
+ for (int i = 0; i < MAX_NUMBER_OF_GROUPS - colonCounter; i++) {
+ expanded.insert(colonsPos + 1, "0:");
+ change = change + 2;
+ }
+
+
+ if (colonsPos == ipv6AddressLength - 2) {
+ expanded.setCharAt(colonsPos + change + 1, '0');
+ } else {
+ expanded.deleteCharAt(colonsPos + change + 1);
+ change = change - 1;
+ }
+ length = length + change;
+ }
+
+
+ // Processing one char at the time
+ for (int charCounter = 0; charCounter < length; charCounter++) {
+ char c = expanded.charAt(charCounter);
+ if (c >= 'A' && c <= 'F') {
+ c = (char) (c + 32);
+ }
+ if (c != ':') {
+ groups[groupCounter][charInGroupCounter] = c;
+ if (!(groupStart && c == '0')) {
+ ++charInGroupCounter;
+ groupStart = false;
+ }
+ if (c != '0') {
+ isZero = false;
+ }
+ }
+ if (c == ':' || charCounter == (length - 1)) {
+ // We reached end of current group
+ if (isZero) {
+ ++zeroGroupLength;
+ if (zeroGroupIndex == -1) {
+ zeroGroupIndex = groupCounter;
+ }
+ }
+
+ if (!isZero || charCounter == (length - 1)) {
+ // We reached end of zero group
+ if (zeroGroupLength > maxZeroGroupLength) {
+ maxZeroGroupLength = zeroGroupLength;
+ maxZeroGroupIndex = zeroGroupIndex;
+ }
+ zeroGroupLength = 0;
+ zeroGroupIndex = -1;
+ }
+ ++groupCounter;
+ charInGroupCounter = 0;
+ isZero = true;
+ groupStart = true;
+ }
+ }
+
+ int numberOfGroups = groupCounter;
+
+ // Output results
+ for (groupCounter = 0; groupCounter < numberOfGroups; groupCounter++) {
+ if (maxZeroGroupLength <= 1 || groupCounter < maxZeroGroupIndex
+ || groupCounter >= maxZeroGroupIndex + maxZeroGroupLength) {
+ for (int j = 0; j < MAX_GROUP_LENGTH; j++) {
+ if (groups[groupCounter][j] != 0) {
+ result.append(groups[groupCounter][j]);
+ }
+ }
+ if (groupCounter < (numberOfGroups - 1)
+ && (groupCounter != maxZeroGroupIndex - 1
+ || maxZeroGroupLength <= 1)) {
+ result.append(':');
+ }
+ } else if (groupCounter == maxZeroGroupIndex) {
+ result.append("::");
+ }
+ }
+
+ // Solve problem with three colons in IPv4 in IPv6 format
+ // e.g. 0:0:0:0:0:0:127.0.0.1 -> :::127.0.0.1 -> ::127.0.0.1
+ int resultLength = result.length();
+ if (result.charAt(resultLength - 1) == ':' && ipv6AddressLength < ipv6Address.length()
+ && ipv6Address.charAt(ipv6AddressLength) == ':') {
+ result.delete(resultLength - 1, resultLength);
+ }
+
+ /*
+ * Append IPv4 from IPv4-in-IPv6 format or Zone ID
+ */
+ for (int i = ipv6AddressLength; i < ipv6Address.length(); i++) {
+ result.append(ipv6Address.charAt(i));
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Heuristic check if string might be an IPv6 address.
+ *
+ * @param input Any string or null
+ * @return true, if input string contains only hex digits and at least two colons, before '.' or '%' character
+ */
+ static boolean mayBeIPv6Address(String input) {
+ if (input == null) {
+ return false;
+ }
+
+ int colonsCounter = 0;
+ int length = input.length();
+ for (int i = 0; i < length; i++) {
+ char c = input.charAt(i);
+ if (c == '.' || c == '%') {
+ // IPv4 in IPv6 or Zone ID detected, end of checking.
+ break;
+ }
+ if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
+ || (c >= 'A' && c <= 'F') || c == ':')) {
+ return false;
+ } else if (c == ':') {
+ colonsCounter++;
+ }
+ }
+ if (colonsCounter < 2) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/test/org/apache/tomcat/util/net/IPv6UtilsTest.java b/test/org/apache/tomcat/util/net/IPv6UtilsTest.java
new file mode 100644
index 0000000..11dc4ae
--- /dev/null
+++ b/test/org/apache/tomcat/util/net/IPv6UtilsTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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
+ *
+ * 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.
+ */
+package org.apache.tomcat.util.net;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+
+/**
+ * Mostly examples from RFC 5952
+ */
+public class IPv6UtilsTest {
+
+ @Test
+ public void testMayBeIPv6Address() {
+ Assert.assertFalse(IPv6Utils.mayBeIPv6Address(null));
+
+ Assert.assertTrue(IPv6Utils.mayBeIPv6Address("::1"));
+ Assert.assertTrue(IPv6Utils.mayBeIPv6Address("::"));
+ Assert.assertTrue(IPv6Utils.mayBeIPv6Address("2001:db8:0:0:1:0:0:1"));
+
+ Assert.assertFalse(IPv6Utils.mayBeIPv6Address(""));
+ Assert.assertFalse(IPv6Utils.mayBeIPv6Address(":1"));
+ Assert.assertFalse(IPv6Utils.mayBeIPv6Address("123.123.123.123"));
+ Assert.assertFalse(IPv6Utils.mayBeIPv6Address("tomcat.eu.apache.org:443"));
+ }
+
+ @Test
+ public void testCanonize() {
+ Assert.assertNull(IPv6Utils.canonize(null));
+ Assert.assertEquals("", IPv6Utils.canonize(""));
+
+ // IPv4-safe
+ Assert.assertEquals("123.123.123.123", IPv6Utils.canonize("123.123.123.123"));
+ Assert.assertEquals("123.1.2.23", IPv6Utils.canonize("123.1.2.23"));
+
+ // Introductory RFC 5952 examples
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:db8:0:0:1:0:0:1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:0db8:0:0:1:0:0:1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:db8::1:0:0:1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:db8::0:1:0:0:1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:0db8::1:0:0:1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:db8:0:0:1::1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:db8:0000:0:1::1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:DB8:0:0:1::1"));
+
+ // Strip leading zeros (2.1)
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001"));
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:001"));
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:01"));
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"));
+
+ // Zero compression (2.2)
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:0:1", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd::1"));
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:0:1", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:0:1"));
+
+ Assert.assertEquals("2001:db8::1", IPv6Utils.canonize("2001:db8:0:0:0::1"));
+ Assert.assertEquals("2001:db8::1", IPv6Utils.canonize("2001:db8:0:0::1"));
+ Assert.assertEquals("2001:db8::1", IPv6Utils.canonize("2001:db8:0::1"));
+ Assert.assertEquals("2001:db8::1", IPv6Utils.canonize("2001:db8::1"));
+
+ Assert.assertEquals("2001:db8::aaaa:0:0:1", IPv6Utils.canonize("2001:db8::aaaa:0:0:1"));
+ Assert.assertEquals("2001:db8::aaaa:0:0:1", IPv6Utils.canonize("2001:db8:0:0:aaaa::1"));
+
+ // Uppercase or lowercase (2.3)
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa"));
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA"));
+ Assert.assertEquals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa", IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa"));
+
+ // Some more zero compression for localhost addresses
+ Assert.assertEquals("::1", IPv6Utils.canonize("0:0:0:0:0:0:0:1"));
+ Assert.assertEquals("::1", IPv6Utils.canonize("0000:0:0:0:0:0:0:0001"));
+ Assert.assertEquals("::1", IPv6Utils.canonize("00:00:0:0:00:00:0:01"));
+ Assert.assertEquals("::1", IPv6Utils.canonize("::0001"));
+ Assert.assertEquals("::1", IPv6Utils.canonize("::1"));
+
+ // IPv6 unspecified address
+ Assert.assertEquals("::", IPv6Utils.canonize("0:0:0:0:0:0:0:0"));
+ Assert.assertEquals("::", IPv6Utils.canonize("0000:0:0:0:0:0:0:0000"));
+ Assert.assertEquals("::", IPv6Utils.canonize("00:00:0:0:00:00:0:00"));
+ Assert.assertEquals("::", IPv6Utils.canonize("::0000"));
+ Assert.assertEquals("::", IPv6Utils.canonize("::0"));
+ Assert.assertEquals("::", IPv6Utils.canonize("::"));
+
+ // Leading zeros (4.1)
+ Assert.assertEquals("2001:db8::1", IPv6Utils.canonize("2001:0db8::0001"));
+
+ // Shorten as much as possible (4.2.1)
+ Assert.assertEquals("2001:db8::2:1", IPv6Utils.canonize("2001:db8:0:0:0:0:2:1"));
+ Assert.assertEquals("2001:db8::", IPv6Utils.canonize("2001:db8:0:0:0:0:0:0"));
+
+ // Handling One 16-Bit 0 Field (4.2.2)
+ Assert.assertEquals("2001:db8:0:1:1:1:1:1", IPv6Utils.canonize("2001:db8:0:1:1:1:1:1"));
+ Assert.assertEquals("2001:db8:0:1:1:1:1:1", IPv6Utils.canonize("2001:db8::1:1:1:1:1"));
+
+ // Choice in Placement of "::" (4.2.3)
+ Assert.assertEquals("2001:0:0:1::1", IPv6Utils.canonize("2001:0:0:1:0:0:0:1"));
+ Assert.assertEquals("2001:db8::1:0:0:1", IPv6Utils.canonize("2001:db8:0:0:1:0:0:1"));
+
+ // IPv4 inside IPv6
+ Assert.assertEquals("::ffff:192.0.2.1", IPv6Utils.canonize("::ffff:192.0.2.1"));
+ Assert.assertEquals("::ffff:192.0.2.1", IPv6Utils.canonize("0:0:0:0:0:ffff:192.0.2.1"));
+ Assert.assertEquals("::192.0.2.1", IPv6Utils.canonize("::192.0.2.1"));
+ Assert.assertEquals("::192.0.2.1", IPv6Utils.canonize("0:0:0:0:0:0:192.0.2.1"));
+
+ // Zone ID
+ Assert.assertEquals("fe80::f0f0:c0c0:1919:1234%4", IPv6Utils.canonize("fe80::f0f0:c0c0:1919:1234%4"));
+ Assert.assertEquals("fe80::f0f0:c0c0:1919:1234%4", IPv6Utils.canonize("fe80:0:0:0:f0f0:c0c0:1919:1234%4"));
+
+ Assert.assertEquals("::%4", IPv6Utils.canonize("::%4"));
+ Assert.assertEquals("::%4", IPv6Utils.canonize("::0%4"));
+ Assert.assertEquals("::%4", IPv6Utils.canonize("0:0::0%4"));
+ Assert.assertEquals("::%4", IPv6Utils.canonize("0:0:0:0:0:0:0:0%4"));
+
+ Assert.assertEquals("::1%4", IPv6Utils.canonize("::1%4"));
+ Assert.assertEquals("::1%4", IPv6Utils.canonize("0:0::1%4"));
+ Assert.assertEquals("::1%4", IPv6Utils.canonize("0:0:0:0:0:0:0:1%4"));
+
+ Assert.assertEquals("::1%eth0", IPv6Utils.canonize("::1%eth0"));
+ Assert.assertEquals("::1%eth0", IPv6Utils.canonize("0:0::1%eth0"));
+ Assert.assertEquals("::1%eth0", IPv6Utils.canonize("0:0:0:0:0:0:0:1%eth0"));
+
+ // Hostname safety
+ Assert.assertEquals("www.apache.org", IPv6Utils.canonize("www.apache.org"));
+ Assert.assertEquals("ipv6.google.com", IPv6Utils.canonize("ipv6.google.com"));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org