All Downloads are FREE. Search and download functionalities are using the official Maven repository.

inet.ipaddr.format.validate.Validator Maven / Gradle / Ivy

There is a newer version: 5.5.1
Show newest version
package inet.ipaddr.format.validate;

import inet.ipaddr.HostName;
import inet.ipaddr.HostNameException;
import inet.ipaddr.HostNameParameters;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import inet.ipaddr.IPAddressStringException;
import inet.ipaddr.IPAddressStringParameters;
import inet.ipaddr.IPAddress.IPVersion;
import inet.ipaddr.IPAddressStringParameters.IPVersionAddressStringParameters;
import inet.ipaddr.IPAddressStringParameters.RangeParameters;
import inet.ipaddr.format.validate.AddressProvider.AllCreator;
import inet.ipaddr.format.validate.AddressProvider.MaskCreator;
import inet.ipaddr.format.validate.AddressProvider.ParsedAddressProvider;
import inet.ipaddr.format.validate.ParsedAddress.ParseData;
import inet.ipaddr.ipv4.IPv4Address;
import inet.ipaddr.ipv4.IPv4AddressSegment;
import inet.ipaddr.ipv4.IPv4AddressStringParameters;
import inet.ipaddr.ipv6.IPv6Address;
import inet.ipaddr.ipv6.IPv6AddressSegment;
import inet.ipaddr.ipv6.IPv6AddressStringParameters;

/**
 * Validates host strings, address strings, and prefix lengths.
 * 
 * @author sfoley
 *
 */
public class Validator implements HostIdentifierStringValidator {
	
	private static final int chars[] = new int[128]; static {
		int charArray[] = chars;
		int i = 0;
		for(char c = '0'; i < 10; i++, c++) {
			charArray[c] = i;
		}
		for(char c = 'a', c2 = 'A'; i < 16; i++, c++, c2++) {
			charArray[c] = charArray[c2] = i;
		}
	}

	private static final int MAX_HOST_LENGTH = 253;
	private static final int MAX_HOST_SEGMENTS = 127;
	private static final int MAX_LABEL_LENGTH = 63;
	
	private static final int EMPTY_INDICES[] = new int[0];
	private static final ParsedAddressQualifier PREFIX_CACHE[] = new ParsedAddressQualifier[IPv6Address.BIT_COUNT + 1];
	private static final ParsedAddressQualifier NO_QUALIFIER = new ParsedAddressQualifier();
	private static final ParsedHost DEFAULT_EMPTY_HOST = new ParsedHost("", EMPTY_INDICES, null, NO_QUALIFIER);
	private static final IPAddressStringParameters DEFAULT_PREFIX_OPTIONS = new IPAddressStringParameters.Builder().toParams();

	public static final HostIdentifierStringValidator VALIDATOR = new Validator();
	
	/**
	 * Singleton - this class has no state
	 */
	private Validator() {}

	@Override
	public ParsedHost validateHost(HostName fromHost) throws HostNameException {
		return validateHostImpl(fromHost);
	}

	@Override
	public AddressProvider validateAddress(IPAddressString fromString) throws IPAddressStringException {
		return validateAddressImpl(fromString);
	}

	@Override
	public int validatePrefix(CharSequence fullAddr, IPVersion version) throws IPAddressStringException {
		return validatePrefixImpl(fullAddr, version);
	}
	
	static AddressProvider validateAddressImpl(IPAddressString fromString) throws IPAddressStringException {
		String str = fromString.toString();
		IPAddressStringParameters validationOptions = fromString.getValidationOptions();
		ParseData parseData = validateAddress(validationOptions, str, 0, str.length());
		return createProvider(
				null,
				fromString,
				str,
				validationOptions,
				parseData,
				parseQualifier(str,
						validationOptions,
						parseData.isPrefixed,
						parseData.isZoned,
						parseData.isEmpty,
						parseData.qualifierIndex,
						str.length(),
						parseData.ipVersion));
	}
	
