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}