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}