///|
pub struct DomainName {
label_values : Array[String]
absolute_value : Bool
} derive(Eq, Debug)
///|
fn name_error(
code : ZoneErrorCode,
message : String,
column? : Int = 0,
) -> ZoneError {
ZoneError::new(code, message, SourceSpan::point(0, column))
}
///|
fn ascii_lower(text : String) -> String {
let mut result = ""
for index = 0; index < text.length(); index = index + 1 {
let code = text[index].to_int()
let lowered = if code >= 65 && code <= 90 { code + 32 } else { code }
let character = match lowered.to_char() {
Some(value) => value.to_string()
None => ""
}
result = result + character
}
result
}
///|
fn valid_label_character(code : Int) -> Bool {
(code >= 97 && code <= 122) ||
(code >= 65 && code <= 90) ||
(code >= 48 && code <= 57) ||
code == 45 ||
code == 95
}
///|
fn validate_domain_label(
label : String,
index : Int,
) -> Result[Unit, ZoneError] {
if label.length() == 0 {
return Err(
name_error(
InvalidDomainLabel,
"domain name contains an empty interior label",
column=index,
),
)
}
if label.length() > 63 {
return Err(
name_error(
InvalidDomainLabel,
"domain label exceeds 63 ASCII octets",
column=index,
),
)
}
if label == "*" {
return Ok(())
}
for offset = 0; offset < label.length(); offset = offset + 1 {
if !valid_label_character(label[offset].to_int()) {
return Err(
name_error(
InvalidDomainLabel,
"domain label contains an unsupported character",
column=index + offset,
),
)
}
}
Ok(())
}
///|
fn domain_wire_length(labels : Array[String], absolute : Bool) -> Int {
let mut length = if absolute { 1 } else { 0 }
for label in labels {
length = length + 1 + label.length()
}
length
}
///|
/// Parse an ASCII DNS presentation name without resolving a relative origin.
pub fn parse_domain_name(text : String) -> Result[DomainName, ZoneError] {
if text.length() == 0 {
return Err(name_error(InvalidDomainName, "domain name is empty"))
}
if text == "." {
return Ok({ label_values: [], absolute_value: true })
}
let absolute = text[text.length() - 1].to_int() == 46
let limit = if absolute { text.length() - 1 } else { text.length() }
let labels : Array[String] = []
let mut current = ""
let mut label_start = 0
for index = 0; index < limit; index = index + 1 {
let code = text[index].to_int()
if code > 127 {
return Err(
name_error(
InvalidDomainLabel,
"domain name must use ASCII presentation characters",
column=index,
),
)
}
if code == 46 {
match validate_domain_label(current, label_start) {
Ok(_) => labels.push(ascii_lower(current))
Err(error) => return Err(error)
}
current = ""
label_start = index + 1
} else {
let character = match code.to_char() {
Some(value) => value.to_string()
None => ""
}
current = current + character
}
}
match validate_domain_label(current, label_start) {
Ok(_) => labels.push(ascii_lower(current))
Err(error) => return Err(error)
}
if domain_wire_length(labels, absolute) > 255 {
return Err(
name_error(DomainNameTooLong, "domain name exceeds 255 wire octets"),
)
}
Ok({ label_values: labels, absolute_value: absolute })
}
///|
pub fn DomainName::labels(self : DomainName) -> Array[String] {
self.label_values.copy()
}
///|
pub fn DomainName::is_absolute(self : DomainName) -> Bool {
self.absolute_value
}
///|
pub fn DomainName::wire_length(self : DomainName) -> Int {
domain_wire_length(self.label_values, self.absolute_value)
}
///|
pub fn DomainName::to_text(self : DomainName) -> String {
if self.label_values.length() == 0 {
if self.absolute_value {
"."
} else {
""
}
} else {
let mut result = ""
for index, label in self.label_values {
if index > 0 {
result = result + "."
}
result = result + label
}
if self.absolute_value {
result + "."
} else {
result
}
}
}
///|
pub fn DomainName::resolve(
self : DomainName,
origin : DomainName,
) -> Result[DomainName, ZoneError] {
if self.absolute_value {
return Ok(self)
}
if !origin.absolute_value {
return Err(
name_error(
RelativeNameWithoutOrigin,
"relative domain name requires an absolute origin",
),
)
}
let labels = self.label_values.copy()
for label in origin.label_values {
labels.push(label)
}
if domain_wire_length(labels, true) > 255 {
return Err(
name_error(
DomainNameTooLong,
"resolved domain name exceeds 255 wire octets",
),
)
}
Ok({ label_values: labels, absolute_value: true })
}
///|
pub fn resolve_zone_name(
text : String,
origin : DomainName,
) -> Result[DomainName, ZoneError] {
if text == "@" {
if origin.is_absolute() {
Ok(origin)
} else {
Err(
name_error(
RelativeNameWithoutOrigin,
"at-sign owner requires an absolute origin",
),
)
}
} else {
match parse_domain_name(text) {
Ok(value) => value.resolve(origin)
Err(error) => Err(error)
}
}
}