From d2829282006355022785979b07fc7f73be3cc4c4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 2 Aug 2018 18:48:15 +0300 Subject: [PATCH] Make sure Address.parseAddress can handle quoted IPv6 addresses Closes #385. [#159499428] --- .../java/com/rabbitmq/client/Address.java | 101 ++++++++++++++++-- .../com/rabbitmq/client/test/AddressTest.java | 90 ++++++++++++++++ 2 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/rabbitmq/client/test/AddressTest.java diff --git a/src/main/java/com/rabbitmq/client/Address.java b/src/main/java/com/rabbitmq/client/Address.java index a0ebf8372d..c70ae34df7 100644 --- a/src/main/java/com/rabbitmq/client/Address.java +++ b/src/main/java/com/rabbitmq/client/Address.java @@ -16,18 +16,28 @@ package com.rabbitmq.client; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A representation of network addresses, i.e. host/port pairs, * with some utility functions for parsing address strings. */ public class Address { - /** host name **/ + private static final Logger LOGGER = LoggerFactory.getLogger(Address.class); + + /** + * host name + **/ private final String _host; - /** port number **/ + /** + * port number + **/ private final int _port; /** * Construct an address from a host name and port number. + * * @param host the host name * @param port the port number */ @@ -38,6 +48,7 @@ public Address(String host, int port) { /** * Construct an address from a host. + * * @param host the host name */ public Address(String host) { @@ -47,6 +58,7 @@ public Address(String host) { /** * Get the host name + * * @return the host name */ public String getHost() { @@ -55,23 +67,98 @@ public String getHost() { /** * Get the port number + * * @return the port number */ public int getPort() { return _port; } + /** + * Extracts hostname or IP address from a string containing a hostname, IP address, + * hostname:port pair or IP address:port pair. + * Note that IPv6 addresses must be quoted with square brackets, e.g. [2001:db8:85a3:8d3:1319:8a2e:370:7348]. + * + * @param addressString the string to extract hostname from + * @return the hostname or IP address + */ + public static String parseHost(String addressString) { + // we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 + int lastColon = addressString.lastIndexOf(":"); + int lastClosingSquareBracket = addressString.lastIndexOf("]"); + if (lastClosingSquareBracket == -1) { + String[] parts = addressString.split(":"); + if (parts.length > 2) { + String msg = "Address " + + addressString + + " seems to contain an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]"; + LOGGER.error(msg); + throw new IllegalArgumentException(msg); + } + + return parts[0]; + } + + if (lastClosingSquareBracket < lastColon) { + // there is a port + return addressString.substring(0, lastColon); + } else { + return addressString; + } + } + + public static int parsePort(String addressString) { + // we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 + int lastColon = addressString.lastIndexOf(":"); + int lastClosingSquareBracket = addressString.lastIndexOf("]"); + if (lastClosingSquareBracket == -1) { + String[] parts = addressString.split(":"); + if (parts.length > 2) { + String msg = "Address " + + addressString + + " seems to contain an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]"; + LOGGER.error(msg); + throw new IllegalArgumentException(msg); + } + + if (parts.length == 2) { + return Integer.parseInt(parts[1]); + } + + return ConnectionFactory.USE_DEFAULT_PORT; + } + + if (lastClosingSquareBracket < lastColon) { + // there is a port + return Integer.parseInt(addressString.substring(lastColon + 1)); + } + + return ConnectionFactory.USE_DEFAULT_PORT; + } + + public static boolean isHostWithPort(String addressString) { + // we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 + int lastColon = addressString.lastIndexOf(":"); + int lastClosingSquareBracket = addressString.lastIndexOf("]"); + + if (lastClosingSquareBracket == -1) { + return addressString.contains(":"); + } else { + return lastClosingSquareBracket < lastColon; + } + } + /** * Factory method: takes a formatted addressString string as construction parameter * @param addressString an addressString of the form "host[:port]". * @return an {@link Address} from the given data */ public static Address parseAddress(String addressString) { - int idx = addressString.indexOf(':'); - return (idx == -1) ? - new Address(addressString) : - new Address(addressString.substring(0, idx), - Integer.parseInt(addressString.substring(idx+1))); + if (isHostWithPort(addressString)) { + return new Address(parseHost(addressString), parsePort(addressString)); + } else { + return new Address(addressString); + } } /** diff --git a/src/test/java/com/rabbitmq/client/test/AddressTest.java b/src/test/java/com/rabbitmq/client/test/AddressTest.java new file mode 100644 index 0000000000..a67bd96c86 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/AddressTest.java @@ -0,0 +1,90 @@ +// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Address; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * + */ +public class AddressTest { + + @Test public void isHostWithPort() { + assertTrue(Address.isHostWithPort("127.0.0.1:5672")); + assertTrue(Address.isHostWithPort("[1080:0:0:0:8:800:200C:417A]:5672")); + assertTrue(Address.isHostWithPort("[::1]:5672")); + + assertFalse(Address.isHostWithPort("127.0.0.1")); + assertFalse(Address.isHostWithPort("[1080:0:0:0:8:800:200C:417A]")); + assertFalse(Address.isHostWithPort("[::1]")); + } + + @Test public void parseHost() { + assertEquals("127.0.0.1", Address.parseHost("127.0.0.1:5672")); + assertEquals("[1080:0:0:0:8:800:200C:417A]", Address.parseHost("[1080:0:0:0:8:800:200C:417A]:5673")); + assertEquals("[::1]", Address.parseHost("[::1]:5672")); + + assertEquals("127.0.0.1", Address.parseHost("127.0.0.1")); + assertEquals("[1080:0:0:0:8:800:200C:417A]", Address.parseHost("[1080:0:0:0:8:800:200C:417A]")); + assertEquals("[::1]", Address.parseHost("[::1]")); + } + + @Test public void parsePort() { + assertEquals(5672, Address.parsePort("127.0.0.1:5672")); + assertEquals(5673, Address.parsePort("[1080:0:0:0:8:800:200C:417A]:5673")); + assertEquals(5672, Address.parsePort("[::1]:5672")); + + // "use default port" value + assertEquals(-1, Address.parsePort("127.0.0.1")); + assertEquals(-1, Address.parsePort("[1080:0:0:0:8:800:200C:417A]")); + assertEquals(-1, Address.parsePort("[::1]")); + } + + @Test public void parseIPv4() { + assertEquals(addr("192.168.1.10"), Address.parseAddress("192.168.1.10")); + assertEquals(addr("192.168.1.10", 5682), Address.parseAddress("192.168.1.10:5682")); + } + + @Test public void parseIPv6() { + // quoted IPv6 addresses without a port + assertEquals(addr("[1080:0:0:0:8:800:200C:417A]"), Address.parseAddress("[1080:0:0:0:8:800:200C:417A]")); + assertEquals(addr("[::1]"), Address.parseAddress("[::1]")); + + // quoted IPv6 addresses with a port + assertEquals(addr("[1080:0:0:0:8:800:200C:417A]", 5673), Address.parseAddress("[1080:0:0:0:8:800:200C:417A]:5673")); + assertEquals(addr("[::1]", 5673), Address.parseAddress("[::1]:5673")); + } + + @Test(expected = IllegalArgumentException.class) + public void parseUnquotedIPv6() { + // using a non-quoted IPv6 addresses with a port + Address.parseAddress("::1:5673"); + } + + private Address addr(String addr) { + return new Address(addr); + } + + private Address addr(String addr, int port) { + return new Address(addr, port); + } + +}