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