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