parseIPv6Address method

List<int> parseIPv6Address (String host, [ int start = 0, int end ])

Parse the host as an IP version 6 (IPv6) address, returning the address as a list of 16 bytes in network byte order (big endian).

Throws a FormatException if host is not a valid IPv6 address representation.

Acts on the substring from start to end. If end is omitted, it defaults ot the end of the string.

Some examples of IPv6 addresses:

  • ::1
  • FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
  • 3ffe:2a00💯7031::1
  • ::FFFF:129.144.52.38
  • 2010:836B:4179::836B:4179

Implementation

static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
  end ??= host.length;
  // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, separated
  // by `:`'s, with the following exceptions:
  //
  //  - One (and only one) wildcard (`::`) may be present, representing a fill
  //    of 0's. The IPv6 `::` is thus 16 bytes of `0`.
  //  - The last two parts may be replaced by an IPv4 "dotted-quad" address.

  // Helper function for reporting a badly formatted IPv6 address.
  void error(String msg, [position]) {
    throw new FormatException('Illegal IPv6 address, $msg', host, position);
  }

  // Parse a hex block.
  int parseHex(int start, int end) {
    if (end - start > 4) {
      error('an IPv6 part can only contain a maximum of 4 hex digits', start);
    }
    int value = int.parse(host.substring(start, end), radix: 16);
    if (value < 0 || value > 0xFFFF) {
      error('each part must be in the range of `0x0..0xFFFF`', start);
    }
    return value;
  }

  if (host.length < 2) error('address is too short');
  List<int> parts = [];
  bool wildcardSeen = false;
  // Set if seeing a ".", suggesting that there is an IPv4 address.
  bool seenDot = false;
  int partStart = start;
  // Parse all parts, except a potential last one.
  for (int i = start; i < end; i++) {
    int char = host.codeUnitAt(i);
    if (char == _COLON) {
      if (i == start) {
        // If we see a `:` in the beginning, expect wildcard.
        i++;
        if (host.codeUnitAt(i) != _COLON) {
          error('invalid start colon.', i);
        }
        partStart = i;
      }
      if (i == partStart) {
        // Wildcard. We only allow one.
        if (wildcardSeen) {
          error('only one wildcard `::` is allowed', i);
        }
        wildcardSeen = true;
        parts.add(-1);
      } else {
        // Found a single colon. Parse [partStart..i] as a hex entry.
        parts.add(parseHex(partStart, i));
      }
      partStart = i + 1;
    } else if (char == _DOT) {
      seenDot = true;
    }
  }
  if (parts.length == 0) error('too few parts');
  bool atEnd = (partStart == end);
  bool isLastWildcard = (parts.last == -1);
  if (atEnd && !isLastWildcard) {
    error('expected a part after last `:`', end);
  }
  if (!atEnd) {
    if (!seenDot) {
      parts.add(parseHex(partStart, end));
    } else {
      List<int> last = _parseIPv4Address(host, partStart, end);
      parts.add(last[0] << 8 | last[1]);
      parts.add(last[2] << 8 | last[3]);
    }
  }
  if (wildcardSeen) {
    if (parts.length > 7) {
      error('an address with a wildcard must have less than 7 parts');
    }
  } else if (parts.length != 8) {
    error('an address without a wildcard must contain exactly 8 parts');
  }
  List<int> bytes = new Uint8List(16);
  for (int i = 0, index = 0; i < parts.length; i++) {
    int value = parts[i];
    if (value == -1) {
      int wildCardLength = 9 - parts.length;
      for (int j = 0; j < wildCardLength; j++) {
        bytes[index] = 0;
        bytes[index + 1] = 0;
        index += 2;
      }
    } else {
      bytes[index] = value >> 8;
      bytes[index + 1] = value & 0xff;
      index += 2;
    }
  }
  return bytes;
}