// DNS 抽象与离线 zone。
//
// 核心不做任何网络 I/O:调用方实现 `Resolver` 注入数据,
// 因此同一封邮件在任何机器、任何时间都会得到同一结论。
///|
/// 只读 DNS 解析器。实现者可以是离线 zone,也可以是真实的 DNS 客户端。
pub(open) trait Resolver {
fn txt(Self, String) -> Array[String]
fn a(Self, String) -> Array[String]
fn aaaa(Self, String) -> Array[String]
fn mx(Self, String) -> Array[String]
fn ptr(Self, String) -> Array[String]
}
///|
/// 把域名转成小写并去掉末尾的点。
pub fn normalize_name(name : String) -> String {
let out = StringBuilder()
let trimmed = if name.has_suffix(".") {
name[:name.length() - 1].to_owned()
} else {
name
}
for c in trimmed {
if c >= 'A' && c <= 'Z' {
out.write_char(Int::unsafe_to_char(c.to_int() + 32))
} else {
out.write_char(c)
}
}
out.to_string()
}
///|
/// 离线 DNS zone。按「名称 + 记录类型」保存记录,供测试与 CI 使用。
pub struct Zone {
records : Array[(String, String, Array[String])]
}
///|
pub fn Zone::new() -> Zone {
{ records: [], }
}
///|
/// 添加一条记录。名称大小写与末尾点会被归一化。
pub fn Zone::add(
self : Zone,
name : String,
rtype : String,
values : Array[String],
) -> Unit {
self.records.push((normalize_name(name), rtype, values))
}
///|
/// 从文本加载 zone。每行格式:`name TYPE value`,value 可以包含空格。
pub fn Zone::from_text(text : StringView) -> Zone {
let zone = Zone::new()
for raw_line in text.split("\n") {
let line = trim_wsp(raw_line)
if line.length() == 0 {
continue
}
let first = line.find(" ")
match first {
None => ()
Some(i) => {
let name = line[:i].to_owned()
let rest = trim_wsp(line[i + 1:])
let second = rest.find(" ")
match second {
None => zone.add(name, rest, [""])
Some(j) => {
let rtype = rest[:j].to_owned()
let value = trim_wsp(rest[j + 1:])
zone.add(name, rtype, [value])
}
}
}
}
}
zone
}
///|
fn Zone::lookup(self : Zone, name : String, rtype : String) -> Array[String] {
let key = normalize_name(name)
let out : Array[String] = []
for record in self.records {
if record.0 == key && record.1 == rtype {
for value in record.2 {
out.push(value)
}
}
}
out
}
///|
pub impl Resolver for Zone with fn txt(self, name) {
self.lookup(name, "TXT")
}
///|
pub impl Resolver for Zone with fn a(self, name) {
self.lookup(name, "A")
}
///|
pub impl Resolver for Zone with fn aaaa(self, name) {
self.lookup(name, "AAAA")
}
///|
pub impl Resolver for Zone with fn mx(self, name) {
self.lookup(name, "MX")
}
///|
pub impl Resolver for Zone with fn ptr(self, name) {
self.lookup(name, "PTR")
}