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