package com.netifera.platform.util.patternmatching; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class InternetAddressMatcher implements ITextMatcher { /** regex matching an IPv4 address. */ private static final String IPV4_ADDRESS_REGEX = "(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)" + /* 0 to 255 */ "(\\." + "(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)" + /* '.' 0 to 255, 3 times */ "){3}"; /** regex matching an IPv6 address. */ private static final String IPV6_ADDRESS_REGEX = "^(?:\\p{XDigit}{0,4}:){2,7}" + "(?:" + "(\\d+(\\.\\d+){3})" // Mapped IPv4 + "|" + "(?:\\p{XDigit}{0,4}" // IPv6 + "(?:%\\d+)?)" // optional scope + ")$"; private final String value; private final boolean invalid; /** * @param text * the string to be matched */ public InternetAddressMatcher(final String text) { value = text; invalid = text == null || text.length() < 2; } @Override public String toString() { return value; } /** * Tells whether or not this string matches an IPv4/IPv6 address. * @return <tt>true</tt> if, and only if, this string matches a IP address. */ public boolean matches() { if (invalid || value.matches("[g-zG-Z]")) { // [AlNum && ^XDigit] return false; } return matchesIPv4() || matchesIPv6(); } /** * Tells whether or not this string matches an IPv4 address. * @return <tt>true</tt> if, and only if, this string matches a IPv4 * address. */ public boolean matchesIPv4() { return !invalid && value.matches(IPV4_ADDRESS_REGEX); } /** * Tells whether or not this string matches an IPv4 address. * @param text * the string to be matched * @return <tt>true</tt> if, and only if, this string matches a IPv4 * address. */ public static boolean matchesIPv4(final String text) { return text.matches(IPV4_ADDRESS_REGEX); } /** Maximum number of 16-bits members in an IPv6 address. */ private static final int MAX_IPV6_PARTS = 8; /** * Tells whether or not this string matches an IPv6 address. * @return <tt>true</tt> if, and only if, this string matches a IPv6 * address. */ @SuppressWarnings("fallthrough") public boolean matchesIPv6() { if (invalid || value.contains(":::")) { return false; } if (value.contains("-") && matchesIPv6RFC2732()) { return true; } String nvalue = value; // literal notation if (value.charAt(0) == '[') { int len = value.length(); if (value.charAt(len - 1) == ']') { nvalue = value.substring(1, len - 1); } } int secondExt = 0; // ipv4 mapped if (nvalue.contains(".")) { String ipv4String = nvalue.substring(nvalue.lastIndexOf(':') + 1); if (!ipv4String.matches(IPV4_ADDRESS_REGEX)) { return false; } /* [1]: mapped IPv4 matched */ secondExt = 1; // 2(v4=2x16bits) - 1(this) } // compressed notation if (nvalue.contains("::")) { String[] splitted = nvalue.split("::", -1); switch (splitted.length) { case 0: // loopback return true; case 2: secondExt = splitted[1].split(":").length; case 1: if (splitted[0].split(":").length + secondExt > MAX_IPV6_PARTS - 1) { return false; } break; default: return false; } } else if (nvalue.split(":").length + secondExt != MAX_IPV6_PARTS) { return false; } Matcher matcher = IPV6_ADDRESS_PATTERN.matcher(nvalue); if (!matcher.find()) { return false; } if (matcher.group(1) != null) { return true; /* already matched in [1] */ } return true; } /** * Tells whether or not this string matches an IPv6 address. * @param text * the string to be matched * @return <tt>true</tt> if, and only if, this string matches a IPv6 * address. */ public static boolean matchesIPv6(final String text) { return new InternetAddressMatcher(text).matchesIPv6(); } /** * Compiled representation of the {@link #IPV6_ADDRESS_REGEX * IPV6_ADDRESS_REGEX} regular expression. */ private static final Pattern IPV6_ADDRESS_PATTERN = Pattern.compile(IPV6_ADDRESS_REGEX); /** * Compiled representation of a regular expression matching the RFC 2732 * IPv6 notation. */ private static final Pattern RFC2732_PATTERN = Pattern.compile("([\\p{XDigit}0-9sS-]+)\\.ipv6-literal\\.net\\.?", Pattern.CASE_INSENSITIVE); /** * Tells whether or not this string matches an IPv6 address using the RFC * 2732 notation. * @return <tt>true</tt> if, and only if, this string matches a IPv6 * address using the RFC 2732 notation. */ public boolean matchesIPv6RFC2732() { Matcher matcher = RFC2732_PATTERN.matcher(value); if (!matcher.matches()) { return false; } return matchesIPv6(matcher.group(1) .replace('-', ':').replace('s', '%')); } /** * Tells whether or not this string matches an IPv6 address using the RFC * 2732 notation. * @param text * the string to be matched * @return <tt>true</tt> if, and only if, this string matches a IPv6 * address using the RFC 2732 notation. */ public static boolean matchesIPv6RFC2732(final String text) { return new InternetAddressMatcher(text).matchesIPv6RFC2732(); } /** * Tells whether or not this string matches an IPv4/IPv6 address. * @param text * the string to be matched * @return <tt>true</tt> if, and only if, this string matches a IP address. */ public static boolean matches(final String text) { return new InternetAddressMatcher(text).matches(); } }