parse static method

DVCidr parse(
  1. String text
)

Parses text, refusing anything that is not exactly a range.

A range with bits set past its prefix (10.0.0.1/8) is refused rather than masked: it is almost always a single host somebody meant to write as /32, and trusting the whole /8 instead is the mistake that makes a client a proxy.

Implementation

static DVCidr parse(String text) {
  FormatException refuse(String why) => FormatException(
        '"$text" is not an address range: $why. Write one as 10.0.0.0/8, '
        '2001:db8::/32, or a single address.',
      );
  final List<String> parts = text.split('/');
  if (parts.length > 2) throw refuse('it has more than one /');
  final String host = parts.first;
  // An IPv4-mapped IPv6 range is written against 128 bits; the address
  // parses to its four, so the prefix moves with it.
  final bool writtenAsV6 = host.contains(':');
  final DVIpAddress? address = DVIpAddress.tryParse(host);
  if (address == null) throw refuse('"$host" is not an IPv4 or IPv6 address');
  final int writtenBits = writtenAsV6 ? 128 : 32;
  int prefix = writtenBits;
  if (parts.length == 2) {
    final String bits = parts[1];
    if (bits.isEmpty ||
        bits.length > 3 ||
        (bits.length > 1 && bits.startsWith('0')) ||
        bits.codeUnits.any((int u) => u < 0x30 || u > 0x39)) {
      throw refuse('"$bits" is not a prefix length');
    }
    prefix = int.parse(bits);
    if (prefix > writtenBits) {
      throw refuse('the prefix is longer than $writtenBits bits');
    }
  }
  if (writtenAsV6 && address.isIPv4) {
    if (prefix < 96) {
      throw refuse('an IPv4-mapped range needs a prefix of at least 96');
    }
    prefix -= 96;
  }
  final List<int> bytes = address.bytes;
  for (int bit = prefix; bit < bytes.length * 8; bit++) {
    if ((bytes[bit >> 3] >> (7 - (bit & 7))) & 1 == 1) {
      throw refuse('it has address bits set past /$prefix');
    }
  }
  return DVCidr._(address, prefix);
}