InetAddressValidator.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.validator.routines;

  18. import java.io.Serializable;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.List;
  22. import java.util.regex.Pattern;

  23. import org.apache.commons.validator.GenericValidator;

  24. /**
  25.  * <p><b>InetAddress</b> validation and conversion routines (<code>java.net.InetAddress</code>).</p>
  26.  *
  27.  * <p>This class provides methods to validate a candidate IP address.
  28.  *
  29.  * <p>
  30.  * This class is a Singleton; you can retrieve the instance via the {@link #getInstance()} method.
  31.  * </p>
  32.  *
  33.  * @since 1.4
  34.  */
  35. public class InetAddressValidator implements Serializable {

  36.     private static final int MAX_BYTE = 128;

  37.     private static final int IPV4_MAX_OCTET_VALUE = 255;

  38.     private static final int MAX_UNSIGNED_SHORT = 0xffff;

  39.     private static final int BASE_16 = 16;

  40.     private static final long serialVersionUID = -919201640201914789L;

  41.     private static final String IPV4_REGEX =
  42.             "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";

  43.     // Max number of hex groups (separated by :) in an IPV6 address
  44.     private static final int IPV6_MAX_HEX_GROUPS = 8;

  45.     // Max hex digits in each IPv6 group
  46.     private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;

  47.     /**
  48.      * Singleton instance of this class.
  49.      */
  50.     private static final InetAddressValidator VALIDATOR = new InetAddressValidator();

  51.     private static final Pattern DIGITS_PATTERN = Pattern.compile("\\d{1,3}");

  52.     private static final Pattern ID_CHECK_PATTERN = Pattern.compile("[^\\s/%]+");
  53.     /**
  54.      * Returns the singleton instance of this validator.
  55.      * @return the singleton instance of this validator
  56.      */
  57.     public static InetAddressValidator getInstance() {
  58.         return VALIDATOR;
  59.     }

  60.     /** IPv4 RegexValidator */
  61.     private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX);

  62.     /**
  63.      * Checks if the specified string is a valid IPv4 or IPv6 address.
  64.      * @param inetAddress the string to validate
  65.      * @return true if the string validates as an IP address
  66.      */
  67.     public boolean isValid(final String inetAddress) {
  68.         return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress);
  69.     }

  70.     /**
  71.      * Validates an IPv4 address. Returns true if valid.
  72.      * @param inet4Address the IPv4 address to validate
  73.      * @return true if the argument contains a valid IPv4 address
  74.      */
  75.     public boolean isValidInet4Address(final String inet4Address) {
  76.         // verify that address conforms to generic IPv4 format
  77.         final String[] groups = ipv4Validator.match(inet4Address);
  78.         if (groups == null) {
  79.             return false;
  80.         }
  81.         // verify that address subgroups are legal
  82.         for (final String ipSegment : groups) {
  83.             if (GenericValidator.isBlankOrNull(ipSegment)) {
  84.                 return false;
  85.             }
  86.             int iIpSegment = 0;
  87.             try {
  88.                 iIpSegment = Integer.parseInt(ipSegment);
  89.             } catch (final NumberFormatException e) {
  90.                 return false;
  91.             }
  92.             if (iIpSegment > IPV4_MAX_OCTET_VALUE) {
  93.                 return false;
  94.             }
  95.             if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
  96.                 return false;
  97.             }
  98.         }
  99.         return true;
  100.     }

  101.     /**
  102.      * Validates an IPv6 address. Returns true if valid.
  103.      * @param inet6Address the IPv6 address to validate
  104.      * @return true if the argument contains a valid IPv6 address
  105.      *
  106.      * @since 1.4.1
  107.      */
  108.     public boolean isValidInet6Address(String inet6Address) {
  109.         String[] parts;
  110.         // remove prefix size. This will appear after the zone id (if any)
  111.         parts = inet6Address.split("/", -1);
  112.         if (parts.length > 2) {
  113.             return false; // can only have one prefix specifier
  114.         }
  115.         if (parts.length == 2) {
  116.             if (!DIGITS_PATTERN.matcher(parts[1]).matches()) {
  117.                 return false; // not a valid number
  118.             }
  119.             final int bits = Integer.parseInt(parts[1]); // cannot fail because of RE check
  120.             if (bits < 0 || bits > MAX_BYTE) {
  121.                 return false; // out of range
  122.             }
  123.         }
  124.         // remove zone-id
  125.         parts = parts[0].split("%", -1);
  126.         if (parts.length > 2) {
  127.             return false;
  128.         }
  129.         // The id syntax is implementation independent, but it presumably cannot allow:
  130.         // whitespace, '/' or '%'
  131.         if (parts.length == 2 && !ID_CHECK_PATTERN.matcher(parts[1]).matches()) {
  132.             return false; // invalid id
  133.         }
  134.         inet6Address = parts[0];
  135.         final boolean containsCompressedZeroes = inet6Address.contains("::");
  136.         if (containsCompressedZeroes && inet6Address.indexOf("::") != inet6Address.lastIndexOf("::")) {
  137.             return false;
  138.         }
  139.         if (inet6Address.startsWith(":") && !inet6Address.startsWith("::")
  140.                 || inet6Address.endsWith(":") && !inet6Address.endsWith("::")) {
  141.             return false;
  142.         }
  143.         String[] octets = inet6Address.split(":");
  144.         if (containsCompressedZeroes) {
  145.             final List<String> octetList = new ArrayList<>(Arrays.asList(octets));
  146.             if (inet6Address.endsWith("::")) {
  147.                 // String.split() drops ending empty segments
  148.                 octetList.add("");
  149.             } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) {
  150.                 octetList.remove(0);
  151.             }
  152.             octets = octetList.toArray(new String[0]);
  153.         }
  154.         if (octets.length > IPV6_MAX_HEX_GROUPS) {
  155.             return false;
  156.         }
  157.         int validOctets = 0;
  158.         int emptyOctets = 0; // consecutive empty chunks
  159.         for (int index = 0; index < octets.length; index++) {
  160.             final String octet = octets[index];
  161.             if (GenericValidator.isBlankOrNull(octet)) {
  162.                 emptyOctets++;
  163.                 if (emptyOctets > 1) {
  164.                     return false;
  165.                 }
  166.             } else {
  167.                 emptyOctets = 0;
  168.                 // Is last chunk an IPv4 address?
  169.                 if (index == octets.length - 1 && octet.contains(".")) {
  170.                     if (!isValidInet4Address(octet)) {
  171.                         return false;
  172.                     }
  173.                     validOctets += 2;
  174.                     continue;
  175.                 }
  176.                 if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
  177.                     return false;
  178.                 }
  179.                 int octetInt = 0;
  180.                 try {
  181.                     octetInt = Integer.parseInt(octet, BASE_16);
  182.                 } catch (final NumberFormatException e) {
  183.                     return false;
  184.                 }
  185.                 if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) {
  186.                     return false;
  187.                 }
  188.             }
  189.             validOctets++;
  190.         }
  191.         if (validOctets > IPV6_MAX_HEX_GROUPS || validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes) {
  192.             return false;
  193.         }
  194.         return true;
  195.     }
  196. }