// URI-reference values (ISO 28500:2017 clause 5.2).
//
// WARC fields such as WARC-Record-ID and WARC-Target-URI carry a URI
// reference written as ``. This module validates that envelope:
// the surrounding angle brackets, a non-empty interior, and the
// absence of whitespace and control characters. Deeper URI grammar
// checks (scheme rules, percent-encoding) belong to the validator.
///|
/// A structured invalid-uri error at a byte offset.
fn bad_uri(record_index : Int64, offset : Int, context : String) -> WarcError {
WarcError::new(
WarcErrorStage::Uri,
WarcErrorKind::InvalidUri,
offset.to_int64(),
record_index,
context,
)
}
///|
/// Parse a `` field value and return the URI inside the angle
/// brackets.
///
/// Fails with `InvalidUri` when the value is not exactly `<...>` with
/// a non-empty interior, or when the interior contains whitespace or
/// control characters.
pub fn parse_uri_ref(
s : String,
record_index : Int64,
) -> Result[String, WarcError] {
let data = @utf8.encode(s)
let len = data.length()
if len < 3 || data[0] != b'<' || data[len - 1] != b'>' {
return Err(bad_uri(record_index, 0, "URI value must be written as "))
}
let mut i = 1
while i < len - 1 {
let b = data[i]
if is_sp(b) || is_ht(b) || is_cr(b) || is_lf(b) || is_ctl(b) {
return Err(
bad_uri(
record_index, i, "URI must not contain whitespace or control characters",
),
)
}
i = i + 1
}
// Interior bytes came from a String, so decoding cannot fail.
let interior = @utf8.decode(data.view(start=1, end=len - 1)) catch {
_ => return Err(bad_uri(record_index, 0, "URI is not valid UTF-8"))
}
Ok(interior)
}