parse static method
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);
}