	private static ParseData validateAddress(
			final IPAddressStringParameters validationOptions,
			final String str,
			final int strStartIndex,
			int strEndIndex) throws IPAddressStringException {
		ParseData parseData = new ParseData();
		final IPv6AddressStringParameters ipv6Options = validationOptions.getIPv6Parameters();
		final IPv4AddressStringParameters ipv4Options = validationOptions.getIPv4Parameters();
		
		int index = strStartIndex;
		
		//per segment variables
		int lastSeparatorIndex = -1, digitCount = 0, leadingZeroCount = 0, rangeWildcardIndex = -1, singleWildcardCount = 0, wildcardCount = 0;
		boolean notOctal = false, notDecimal = false, uppercase = false,  isIPv4Hex = false;
		int frontDigitCount = 0, frontLeadingZeroCount = 0;
		boolean frontNotOctal = false, frontNotDecimal = false, frontUppercase = false,  frontIsIPv4Hex = false;

		while(index <= strEndIndex) {
			char currentChar;
			if(index == strEndIndex) {
				parseData.addressEndIndex = index;
				//current char is either . or : to handle last segment, unless we have double :: in which case we already handled last segment
				IPVersion version = parseData.ipVersion;
				if(version != null) {
					if(version.isIPv4()) {
						currentChar = IPv4Address.SEGMENT_SEPARATOR;
					} else { //ipv6
						if(index == lastSeparatorIndex + 1) {
							if(index == parseData.consecutiveIPv6SepIndex + 2) {
								//ends with ::, we've already parsed the last segment
								break;
							}
							throw new IPAddressStringException(str, "ipaddress.error.cannot.end.with.single.separator");
						} else if(parseData.mixedParsedAddress != null) {
							//no need to parse the last segment, since it is mixed we already have
							break;
						} else {
							currentChar = IPv6Address.SEGMENT_SEPARATOR;
						}
					} 
				} else {
					//no segment separator so far and segmentCount is 0
					//it could be all addresses like "*", ipv4 single segment like 12345 , empty "", or prefix only like /64
					if(index == strStartIndex) {
						//it is prefix-only or ""
						parseData.isEmpty = true;
						break;
					} else if(wildcardCount > 0) {// "*"
						if(singleWildcardCount > 0 || rangeWildcardIndex >= 0 || leadingZeroCount > 0 || digitCount > 0 || isIPv4Hex) {//wildcards must appear alone
							throw new IPAddressStringException(str, index, true);
						}
						parseData.anyWildcard = true;
						parseData.isAll = true;
						break;
					}
					//TODO handle new values depending on characters and length - check base 85 flag, check for ipv6 chars, in obth cases check count of chars
					//it is ipv4 single segment like 4294967295 which is equivalent to 255.255.255.255
					currentChar = IPv4Address.SEGMENT_SEPARATOR;
				}
			} else {
				currentChar = str.charAt(index);
			}
			
			//evaluate the character
			if(currentChar >= '1' && currentChar <= '9') {
				++digitCount;
				++index;
				notOctal |= currentChar >= '8';
			} else if(currentChar >= 'a' && currentChar <= 'f') {
				++digitCount;
				++index;
				notOctal = notDecimal = true;
			} else if(currentChar == '0') {
				if(digitCount > 0) {
					++digitCount;
				} else {
					++leadingZeroCount;
				}
				++index;
			} else if(currentChar == IPv4Address.SEGMENT_SEPARATOR) {
				if(parseData.ipVersion != null && parseData.ipVersion.isIPv6()) {
					//mixed address like 1:2:3:4:5:6:1.2.3.4
					int segCount = parseData.segmentCount;
					if(!ipv6Options.allowMixed) {
						throw new IPAddressStringException(str, "ipaddress.error.no.mixed");
					}
					int totalSegmentCount = parseData.segmentCount + IPv6Address.MIXED_REPLACED_SEGMENT_COUNT;
					if(totalSegmentCount > IPv6Address.SEGMENT_COUNT) {
						throw new IPAddressStringException(str, "ipaddress.error.ipv6.too.many.segments");
					}
					if(wildcardCount > 0) {
						parseData.anyWildcard = true;
					}
					boolean isNotExpandable = wildcardCount > 0 && parseData.consecutiveIPv6SepIndex < 0;
					if(isNotExpandable && 
							totalSegmentCount < IPv6Address.SEGMENT_COUNT && 
							ipv6Options.allowWildcardedSeparator) {
						//the '*' is covering an additional ipv6 segment (eg 1:2:3:4:5:*.2.3.4, the * covers both an ipv4 and ipv6 segment)
						parseData.values[segCount][ParseData.UPPER_INDEX] = IPv6Address.MAX_VALUE_PER_SEGMENT;
						parseData.flags[segCount][ParseData.WILDCARD_INDEX] = true;
						parseData.segmentCount++;
					}
					IPAddressStringParameters mixedOptions = ipv6Options.getMixedParameters();
					ParseData mixedParseData = validateAddress(mixedOptions, str, lastSeparatorIndex + 1, strEndIndex);
					parseData.mixedParsedAddress = createAddressProvider(null, null, str, mixedOptions, mixedParseData, NO_QUALIFIER);
					index = mixedParseData.addressEndIndex;
				} else {
					//end of an ipv4 segment
					parseData.ipVersion = IPVersion.IPV4;
					int segCount = parseData.segmentCount;
					if(segCount == 0) {
						parseData.initSegmentData(IPv4Address.SEGMENT_COUNT);
					} else if(segCount >= IPv4Address.SEGMENT_COUNT) {
						throw new IPAddressStringException(str, "ipaddress.error.ipv4.too.many.segments");
					}
					long vals[] = parseData.values[segCount];
					int indices[] = parseData.indices[segCount];
					boolean flags[] = parseData.flags[segCount];
					if(wildcardCount > 0) {
						if(!ipv4Options.rangeOptions.allowsWildcard()) {
							throw new IPAddressStringException(str, "ipaddress.error.no.wildcard");
						}
						if(singleWildcardCount > 0 || rangeWildcardIndex >= 0 || leadingZeroCount > 0 || digitCount > 0 || isIPv4Hex) {//wildcards must appear alone
							throw new IPAddressStringException(str, index, true);
						}
						parseData.anyWildcard = true;
						flags[ParseData.WILDCARD_INDEX] = true;
						vals[ParseData.UPPER_INDEX] = IPv4Address.MAX_VALUE_PER_SEGMENT;
						int startIndex = index - wildcardCount;
						assignAttributes(startIndex, index, indices, startIndex);
					} else {
						if(leadingZeroCount > 0 && !ipv4Options.allowLeadingZeros && !ipv4Options.inet_aton_octal) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv4.segment.leading.zeros");
						}
						long value = 0;
						boolean isStandard = false;
						int radix;
						int startIndex = index - digitCount;
						int leadingZeroStartIndex = startIndex - leadingZeroCount;
						int endIndex = index;
						boolean isSingleWildcard;
						boolean isJustZero;
						int maxChars = getMaxIPv4StringLength(3, 8);
						if(digitCount == 0) {
							if(leadingZeroCount == 0) {
								//starts with '.' or two consecutive '.'
								throw new IPAddressStringException(str, "ipaddress.error.ipv4.empty.segment");
							}
							isSingleWildcard = false;
							isJustZero = true;
							startIndex--;
							digitCount++;
							leadingZeroCount--;
							if(isIPv4Hex) {
								if(!ipv4Options.inet_aton_hex) {
									throw new IPAddressStringException(str, "ipaddress.error.ipv4.segment.hex");
								}
								radix = 16;
							} else if(leadingZeroCount > 0 && !ipv4Options.allowLeadingZeros && ipv4Options.inet_aton_octal) {
								radix = 8;
							} else {
								radix = 10;
							}
							flags[ParseData.STANDARD_STR_INDEX] = true;
							assignAttributes(startIndex, endIndex, indices, radix, leadingZeroStartIndex);
						} else {
							//Note: we cannot do max value check on ipv4 until after all segments have been read due to inet_aton joined segments, 
							//although we can do a preliminary check here that is in fact needed to prevent overflow when calculating values later
							if(digitCount > maxChars + leadingZeroCount) { 
								throw new IPAddressStringException(str, "ipaddress.error.ipv4.segment.too.long");
							}
							isJustZero = false;
							isSingleWildcard = singleWildcardCount > 0;
							if(isIPv4Hex) {
								if(!ipv4Options.inet_aton_hex) {
									throw new IPAddressStringException(str, "ipaddress.error.ipv4.segment.hex");
								}
								radix = 16;
								if(isSingleWildcard) {
									if(rangeWildcardIndex >= 0) {
										throw new IPAddressStringException(str, index, true);
									}
									parseSingleWildcard16(str, startIndex, endIndex, singleWildcardCount, indices, vals, flags, leadingZeroStartIndex, ipv4Options);
								} else {
									value = parseLong16(str, startIndex, endIndex);
								}
							} else {
								boolean isOctal = leadingZeroCount > 0 && ipv4Options.inet_aton_octal;
								if(isOctal) {
									if(notOctal) {
										throw new IPAddressStringException(str, "ipaddress.error.ipv4.invalid.octal.digit");
									}
									radix = 8;
									if(isSingleWildcard) {
										if(rangeWildcardIndex >= 0) {
											throw new IPAddressStringException(str, index, true);
										}
										parseSingleWildcard8(str, startIndex, endIndex, singleWildcardCount, indices, vals, flags, leadingZeroStartIndex, ipv4Options);
									} else {
										value = parseLong8(str, startIndex, endIndex);
									}
								} else {
									if(notDecimal) {
										throw new IPAddressStringException(str, "ipaddress.error.ipv4.invalid.decimal.digit");
									}
									radix = 10;
									if(isSingleWildcard) {
										if(rangeWildcardIndex >= 0) {
											throw new IPAddressStringException(str, index, true);
										}
										parseSingleWildcard10(str, startIndex, endIndex, singleWildcardCount, indices, vals, flags, leadingZeroStartIndex, ipv4Options);
									} else {
										value = parseLong10(str, startIndex, endIndex);
										isStandard = true;
									}
								}
							}
						}
						if(rangeWildcardIndex >= 0) {
							if(!ipv4Options.rangeOptions.allowsRangeSeparator()) {
								throw new IPAddressStringException(str, "ipaddress.error.no.range");
							} else if(!ipv4Options.allowLeadingZeros && frontLeadingZeroCount > 0) {
								throw new IPAddressStringException(str, "ipaddress.error.ipv4.segment.leading.zeros");
							} else if(frontDigitCount > maxChars + frontLeadingZeroCount) { 
								throw new IPAddressStringException(str, "ipaddress.error.ipv4.segment.too.long");
							}
							
							int frontRadix;
							long front;
							int frontStartIndex = rangeWildcardIndex - frontDigitCount, frontEndIndex = rangeWildcardIndex;
							int frontLeadingZeroStartIndex = frontStartIndex - frontLeadingZeroCount;
							if(frontIsIPv4Hex) {
								if(!ipv4Options.inet_aton_hex) {
									throw new IPAddressStringException(str, "ipaddress.error.ipv4.segment.hex");
								}
								front = parseLong16(str, frontStartIndex, frontEndIndex);
								frontRadix = 16;
							} else { 
								boolean frontIsOctal = frontLeadingZeroCount > 0 && !frontIsIPv4Hex && ipv4Options.inet_aton_octal;
								if(frontIsOctal) {
									if(frontNotOctal) {
										throw new IPAddressStringException(str, "ipaddress.error.ipv4.invalid.octal.digit");
									}
									front = parseLong8(str, frontStartIndex, frontEndIndex);
									frontRadix = 8;
								} else {
									if(frontNotDecimal) {
										throw new IPAddressStringException(str, "ipaddress.error.ipv4.invalid.decimal.digit");
									}
									if(frontLeadingZeroCount == 0) {
										flags[ParseData.STANDARD_STR_INDEX] = true; 
										if(isStandard && leadingZeroCount == 0) {
											flags[ParseData.STANDARD_RANGE_STR_INDEX] = true;
										}
									}
									front = parseLong10(str, frontStartIndex, frontEndIndex);
									frontRadix = 10;
								}
							}
							if(front > value) {
								throw new IPAddressStringException(str, "ipaddress.error.invalidRange");
							} //else we would have to flip the values and the indices and we would not set or flags[ParseData.STANDARD_RANGE_STR_INDEX]
							if(!isJustZero) {
								assignAttributes(frontStartIndex, frontEndIndex, startIndex, endIndex, indices, frontLeadingZeroStartIndex, leadingZeroStartIndex, frontRadix, radix);
								vals[ParseData.LOWER_INDEX] = front;
								vals[ParseData.UPPER_INDEX] = value;
							}
							frontDigitCount = frontLeadingZeroCount = 0;
							frontNotOctal = frontNotDecimal = frontUppercase = frontIsIPv4Hex = false;
						} else if(!isSingleWildcard && !isJustZero) {
							if(isStandard) {
								flags[ParseData.STANDARD_STR_INDEX] = true; 
							}
							assignAttributes(startIndex, endIndex, indices, radix, leadingZeroStartIndex);
							vals[ParseData.LOWER_INDEX] = vals[ParseData.UPPER_INDEX] = value;
						}	
					}
					parseData.segmentCount++;
					lastSeparatorIndex = index;
					digitCount = singleWildcardCount = wildcardCount = leadingZeroCount = 0;
					rangeWildcardIndex = -1;
					notOctal = notDecimal = uppercase = isIPv4Hex = false;
					++index;
				}
			} else if(currentChar == IPv6Address.SEGMENT_SEPARATOR) {
				//end of an ipv6 segment
				if(parseData.ipVersion != null && parseData.ipVersion.isIPv4()) {
					throw new IPAddressStringException(str, "ipaddress.error.ipv6.separator");
				}
				if(isIPv4Hex) {
					throw new IPAddressStringException(str,"ipaddress.error.ipv6.character");
				}
				parseData.ipVersion = IPVersion.IPV6;
				int segCount = parseData.segmentCount;
				if(segCount == 0) {
					parseData.initSegmentData(IPv6Address.SEGMENT_COUNT);
				} else if(segCount >= IPv6Address.SEGMENT_COUNT) {
					throw new IPAddressStringException(str, "ipaddress.error.ipv6.too.many.segments");
				}
				long vals[] = parseData.values[segCount];
				boolean flags[] = parseData.flags[segCount];
				int indices[] = parseData.indices[segCount];
				if(wildcardCount > 0) {
					if(!ipv6Options.rangeOptions.allowsWildcard()) {
						throw new IPAddressStringException(str, "ipaddress.error.no.wildcard");
					}
					if(singleWildcardCount > 0 || rangeWildcardIndex >= 0 || leadingZeroCount > 0 || digitCount > 0) {//wildcards must appear alone
						throw new IPAddressStringException(str, index, true);
					}
					parseData.anyWildcard = true;
					flags[ParseData.WILDCARD_INDEX] = true;
					vals[ParseData.UPPER_INDEX] = IPv6Address.MAX_VALUE_PER_SEGMENT;
					int startIndex = index - wildcardCount;
					assignAttributes(startIndex, index, indices, startIndex);
					parseData.segmentCount++;
				} else {
					if(index == strStartIndex) {
						if(index + 1 == strEndIndex) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv6.too.few.segments");
						}
						if(str.charAt(index + 1) != IPv6Address.SEGMENT_SEPARATOR) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv6.cannot.start.with.single.separator");
						}
						//no segment, so we do not increment segmentCount
					} else if(index == lastSeparatorIndex + 1) {
						if(parseData.consecutiveIPv6SepIndex >= 0) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv6.ambiguous");
						}
						parseData.consecutiveIPv6SepIndex = index - 1;
						assignAttributes(index, index, indices, index);
						parseData.segmentCount++;
					} else {
						if(!ipv6Options.allowLeadingZeros && leadingZeroCount > 0) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv6.segment.leading.zeros");
						}
						if(digitCount > IPv6AddressSegment.MAX_CHARS) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv6.segment.too.long");
						}
						long value = 0;
						boolean isStandard = false;
						int startIndex = index - digitCount;
						int leadingZeroStartIndex = startIndex - leadingZeroCount;
						int endIndex = index;
						if(!ipv6Options.allowUnlimitedLeadingZeros && endIndex - leadingZeroStartIndex > IPv6AddressSegment.MAX_CHARS) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv6.segment.too.long");
						}
						boolean isJustZero = false;
						if(digitCount == 0) {
							//note we know there is a zero as we have already checked for empty segments so we know leadingZeroCount is non-zero when digitCount is zero
							startIndex--;
							digitCount++;
							leadingZeroCount--;
							isJustZero = true;
							flags[ParseData.STANDARD_STR_INDEX] = true; 
							assignAttributes(startIndex, endIndex, indices, leadingZeroStartIndex);
						} else if(digitCount > IPv6AddressSegment.MAX_CHARS) {
							throw new IPAddressStringException(str, "ipaddress.error.ipv6.segment.too.long");
						} else if(singleWildcardCount > 0) {
							if(rangeWildcardIndex >= 0) {
								throw new IPAddressStringException(str, index, true);
							}
							parseSingleWildcard16(str, startIndex, endIndex, singleWildcardCount, indices, vals, flags, leadingZeroStartIndex, ipv6Options);
						} else {
							value = parseLong16(str, startIndex, endIndex);
							isStandard = !uppercase;
						} //else we need do nothing as we know digitCount == 0 means we have only 0 characters
						if(rangeWildcardIndex >= 0) {
							if(!ipv6Options.rangeOptions.allowsRangeSeparator()) {
								throw new IPAddressStringException(str, "ipaddress.error.no.range");
							} else if(frontIsIPv4Hex) {
								throw new IPAddressStringException(str, index, false);
							} else if(!ipv6Options.allowLeadingZeros && frontLeadingZeroCount > 0) {
								throw new IPAddressStringException(str, "ipaddress.error.ipv6.segment.leading.zeros");
							} else if(frontDigitCount > IPv6AddressSegment.MAX_CHARS) {
								throw new IPAddressStringException(str, "ipaddress.error.ipv6.segment.too.long");
							}
							int frontStartIndex = rangeWildcardIndex - frontDigitCount, frontEndIndex = rangeWildcardIndex;
							int frontLeadingZeroStartIndex = frontStartIndex - frontLeadingZeroCount;
							if(!ipv6Options.allowUnlimitedLeadingZeros && frontEndIndex - frontLeadingZeroStartIndex > IPv6AddressSegment.MAX_CHARS) {
								throw new IPAddressStringException(str, "ipaddress.error.ipv6.segment.too.long");
							}
							long front = parseLong16(str, frontStartIndex, frontEndIndex);
							if(front > value) {
								throw new IPAddressStringException(str, "ipaddress.error.invalidRange");
							} //else we would have to flip the values and the indices and we would not set or flags[ParseData.STANDARD_RANGE_STR_INDEX]
							if(!isJustZero) {
								if(frontLeadingZeroCount == 0 && !frontUppercase) {
									flags[ParseData.STANDARD_STR_INDEX] = true;
									if(isStandard && leadingZeroCount == 0) {
										flags[ParseData.STANDARD_RANGE_STR_INDEX] = true;
									}
								}
								assignAttributes(frontStartIndex, frontEndIndex, startIndex, endIndex, indices, frontLeadingZeroStartIndex, leadingZeroStartIndex, IPv6Address.DEFAULT_TEXTUAL_RADIX, IPv6Address.DEFAULT_TEXTUAL_RADIX);
								vals[ParseData.LOWER_INDEX] = front;
								vals[ParseData.UPPER_INDEX] = value;
							}
							frontDigitCount = frontLeadingZeroCount = 0;
							frontNotOctal = frontNotDecimal = frontUppercase = frontIsIPv4Hex = false;
						} else if(singleWildcardCount == 0 && !isJustZero) {
							if(isStandard) {
								flags[ParseData.STANDARD_STR_INDEX] = true;
							}
							assignAttributes(startIndex, endIndex, indices, IPv6Address.DEFAULT_TEXTUAL_RADIX, leadingZeroStartIndex);
							vals[ParseData.LOWER_INDEX] = vals[ParseData.UPPER_INDEX] = value;
						}
						parseData.segmentCount++;
					}
				}
				lastSeparatorIndex = index;
				rangeWildcardIndex = -1;
				digitCount = singleWildcardCount = wildcardCount = leadingZeroCount = 0;
				notOctal = notDecimal = uppercase = isIPv4Hex = false;
				++index;
			} else if(currentChar >= 'A' && currentChar <= 'F') {
				++digitCount;
				++index;
				notOctal = notDecimal = uppercase = true;
			} else if(currentChar == IPAddress.PREFIX_LEN_SEPARATOR) {
				parseData.isPrefixed = true;
				strEndIndex = index;
				parseData.qualifierIndex = index + 1;
			} else {
				boolean b = false;
				if(currentChar == IPAddress.SEGMENT_WILDCARD || (b = (currentChar == IPAddress.SEGMENT_SQL_WILDCARD))) {
					if(b && ipv6Options.allowZone) { //the zone character % is also the SQL wildcard, we so cannot support both at the same time
						parseData.isZoned = true;
						strEndIndex = index;
						parseData.qualifierIndex = index + 1;
					} else {
						++wildcardCount;
						++index;
					}
				} else if(currentChar == IPAddress.RANGE_SEPARATOR) {
					if(rangeWildcardIndex >= 0) {
						throw new IPAddressStringException(str, index, true);
					}
					rangeWildcardIndex = index;
					frontDigitCount = digitCount;
					frontLeadingZeroCount = leadingZeroCount;
					if(frontDigitCount == 0) {
						if(frontLeadingZeroCount == 0) {
							throw new IPAddressStringException(str, "ipaddress.error.empty.start.of.range");
						}
						frontDigitCount++;
						frontLeadingZeroCount--;
					}
					frontNotOctal = notOctal;
					frontNotDecimal = notDecimal;
					frontUppercase = uppercase;
					frontIsIPv4Hex = isIPv4Hex;	
					leadingZeroCount = digitCount = 0;
					notOctal = notDecimal = uppercase = isIPv4Hex = false;
					++index;
				} else if(currentChar == IPAddress.SEGMENT_SQL_SINGLE_WILDCARD) {
					++digitCount;
					++index;
					++singleWildcardCount;
				} else if(currentChar == 'x') {
					if(digitCount > 0 || leadingZeroCount != 1 || isIPv4Hex || singleWildcardCount > 0) {
						throw new IPAddressStringException(str, index, true);
					}
					isIPv4Hex = true;
					leadingZeroCount = 0;
					++index;
				} else {//TODO base 85 - handle some additional chars without throwing if we allow base 85.  But if we do, we need to throw later when we find a char that is no base-85, like a segment separator
					//invalid char
					throw new IPAddressStringException(str, index, false);
				}
			}
		}
		return parseData;
	}
	
	private static AddressProvider createProvider(
			final HostName fromHost,
			final IPAddressString fromString,
			final String fullAddr,
			final IPAddressStringParameters validationOptions,
			final ParseData parseData,
			final ParsedAddressQualifier qualifier) throws IPAddressStringException {
		if(parseData.ipVersion == null) {
			IPVersion version = qualifier.inferVersion(validationOptions);
			if(parseData.isEmpty) {
				if(qualifier.getNetworkPrefixLength() != null) {
					if(validationOptions.allowPrefixOnly) {
						return new MaskCreator(qualifier, version);
					}
					throw new IPAddressStringException(fullAddr, "ipaddress.error.prefix.only");
				} else {
					if(!validationOptions.allowEmpty) {
						throw new IPAddressStringException(fullAddr, "ipaddress.error.empty");
					}
					if(validationOptions.emptyIsLoopback) {
						return AddressProvider.LOOPBACK_CREATOR;
					}
					return AddressProvider.EMPTY_PROVIDER;
				}
			} else { //isAll
				if(validationOptions.allowAll) {
					if(qualifier == NO_QUALIFIER && version == null) {
						return AddressProvider.ALL_ADDRESSES_CREATOR;
					}
					return new AllCreator(qualifier, version, fromHost, fromString);
				}
				throw new IPAddressStringException(fullAddr, "ipaddress.error.all");
			}
		} else {
			ParsedAddress valueCreator = createAddressProvider(fromHost, fromString, fullAddr, validationOptions, parseData, qualifier);
			return new ParsedAddressProvider(valueCreator);
		}
	}

	private static ParsedAddress createAddressProvider(
			final HostName fromHost,
			final IPAddressString fromString,
			final String fullAddr,
			final IPAddressStringParameters validationOptions,
			final ParseData parseData,
			final ParsedAddressQualifier qualifier) throws IPAddressStringException {
		final int segCount = parseData.segmentCount;
		IPVersion version = parseData.ipVersion;
		if(version.isIPv4()) {
			int missingCount = IPv4Address.SEGMENT_COUNT - segCount;
			final IPv4AddressStringParameters ipv4Options = validationOptions.getIPv4Parameters();
			if(missingCount > 0 && !parseData.anyWildcard && !ipv4Options.inet_aton_joinedSegments) {
				throw new IPAddressStringException(fullAddr, "ipaddress.error.ipv4.too.few.segments");
			}
			//here we check whether values are too large or strings too long
			long oneSegmentMax = getMaxIPv4Value(0);
			for(int i = 0; i < segCount; i++) {
				long max;
				if(i == segCount - 1 && missingCount > 0) {
					max = getMaxIPv4Value(missingCount);
				} else {
					max = oneSegmentMax;
				}
				boolean flags[] = parseData.flags[i];
				long values[] = parseData.values[i];
				int indices[] = parseData.indices[i];
				int lowerRadix = indices[ParseData.LOWER_RADIX_INDEX];
				int maxDigits = getMaxIPv4StringLength(missingCount, lowerRadix);
				if(flags[ParseData.SINGLE_WILDCARD_INDEX]) {
					if(values[ParseData.LOWER_INDEX] > max) {
						throw new IPAddressStringException(fullAddr, "ipaddress.error.ipv4.segment.too.large");
					}
					if(values[ParseData.UPPER_INDEX] > max) {
						values[ParseData.UPPER_INDEX] = max;
					}
					if(!ipv4Options.allowUnlimitedLeadingZeros) {
						if(indices[ParseData.LOWER_STR_END_INDEX] - indices[ParseData.LOWER_STR_DIGITS_INDEX] -  getStringPrefixCharCount(lowerRadix) > maxDigits) {
							throw new IPAddressStringException(fullAddr, "ipaddress.error.ipv4.segment.too.long");
						}
					}
				} else {
					if(values[ParseData.UPPER_INDEX] > max) {
						throw new IPAddressStringException(fullAddr, "ipaddress.error.ipv4.segment.too.large");
					}
					int upperRadix = indices[ParseData.UPPER_RADIX_INDEX];
					int maxUpperDigits = getMaxIPv4StringLength(missingCount, upperRadix);
					if(!ipv4Options.allowUnlimitedLeadingZeros) {
						if(indices[ParseData.LOWER_STR_END_INDEX] - indices[ParseData.LOWER_STR_DIGITS_INDEX] - getStringPrefixCharCount(lowerRadix) > maxDigits) {
							throw new IPAddressStringException(fullAddr, "ipaddress.error.ipv4.segment.too.long");
						}
						if(indices[ParseData.UPPER_STR_END_INDEX] - indices[ParseData.UPPER_STR_DIGITS_INDEX] - getStringPrefixCharCount(upperRadix) > maxUpperDigits) {
							throw new IPAddressStringException(fullAddr, "ipaddress.error.ipv4.segment.too.long");
						}
					}
				}
			}
		} else {
			int totalSegmentCount = segCount;
			if(parseData.mixedParsedAddress != null) {
				totalSegmentCount += IPv6Address.MIXED_REPLACED_SEGMENT_COUNT;
			}
			if(totalSegmentCount < IPv6Address.SEGMENT_COUNT && !parseData.anyWildcard && !parseData.isCompressed()) {//TODO new address values - do not throw here.
				throw new IPAddressStringException(fullAddr, "ipaddress.error.ipv6.too.few.segments");
			}
		}
		ParsedAddress valueCreator = new ParsedAddress(fromHost, fromString, fullAddr, parseData, version, qualifier);
		return valueCreator;
	}
	
	static int validatePrefixImpl(CharSequence fullAddr, IPVersion version) throws IPAddressStringException {
		ParsedAddressQualifier qualifier = validatePrefix(fullAddr, DEFAULT_PREFIX_OPTIONS, 0, fullAddr.length(), version);
		if(qualifier == null) {
			throw new IPAddressStringException(fullAddr.toString(), "ipaddress.error.invalidCIDRPrefix");
		}
		return qualifier.getNetworkPrefixLength();
	}

	private static ParsedAddressQualifier validatePrefix(
			final CharSequence fullAddr,
			final IPAddressStringParameters validationOptions,
			final int index,
			final int endIndex,
			final IPVersion ipVersion) throws IPAddressStringException {
		if(index == fullAddr.length()) {
			return null;
		}
		boolean isPrefix = true;
		int digitCount, leadingZeros;
		digitCount = leadingZeros = 0;
		for(int i = index; isPrefix && i < endIndex; i++) {
			char c = fullAddr.charAt(i);
			if(c >= '1' && c <= '9') {
				++digitCount;
			} else if(c == '0') {
				if(digitCount > 0) {
					++digitCount;
				} else {
					++leadingZeros;
				}
			} else {
				isPrefix = false;
			}
		}
		//we treat as a prefix if all the characters were digits, even if there were too many, unless the mask options allow for inet_aton single segment
		if(isPrefix) {
			boolean asIPv4 = (ipVersion != null && ipVersion.isIPv4());
			if(digitCount == 0) {
				//we know leadingZeroCount is > 0 since we have checked already if there were no characters at all
				leadingZeros--;
				digitCount++;
			}
			if(leadingZeros > 0) {
				if(asIPv4) {
					if(!validationOptions.getIPv4Parameters().allowPrefixLengthLeadingZeros) {
						throw new IPAddressStringException(fullAddr.toString(), "ipaddress.error.ipv4.prefix.leading.zeros");
					}
				} else {
					if(!validationOptions.getIPv6Parameters().allowPrefixLengthLeadingZeros) {
						throw new IPAddressStringException(fullAddr.toString(), "ipaddress.error.ipv6.prefix.leading.zeros");
					}
				}
			}
			boolean allowPrefixesBeyondAddressSize = (asIPv4 ? validationOptions.getIPv4Parameters() : validationOptions.getIPv6Parameters()).allowPrefixesBeyondAddressSize;
			//before we attempt to parse, ensure the string is a reasonable size
			if(!allowPrefixesBeyondAddressSize && digitCount > (asIPv4 ? 2 : 3)) {
				if(asIPv4 && validationOptions.getIPv4Parameters().inet_aton_joinedSegments && validationOptions.getIPv4Parameters().inet_aton_single_segment_mask) {
					return null; //treat it as single segment ipv4 mask (ie /xxx not a prefix of length xxx but the mask xxx
				}
				throw new IPAddressStringException(fullAddr.toString(), "ipaddress.error.prefixSize");
			}
			int result = parse10(fullAddr, index, endIndex);
			if(!allowPrefixesBeyondAddressSize && result > (asIPv4 ? IPv4Address.BIT_COUNT : IPv6Address.BIT_COUNT)) {
				if(asIPv4 && validationOptions.getIPv4Parameters().inet_aton_joinedSegments && validationOptions.getIPv4Parameters().inet_aton_single_segment_mask) {
					return null; //treat it as a single segment ipv4 mask
				}
				throw new IPAddressStringException(fullAddr.toString(), "ipaddress.error.prefixSize");
			}
			if(result < PREFIX_CACHE.length) {
				ParsedAddressQualifier qual = PREFIX_CACHE[result];
				if(qual == null) {
					qual = PREFIX_CACHE[result] = new ParsedAddressQualifier(result);
				}
				return qual;
			}
			return new ParsedAddressQualifier(result);
		}
		return null;
	}
	
	private static ParsedAddressQualifier parseQualifier(
			final String fullAddr,
			final IPAddressStringParameters validationOptions,
			final boolean isPrefixed,
			final boolean isZoned,
			final boolean addressIsEmpty,
			final int index,
			final int endIndex,
			final IPVersion ipVersion) throws IPAddressStringException {
		if(isPrefixed) {
			if(validationOptions.allowPrefix) {
				ParsedAddressQualifier qualifier = validatePrefix(fullAddr, validationOptions, index, fullAddr.length(), ipVersion);
				if(qualifier != null) {
					return qualifier;
				}
			}
			if(addressIsEmpty) {
				//PREFIX_ONLY must have a prefix and not a mask - we don't allow /255.255.0.0
				throw new IPAddressStringException(fullAddr, "ipaddress.error.invalidCIDRPrefix");
			}
			if(validationOptions.allowMask) {
				try {
					//check for a mask
					//check if we need a new validation options for the mask
					IPAddressStringParameters maskOptions = toMaskOptions(validationOptions, ipVersion);
					ParseData maskParseData = validateAddress(maskOptions, fullAddr, index, endIndex);
					
					if(maskParseData.isEmpty || maskParseData.isAll) {
						throw new IPAddressStringException(fullAddr, "ipaddress.error.invalidCIDRPrefixOrMask");
					}
					ParsedAddress maskAddress = createAddressProvider(null, null, fullAddr, maskOptions, maskParseData, NO_QUALIFIER);
					if(maskParseData.addressEndIndex != fullAddr.length()) { // 1.2.3.4/ or 1.2.3.4// or 1.2.3.4/%
						throw new IPAddressStringException(fullAddr, "ipaddress.error.invalidCIDRPrefixOrMask");
					}
					IPVersion maskVersion = maskParseData.ipVersion;
					if(maskVersion.isIPv4() && maskParseData.segmentCount == 1 && !maskParseData.anyWildcard && !validationOptions.getIPv4Parameters().inet_aton_single_segment_mask) {//1.2.3.4/33 where 33 is an aton_inet single segment address and not a prefix length
						throw new IPAddressStringException(fullAddr, "ipaddress.error.mask.single.segment");
					}
					if(ipVersion != null && (maskVersion.isIPv4() != ipVersion.isIPv4() || maskVersion.isIPv6() != ipVersion.isIPv6())) {
						//note that this also covers the cases of non-standard addresses in the mask, ie mask neither ipv4 or ipv6
						throw new IPAddressStringException(fullAddr, "ipaddress.error.ipMismatch");
					}
					return new ParsedAddressQualifier(maskAddress);
				} catch(IPAddressStringException e) {
					throw new IPAddressStringException(fullAddr, "ipaddress.error.invalidCIDRPrefixOrMask", e);
				}
			}
			throw new IPAddressStringException(fullAddr, "ipaddress.error.CIDRNotAllowed");
		} else if(isZoned) {
			if(addressIsEmpty) {
				throw new IPAddressStringException(fullAddr, "ipaddress.error.only.zone");
			}
			String zone = fullAddr.substring(index, endIndex);
			return new ParsedAddressQualifier(zone);
		} else {
			return NO_QUALIFIER;
		}
	}

	/**
	 * Some options are not supported in masks (prefix, wildcards, etc)
	 * So we eliminate those options while preserving the others from the address options.
	 * @param validationOptions
	 * @param ipVersion
	 * @return
	 */
	private static IPAddressStringParameters toMaskOptions(final IPAddressStringParameters validationOptions,
			final IPVersion ipVersion) {
		//We must provide options that do not allow a mask with wildcards or ranges
		IPAddressStringParameters.Builder builder = null;
		if(ipVersion == null || ipVersion.isIPv6()) {
			IPv6AddressStringParameters ipv6Options = validationOptions.getIPv6Parameters();
			if(!ipv6Options.rangeOptions.isNoRange()) {
				builder = validationOptions.toBuilder();
				builder.getIPv6AddressParametersBuilder().setRangeOptions(RangeParameters.NO_RANGE);
			}
			if(ipv6Options.allowMixed && !ipv6Options.getMixedParameters().getIPv4Parameters().rangeOptions.isNoRange()) {
				if(builder == null) {
					builder = validationOptions.toBuilder();
				}
				builder.getIPv6AddressParametersBuilder().setRangeOptions(RangeParameters.NO_RANGE);
			}
		}
		if(ipVersion == null || ipVersion.isIPv4()) {
			IPv4AddressStringParameters ipv4Options = validationOptions.getIPv4Parameters();
			if(!ipv4Options.rangeOptions.isNoRange()) {
				if(builder == null) {
					builder = validationOptions.toBuilder();
				}
				builder.getIPv4AddressParametersBuilder().setRangeOptions(RangeParameters.NO_RANGE);
			}
		}
		if(validationOptions.allowAll) {
			if(builder == null) {
				builder = validationOptions.toBuilder();
			}
			builder.allowAll(false);
		}
		IPAddressStringParameters maskOptions = (builder == null) ? validationOptions : builder.toParams();
		return maskOptions;
	}
	
	private static void assignAttributes(int frontStart, int frontEnd, int start, int end, int indices[], int frontLeadingZeroStartIndex, int leadingZeroStartIndex) {
		indices[ParseData.LOWER_STR_DIGITS_INDEX] = frontLeadingZeroStartIndex;
		indices[ParseData.LOWER_STR_START_INDEX] = frontStart;
		indices[ParseData.LOWER_STR_END_INDEX] = frontEnd;
		indices[ParseData.UPPER_STR_DIGITS_INDEX] = leadingZeroStartIndex;
		indices[ParseData.UPPER_STR_START_INDEX] = start;
		indices[ParseData.UPPER_STR_END_INDEX] = end;
	}
	
	private static void assignAttributes(int frontStart, int frontEnd, int start, int end, int indices[], int frontLeadingZeroStartIndex, int leadingZeroStartIndex, int frontRadix, int radix) {
		indices[ParseData.LOWER_RADIX_INDEX] = frontRadix;
		indices[ParseData.UPPER_RADIX_INDEX] = radix;
		assignAttributes(frontStart, frontEnd, start, end, indices, frontLeadingZeroStartIndex, leadingZeroStartIndex);
	}
	
	private static void assignAttributes(int start, int end, int indices[], int leadingZeroStartIndex) {
		indices[ParseData.UPPER_STR_DIGITS_INDEX] = indices[ParseData.LOWER_STR_DIGITS_INDEX] = leadingZeroStartIndex;
		indices[ParseData.UPPER_STR_START_INDEX] = indices[ParseData.LOWER_STR_START_INDEX] = start;
		indices[ParseData.UPPER_STR_END_INDEX] = indices[ParseData.LOWER_STR_END_INDEX] = end;
	}
	
	private static void assignAttributes(int start, int end, int indices[], int radix, int leadingZeroStartIndex) {
		indices[ParseData.UPPER_RADIX_INDEX] = indices[ParseData.LOWER_RADIX_INDEX] = radix;
		assignAttributes(start, end, indices, leadingZeroStartIndex);
	}
	
	private static void assignSingleWildcardAttributes(String str, int start, int end, int digitsEnd, int numSingleWildcards, int indices[],  boolean flags[], int radix, int leadingZeroStartIndex, IPVersionAddressStringParameters options) throws IPAddressStringException {
		if(!options.rangeOptions.allowsSingleWildcard()) {
			throw new IPAddressStringException(str, "ipaddress.error.no.single.wildcard");
		}
		for(int k = digitsEnd; k < end; k++) {
			if(str.charAt(k) != IPAddress.SEGMENT_SQL_SINGLE_WILDCARD) {
				throw new IPAddressStringException(str, "ipaddress.error.single.wildcard.order");
			}
		}
		flags[ParseData.SINGLE_WILDCARD_INDEX] = true;
		assignAttributes(start, end, indices, radix, leadingZeroStartIndex);
	}
	
	private static void parseSingleWildcard10(String s, int start, int end, int numSingleWildcards, int indices[], long vals[],  boolean flags[], int leadingZeroStartIndex, IPVersionAddressStringParameters options) throws IPAddressStringException {
		int digitsEnd = end - numSingleWildcards;
		assignSingleWildcardAttributes(s, start, end, digitsEnd, numSingleWildcards, indices, flags, 10, leadingZeroStartIndex, options);
		long lower;
		if(start < digitsEnd) {
			lower = parseLong10(s, start, digitsEnd);
		} else {
			lower = 0;
		}
		long upper;
		switch(numSingleWildcards) {
			case 1:
				lower *= 10;
				upper = lower + 9;
				break;
			case 2:
				lower *= 100;
				upper = lower + 99;
				break;
			case 3:
				lower *= 1000;
				upper = lower + 999;
				break;
			default:
				long power = (long) Math.pow(10, numSingleWildcards);
				lower *= power;
				upper = lower + power - 1;
		}
		vals[ParseData.LOWER_INDEX] = lower;
		vals[ParseData.UPPER_INDEX] = upper;
	}
	
	private static void parseSingleWildcard8(String s, int start, int end, int numSingleWildcards, int indices[], long vals[],  boolean flags[], int leadingZeroStartIndex, IPVersionAddressStringParameters options) throws IPAddressStringException {
		int digitsEnd = end - numSingleWildcards;
		assignSingleWildcardAttributes(s, start, end, digitsEnd, numSingleWildcards, indices, flags, 8, leadingZeroStartIndex, options);
		long lower;
		if(start < digitsEnd) {
			lower = parseLong8(s, start, digitsEnd);
		} else {
			lower = 0;
		}
		long upper;
		switch(numSingleWildcards) {
			case 1:
				lower <<= 3;
				upper = lower + 07;
				break;
			case 2:
				lower <<= 6;
				upper = lower + 077;
				break;
			case 3:
				lower <<= 9;
				upper = lower + 0777;
				break;
			default:
				long power = (long) Math.pow(8, numSingleWildcards);
				lower *= power;
				upper = lower + ((power * 8) - 1);
		}
		vals[ParseData.LOWER_INDEX] = lower;
		vals[ParseData.UPPER_INDEX] = upper;
	}
	
	private static void parseSingleWildcard16(String s, int start, int end, int numSingleWildcards, int indices[], long vals[],  boolean flags[], int leadingZeroStartIndex, IPVersionAddressStringParameters options) throws IPAddressStringException {
		int digitsEnd = end - numSingleWildcards;
		assignSingleWildcardAttributes(s, start, end, digitsEnd, numSingleWildcards, indices, flags, 16, leadingZeroStartIndex, options);
		long lower;
		if(start < digitsEnd) {
			lower = parseLong16(s, start, digitsEnd);
		} else {
			lower = 0;
		}
		long upper;
		switch(numSingleWildcards) {
			case 1:
				lower <<= 4;
				upper = lower + 0xf;
				break;
			case 2:
				lower <<= 8;
				upper = lower + 0xff;
				break;
			case 3:
				lower <<= 12;
				upper = lower + 0xfff;
				break;
			case 4:
				lower <<= 16;
				upper = lower + 0xffff;
				break;
			default:
				long power = (long) Math.pow(16, numSingleWildcards);
				lower *= power;
				upper = lower + power - 1;
		}
		vals[ParseData.LOWER_INDEX] = lower;
		vals[ParseData.UPPER_INDEX] = upper;
	}
	
	private static long getMaxIPv4Value(int additionalSegmentsCovered) {
		if(additionalSegmentsCovered == 0) {
			return IPv4Address.MAX_VALUE_PER_SEGMENT;
		} else if(additionalSegmentsCovered == 1) {
			return 0xffff;
		} else if(additionalSegmentsCovered == 2) {
			return 0xffffff;
		}
		return 0xffffffffL;
	}
	
	private static int getStringPrefixCharCount(int radix) {
		if(radix == 10) {
			return 0;
		} else if(radix == 16) {
			return 2;
		}
		return 1;
	}
	
	private static int getMaxIPv4StringLength(int additionalSegmentsCovered, int radix) {
		if(radix == 10) {
			if(additionalSegmentsCovered == 0) {
				return IPv4AddressSegment.MAX_CHARS;//255
			} else if(additionalSegmentsCovered == 1) {
				return 5;//65535
			} else if(additionalSegmentsCovered == 2) {
				return 8;//16777215
			}
			return 10;//4294967295
		} else if(radix == 16) {
			if(additionalSegmentsCovered == 0) {
				return 2;//0xff
			} else if(additionalSegmentsCovered == 1) {
				return 4;//0xffff
			} else if(additionalSegmentsCovered == 2) {
				return 6;//0xffffff
			}
			return 8;//0xffffffffL
		}
		//radix is octal
		if(additionalSegmentsCovered == 0) {
			return 3;//0377
		} else if(additionalSegmentsCovered == 1) {
			return 6;//0177777
		} else if(additionalSegmentsCovered == 2) {
			return 8;//077777777
		}
		return 11;//037777777777
	}
	
	private static int parse8(CharSequence s, int start, int end) {
		int charArray[] = chars;
		int result = charArray[s.charAt(start)];
		while (++start < end) {
			result = (result << 3) + charArray[s.charAt(start)];
       }
	   return result;
	}
	
	private static long parseLong8(CharSequence s, int start, int end) {
		if(end - start <= 10) { //10 digits in octal fit into an integer
			return parse8(s, start, end);
		}
		int charArray[] = chars;
		long result = charArray[s.charAt(start)];
		while (++start < end) {
			result = (result << 3) + charArray[s.charAt(start)];
       }
	   return result;
	}
	
	private static int parse10(CharSequence s, int start, int end) {
		int charArray[] = chars;
		int result = charArray[s.charAt(start)];
		while (++start < end) {
			result = (result * 10) + charArray[s.charAt(start)];
		}
		return result;
	}
	
	private static long parseLong10(CharSequence s, int start, int end) {
		if(end - start <= 9) { //9 digits in decimal into an integer
			return parse10(s, start, end);
		}
		int charArray[] = chars;
		long result = charArray[s.charAt(start)];
		while (++start < end) {
			result = (result * 10) + charArray[s.charAt(start)];
		}
		return result;
	}
	
	private static int parse16(CharSequence s, int start, int end) {
		int charArray[] = chars;
		int result = charArray[s.charAt(start)];
		while (++start < end) {
			result = (result << 4) + charArray[s.charAt(start)];
		}
		return result;
	}
	
	private static long parseLong16(CharSequence s, int start, int end) {
		if(end - start <= 7) { //7 hex digits fit into an integer
			return parse16(s, start, end);
		}
		int charArray[] = chars;
		long result = charArray[s.charAt(start)];
		while (++start < end) {
			result = (result << 4) + charArray[s.charAt(start)];
		}
		return result;
	}
	
	//according to rfc 1035 or 952, a label must start with a letter, must end with a letter or digit, and must have in the middle a letter or digit or -
		//rfc 1123 relaxed that to allow labels to start with a digit, section 2.1 has a discussion on this.  It states that the highest level component name must be alphabetic - referring to .com or .net or whatever.
		//furthermore, the underscore has become generally acceptable, as indicated in rfc 2181
		//there is actually a distinction between host names and domain names.  a host name is a specific type of domain name identifying hosts.
		//hosts are not supposed to have the underscore.  
	
	//en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax
	//en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
	
	//max length is 63, cannot start or end with hyphen
	//strictly speaking, the underscore is not allowed anywhere, but it seems that rule is sometimes broken
	//also, underscores seem to be a part of dns names that are not part of host names, so we allow it here to be safe
	
	//networkadminkb.com/KB/a156/windows-2003-dns-and-the-underscore.aspx
	
	//It's a little confusing.  rfc 2181 https://www.ietf.org/rfc/rfc2181.txt in section 11 on name syntax says that any chars are allowed in dns.
	//However, it also says internet host names might have restrictions of their own, and this was defined in rfc 1035.  
	//rfc 1035 defines the restrictions on internet host names, in section 2.3.1 http://www.ietf.org/rfc/rfc1035.txt
	
	//So we will follow rfc 1035 and in addition allow the underscore.
	
	
	static ParsedHost validateHostImpl(HostName fromHost) throws HostNameException {
		final String str = fromHost.toString();
		HostNameParameters validationOptions = fromHost.getValidationOptions();
		return validateHost(fromHost, str, validationOptions);
	}
	
	private static ParsedHost validateHost(final HostName fromHost, final String str, HostNameParameters validationOptions) throws HostNameException {
		int addrLen = str.length();
		if(addrLen > MAX_HOST_LENGTH) {
			throw new HostNameException(str, "ipaddress.host.error.invalid.length");
		}
		int index, lastSeparatorIndex, labelCount = 0;
		boolean segmentUppercase = false, isNotNormalized = false, squareBracketed = false,
				isAllDigits = true, isPossiblyIPv6 = true, isPossiblyIPv4 = true, tryIPv6 = false, tryIPv4 = false, isPrefixed = false, addressIsEmpty = false;
		index = lastSeparatorIndex = -1;
		int maxLocalLabels = 6;//should be at least 4 to avoid the array for ipv4 addresses
		int separatorIndices[] = null;
		boolean normalizedFlags[] = null;
		int sep0, sep1, sep2, sep3, sep4, sep5;
		sep0 = sep1 = sep2 = sep3 = sep4 = sep5 = -1;
		boolean upper0, upper1, upper2, upper3, upper4, upper5;
		upper0 = upper1 = upper2 = upper3 = upper4 = upper5 = false;
		int qualifierIndex = -1;
		while(++index <= addrLen) {
			char currentChar;
			//grab the character to evaluate
			if(index == addrLen) {
				if(index == 0) {
					addressIsEmpty = true;
					break;
				}
				boolean segmentCountMatches = (labelCount + 1 == IPv4Address.SEGMENT_COUNT) ||
						(labelCount + 1 < IPv4Address.SEGMENT_COUNT && validationOptions.addressOptions.getIPv4Parameters().inet_aton_joinedSegments);//TODO new address values
				if(isAllDigits) {
					if(segmentCountMatches) {
						tryIPv4 = true;
						break;
					}
					throw new HostNameException(str, "ipaddress.host.error.invalid");
				}
				isPossiblyIPv4 &= segmentCountMatches;
				currentChar = HostName.LABEL_SEPARATOR;
			} else {
				currentChar = str.charAt(index);
			}
			
			//check that character
			if(currentChar >= 'a' && currentChar <= 'z') {
				if(currentChar > 'f') {
					isPossiblyIPv6 = false;
					isPossiblyIPv4 &= (currentChar == 'x' && validationOptions.addressOptions.getIPv4Parameters().inet_aton_hex);
				} else {
					isPossiblyIPv4 = false;
				}
				isAllDigits = false;
			} else if(currentChar >= '0' && currentChar <= '9') {
				//nothing to do
				continue;
			} else if(currentChar >= 'A' && currentChar <= 'Z') {
				if(currentChar > 'F') {
					isPossiblyIPv6 = false;
				}
				segmentUppercase = true;
				isAllDigits = isPossiblyIPv4 = false;
			} else if(currentChar == HostName.LABEL_SEPARATOR) {
				int len = index - lastSeparatorIndex - 1;
				if(len > MAX_LABEL_LENGTH) {
					throw new HostNameException(str, "ipaddress.host.error.segment.too.long");
				}
				if(len == 0) {
					throw new HostNameException(str, "ipaddress.host.error.segment.too.short");
				}
				if(labelCount < maxLocalLabels) {
					if(labelCount < 3) {
						if(labelCount == 0) {
							sep0 = index;
							upper0 = segmentUppercase;
						} else if(labelCount == 1) {
							sep1 = index;
							upper1 = segmentUppercase;
						} else {
							sep2 = index;
							upper2 = segmentUppercase;
						}
					} else {
						if(labelCount == 3) {
							sep3 = index;
							upper3 = segmentUppercase;
						} else if(labelCount == 4) {
							sep4 = index;
							upper4 = segmentUppercase;
						} else {
							sep5 = index;
							upper5 = segmentUppercase;
						}
					}
					labelCount++;
				} else if(labelCount == maxLocalLabels) {
					separatorIndices = new int[MAX_HOST_SEGMENTS + 1];
					separatorIndices[labelCount] = index;
					if(validationOptions.normalizeToLowercase) {
						normalizedFlags = new boolean[MAX_HOST_SEGMENTS + 1];
						normalizedFlags[labelCount] = !segmentUppercase;
						isNotNormalized |= segmentUppercase;
					}
					labelCount++;	
				} else {
					separatorIndices[labelCount] = index;
					if(normalizedFlags != null) {
						normalizedFlags[labelCount] = !segmentUppercase;
						isNotNormalized |= segmentUppercase;
					}
					if(++labelCount > MAX_HOST_SEGMENTS) {
						throw new HostNameException(str, "ipaddress.host.error.too.many.segments");
					}
				}
				lastSeparatorIndex = index;
				segmentUppercase = false;
			} else if(currentChar == '_') {//this is not supported in host names but is supported in domain names, see discussion in Host class
				isAllDigits = false;
			} else if(currentChar == '-') {
				//host name segments cannot end with '-'
				if(index == lastSeparatorIndex + 1 || index == addrLen - 1 || str.charAt(index + 1) == HostName.LABEL_SEPARATOR) {
					throw new HostNameException(str, index);
				}
				isAllDigits = false;
			} else if(currentChar == HostName.IPV6_START_BRACKET) {
				if(index == 0 && labelCount == 0 && addrLen > 2) {
					squareBracketed = true;
					break;
				}
				throw new HostNameException(str, index);
			} else if(currentChar == IPAddress.PREFIX_LEN_SEPARATOR) {
				isPrefixed = true;
				qualifierIndex = index + 1;
				addrLen = index;
				isNotNormalized = true;
				index--;
			} else {
				boolean b = false;
				if(currentChar == IPAddress.SEGMENT_WILDCARD || (b = (currentChar == IPAddress.SEGMENT_SQL_WILDCARD))) {
					IPAddressStringParameters addressOptions = validationOptions.addressOptions;
					if(b && addressOptions.getIPv6Parameters().allowZone) {//if we allow zones, we treat '%' as a zone and not as a wildcard
						if(isPossiblyIPv6 && labelCount < IPv6Address.SEGMENT_COUNT) {
							tryIPv6 = true;
							break;
						}
					} else {
						if(isPossiblyIPv4 && labelCount < IPv4Address.SEGMENT_COUNT && addressOptions.getIPv4Parameters().rangeOptions.allowsWildcard()) {
							tryIPv4 = true;
							break;
						} else if(isPossiblyIPv6 && 
								labelCount < IPv6Address.SEGMENT_COUNT && 
								addressOptions.getIPv6Parameters().rangeOptions.allowsWildcard()) {
							tryIPv6 = true;
							break;
						}
					}
					throw new HostNameException(str, index);
				} else if(currentChar == IPv6Address.SEGMENT_SEPARATOR && 
						labelCount < IPv6Address.SEGMENT_COUNT) {
					if(isPossiblyIPv6) {
						tryIPv6 = true;
						break;
					}
					throw new HostNameException(str, index);
				} else {
					throw new HostNameException(str, index);
				}
			}
		}

		//1. squareBracketed: [ addr ] 
		//2. tryIPv4 || tryIPv6: this is a string with characters that invalidate it as a host but it still may in fact be an address
		//	This includes ipv6 strings (as dictated by the presence of a ':'), ipv4/ipv6 strings with '*', or all dot/digit strings like 1.2.3.4 that are 4 segments
		//3. isPossiblyIPv4: this is a string with digits, - and _ characters and the number of separators matches ipv4.  Such strings can also be valid hosts.  It also includes "" empty addresses.
		//	If it parses as an address, we do not treat as host.  The range options flag (controlling whether we allow '-' or '_' in addresses) for ipv4 can control whether it is treated as host or address.
		
		try {
			boolean isIPAddress  = squareBracketed || tryIPv4 || tryIPv6;
			if(!validationOptions.allowIPAddress) {
				if(isIPAddress) {
					throw new HostNameException(str, "ipaddress.host.error.ipaddress");
				}
			} else if(isIPAddress || isPossiblyIPv4) {
				try {
					ParseData addressData;
					ParsedAddressQualifier qualifier;
					IPAddressStringParameters addressOptions = validationOptions.addressOptions;
					if(squareBracketed) {
						//Note: 
						//Firstly, we need to find the address end which is denoted by the end bracket
						//Secondly, while zones appear inside bracket, prefix appears outside, according to rfc 4038
						//So we keep track of the boolean endsWithPrefix to differentiate.
						int endIndex = addrLen - 1;
						boolean endsWithPrefix = str.charAt(endIndex) != HostName.IPV6_END_BRACKET;
						if(endsWithPrefix) {
							while(str.charAt(--endIndex) != HostName.IPV6_END_BRACKET) {
								if(endIndex == 1) {
									throw new HostNameException(str, "ipaddress.host.error.bracketed.missing.end");
								}
							}
						}
						int startIndex = 1;
						if(str.startsWith(HostIdentifierStringValidator.SMTP_IPV6_IDENTIFIER, 1)) {
							//SMTP rfc 2821 allows [IPv6:ipv6address]
							startIndex = 6;
						}
						addressData = validateAddress(addressOptions, str, startIndex, endIndex);
						if(endsWithPrefix) {
							if(addressData.addressEndIndex != endIndex || addressData.isZoned) {
								throw new HostNameException(str, "ipaddress.error.zoneAndCIDRPrefix");
							}
							int prefixIndex = endIndex + 1;
							if(str.charAt(prefixIndex)  != IPAddress.PREFIX_LEN_SEPARATOR) {
								throw new HostNameException(str, prefixIndex);
							}
							qualifierIndex = prefixIndex + 1;//skip the ']/'
							endIndex = addrLen;
							isPrefixed = true;
						} else {
							qualifierIndex = addressData.qualifierIndex;
							isPrefixed = addressData.isPrefixed;
							if(addressData.isZoned && str.charAt(addressData.qualifierIndex) == '2' && str.charAt(addressData.qualifierIndex + 1) == '5') {
								//handle %25 from rfc 6874
								qualifierIndex += 2;
							}
						}
						//SMTP rfc 2821 allows [ipv4address]
						IPVersion version = addressData.ipVersion;
						if(version != IPVersion.IPV6 && !validationOptions.allowBracketedIPv4) {
							throw new HostNameException(str, "ipaddress.host.error.bracketed.not.ipv6");
						}
						qualifier = parseQualifier(str, validationOptions.addressOptions, isPrefixed, addressData.isZoned, addressData.isEmpty, qualifierIndex, endIndex, version);
					} else {
						int endIndex = str.length();
						addressData = validateAddress(addressOptions, str, 0, endIndex);
						qualifier = parseQualifier(str, addressOptions, addressData.isPrefixed, addressData.isZoned, addressData.isEmpty, addressData.qualifierIndex, endIndex, addressData.ipVersion);
					}
					AddressProvider provider = createProvider(fromHost, null, str, addressOptions, addressData, qualifier);
					return new ParsedHost(str, provider);
				} catch(IPAddressStringException e) {
					if(isIPAddress) {
						throw e;
					} //else fall though and evaluate as a host
				}
			}
			
			
			ParsedAddressQualifier qualifier = parseQualifier(str, validationOptions.addressOptions, isPrefixed, false, addressIsEmpty, qualifierIndex, str.length(), null);
			ParsedHost parsedHost;
			if(addressIsEmpty) {
				if(!validationOptions.allowEmpty) {
					throw new HostNameException(str, "ipaddress.host.error.empty");
				}
				if(qualifier == NO_QUALIFIER) {
					parsedHost = DEFAULT_EMPTY_HOST;
				} else {
					parsedHost = new ParsedHost(str, EMPTY_INDICES, null, qualifier);
				}
			} else {
				if(labelCount <= maxLocalLabels) {
					separatorIndices = new int[maxLocalLabels = labelCount];
					if(validationOptions.normalizeToLowercase) {
						normalizedFlags = new boolean[labelCount];
					}
				} else if(labelCount != separatorIndices.length) {
					int trimmedSeparatorIndices[] = new int[labelCount];
					System.arraycopy(separatorIndices, maxLocalLabels, trimmedSeparatorIndices, maxLocalLabels, labelCount - maxLocalLabels);
					separatorIndices = trimmedSeparatorIndices;
					if(normalizedFlags != null) {
						boolean trimmedNormalizedFlags[] = new boolean[labelCount];
						System.arraycopy(normalizedFlags, maxLocalLabels, trimmedNormalizedFlags, maxLocalLabels, labelCount - maxLocalLabels);
						normalizedFlags = trimmedNormalizedFlags;
					}
				}
				for(int i = 0; i < maxLocalLabels; i++) {
					int nextSep;
					boolean isUpper;
					if(i < 2) {
						if(i == 0) {
							nextSep = sep0;
							isUpper = upper0;
						} else {
							nextSep = sep1;
							isUpper = upper1;
						}
					} else if(i < 4) {
						if(i == 2) {
							nextSep = sep2;
							isUpper = upper2;
						} else {
							nextSep = sep3;
							isUpper = upper3;
						}
					} else if (i == 4) {
						nextSep = sep4;
						isUpper = upper4;
					} else {
						nextSep = sep5;
						isUpper = upper5;
					}
					separatorIndices[i] = nextSep;
					if(normalizedFlags != null) {
						normalizedFlags[i] = !isUpper;
						isNotNormalized |= isUpper;
					}
				}
				parsedHost = new ParsedHost(str, separatorIndices, normalizedFlags, qualifier);
				if(!isNotNormalized) {
					parsedHost.host = str;
				}
				
				//TODO for special hosts reverse dns and unc hosts, check for this, if so, then parse the address
				//call validateAddress when the string is ready
				//for UNC, replace - with :, then replace the IPv6Address.UNC_RANGE_SEPARATOR_STR with -
				//for reverse dns, count dots
				//if 3, then just reverse, parse, and ensure the result is ipv4
				//if more than 3, we need to join the segments
				//But in fact, maybe we just parse as is, after it's done we will have the ParseData structure to tell us how many 
				//there are and then we reverse them (we also change to ipv6 if more than 3)
			}
			return parsedHost;
		} catch(IPAddressStringException e) {
			throw new HostNameException(str, e, "ipaddress.host.error.invalid");
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy