001/*
002 * Copyright 2016-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util.args;
022
023
024
025import java.net.InetAddress;
026
027import com.unboundid.util.Debug;
028import com.unboundid.util.NotMutable;
029import com.unboundid.util.ThreadSafety;
030import com.unboundid.util.ThreadSafetyLevel;
031import com.unboundid.util.Validator;
032
033import static com.unboundid.util.args.ArgsMessages.*;
034
035
036
037/**
038 * This class provides an implementation of an argument value validator that
039 * ensures that values can be parsed as valid IPv4 or IPV6 addresses.
040 */
041@NotMutable()
042@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
043public final class IPAddressArgumentValueValidator
044       extends ArgumentValueValidator
045{
046  // Indicates whether to accept IPv4 addresses.
047  private final boolean acceptIPv4Addresses;
048
049  // Indicates whether to accept IPv6 addresses.
050  private final boolean acceptIPv6Addresses;
051
052
053
054  /**
055   * Creates a new IP address argument value validator that will accept both
056   * IPv4 and IPv6 addresses.
057   */
058  public IPAddressArgumentValueValidator()
059  {
060    this(true, true);
061  }
062
063
064
065  /**
066   * Creates a new IP address argument value validator that will accept both
067   * IPv4 and IPv6 addresses.  At least one of the {@code acceptIPv4Addresses}
068   * and {@code acceptIPv6Addresses} arguments must have a value of
069   * {@code true}.
070   *
071   * @param  acceptIPv4Addresses  Indicates whether IPv4 addresses will be
072   *                              accepted.  If this is {@code false}, then the
073   *                              {@code acceptIPv6Addresses} argument must be
074   *                              {@code true}.
075   * @param  acceptIPv6Addresses  Indicates whether IPv6 addresses will be
076   *                              accepted.  If this is {@code false}, then the
077   *                              {@code acceptIPv4Addresses} argument must be
078   *                              {@code true}.
079   */
080  public IPAddressArgumentValueValidator(final boolean acceptIPv4Addresses,
081                                         final boolean acceptIPv6Addresses)
082  {
083    Validator.ensureTrue(acceptIPv4Addresses || acceptIPv6Addresses,
084         "One or both of the acceptIPv4Addresses and acceptIPv6Addresses " +
085              "arguments must have a value of 'true'.");
086
087    this.acceptIPv4Addresses = acceptIPv4Addresses;
088    this.acceptIPv6Addresses = acceptIPv6Addresses;
089  }
090
091
092
093  /**
094   * Indicates whether to accept IPv4 addresses.
095   *
096   * @return  {@code true} if IPv4 addresses should be accepted, or
097   *          {@code false} if not.
098   */
099  public boolean acceptIPv4Addresses()
100  {
101    return acceptIPv4Addresses;
102  }
103
104
105
106  /**
107   * Indicates whether to accept IPv6 addresses.
108   *
109   * @return  {@code true} if IPv6 addresses should be accepted, or
110   *          {@code false} if not.
111   */
112  public boolean acceptIPv6Addresses()
113  {
114    return acceptIPv6Addresses;
115  }
116
117
118
119  /**
120   * {@inheritDoc}
121   */
122  @Override()
123  public void validateArgumentValue(final Argument argument,
124                                    final String valueString)
125         throws ArgumentException
126  {
127    // Look at the provided value to determine whether it has any colons.  If
128    // so, then we'll assume that it's an IPv6 address and we can ensure that
129    // it is only comprised of colons, periods (in case it ends with an IPv4
130    // address), and hexadecimal digits.  If it doesn't have any colons but it
131    // does have one or more periods, then assume that it's an IPv4 address and
132    // ensure that it is only comprised of base-10 digits and periods.  This
133    // initial examination will only perform a very coarse validation.
134    final boolean isIPv6 = (valueString.indexOf(':') >= 0);
135    if (isIPv6)
136    {
137      for (final char c : valueString.toCharArray())
138      {
139        if ((c == ':') || (c == '.') || ((c >= '0') && (c <= '9')) ||
140             ((c >= 'a') && (c <= 'f')) || ((c >= 'A') && (c <= 'F')))
141        {
142          // This character is allowed in an IPv6 address.
143        }
144        else
145        {
146          throw new ArgumentException(ERR_IP_VALIDATOR_ILLEGAL_IPV6_CHAR.get(
147               valueString, argument.getIdentifierString(), c));
148        }
149      }
150    }
151    else if (valueString.indexOf('.') >= 0)
152    {
153      for (final char c : valueString.toCharArray())
154      {
155        if ((c == '.') || ((c >= '0') && (c <= '9')))
156        {
157          // This character is allowed in an IPv4 address.
158        }
159        else
160        {
161          throw new ArgumentException(ERR_IP_VALIDATOR_ILLEGAL_IPV4_CHAR.get(
162               valueString, argument.getIdentifierString(), c));
163        }
164      }
165    }
166    else
167    {
168      throw new ArgumentException(ERR_IP_VALIDATOR_MALFORMED.get(valueString,
169           argument.getIdentifierString()));
170    }
171
172
173    // If we've gotten here, then we know that the value string contains only
174    // characters that are allowed in IP address literal.  Let
175    // InetAddress.getByName do the heavy lifting for the rest of the
176    // validation.
177    try
178    {
179      InetAddress.getByName(valueString);
180    }
181    catch (final Exception e)
182    {
183      Debug.debugException(e);
184      throw new ArgumentException(
185           ERR_IP_VALIDATOR_MALFORMED.get(valueString,
186                argument.getIdentifierString()),
187           e);
188    }
189
190
191    if (isIPv6)
192    {
193      if (! acceptIPv6Addresses)
194      {
195        throw new ArgumentException(ERR_IP_VALIDATOR_IPV6_NOT_ACCEPTED.get(
196             valueString, argument.getIdentifierString()));
197      }
198    }
199    else if (! acceptIPv4Addresses)
200    {
201      throw new ArgumentException(ERR_IP_VALIDATOR_IPV4_NOT_ACCEPTED.get(
202           valueString, argument.getIdentifierString()));
203    }
204  }
205
206
207
208  /**
209   * Indicates whether the provided string represents a valid IPv4 or IPv6
210   * address.
211   *
212   * @param  s  The string for which to make the determination.
213   *
214   * @return  {@code true} if the provided string represents a valid IPv4 or
215   *          IPv6 address, or {@code false} if not.
216   */
217  public static boolean isValidNumericIPAddress(final String s)
218  {
219    return isValidNumericIPv4Address(s) ||
220         isValidNumericIPv6Address(s);
221  }
222
223
224
225  /**
226   * Indicates whether the provided string is a valid IPv4 address.
227   *
228   * @param  s  The string for which to make the determination.
229   *
230   * @return  {@code true} if the provided string represents a valid IPv4
231   *          address, or {@code false} if not.
232   */
233  public static boolean isValidNumericIPv4Address(final String s)
234  {
235    if ((s == null) || (s.length() == 0))
236    {
237      return false;
238    }
239
240    for (final char c : s.toCharArray())
241    {
242      if ((c == '.') || ((c >= '0') && (c <= '9')))
243      {
244        // This character is allowed in an IPv4 address.
245      }
246      else
247      {
248        return false;
249      }
250    }
251
252    try
253    {
254      InetAddress.getByName(s);
255      return true;
256    }
257    catch (final Exception e)
258    {
259      Debug.debugException(e);
260      return false;
261    }
262  }
263
264
265
266  /**
267   * Indicates whether the provided string is a valid IPv6 address.
268   *
269   * @param  s  The string for which to make the determination.
270   *
271   * @return  {@code true} if the provided string represents a valid IPv6
272   *          address, or {@code false} if not.
273   */
274  public static boolean isValidNumericIPv6Address(final String s)
275  {
276    if ((s == null) || (s.length() == 0))
277    {
278      return false;
279    }
280
281    boolean colonFound = false;
282    for (final char c : s.toCharArray())
283    {
284      if (c == ':')
285      {
286        // This character is allowed in an IPv6 address, and you can't have a
287        // valid IPv6 address without colons.
288        colonFound = true;
289      }
290      else if ((c == '.') || ((c >= '0') && (c <= '9')) ||
291           ((c >= 'a') && (c <= 'f')) || ((c >= 'A') && (c <= 'F')))
292      {
293        // This character is allowed in an IPv6 address.
294      }
295      else
296      {
297        return false;
298      }
299    }
300
301    if (colonFound)
302    {
303      try
304      {
305        InetAddress.getByName(s);
306        return true;
307      }
308      catch (final Exception e)
309      {
310        Debug.debugException(e);
311      }
312    }
313
314    return false;
315  }
316
317
318
319  /**
320   * Retrieves a string representation of this argument value validator.
321   *
322   * @return  A string representation of this argument value validator.
323   */
324  @Override()
325  public String toString()
326  {
327    final StringBuilder buffer = new StringBuilder();
328    toString(buffer);
329    return buffer.toString();
330  }
331
332
333
334  /**
335   * Appends a string representation of this argument value validator to the
336   * provided buffer.
337   *
338   * @param  buffer  The buffer to which the string representation should be
339   *                 appended.
340   */
341  public void toString(final StringBuilder buffer)
342  {
343    buffer.append("IPAddressArgumentValueValidator(acceptIPv4Addresses=");
344    buffer.append(acceptIPv4Addresses);
345    buffer.append(", acceptIPv6Addresses=");
346    buffer.append(acceptIPv6Addresses);
347    buffer.append(')');
348  }
349}