///|
/// Advisory finding; audit never changes parser validity.
pub(all) enum AuditFindingKind {
TooManyRanges
OverlappingRanges
ManySmallRanges
DescendingOrder
DuplicateRanges
ZeroLengthSuffix
LargeNumericValue
LargeHeader
UnknownRangeUnit
AdjacentRanges
InefficientRangeSet
} derive(Eq, Debug)
///|
pub struct AuditFinding {
kind : AuditFindingKind
context : String
} derive(Eq, Debug)
///|
pub struct AuditReport {
findings : Array[AuditFinding]
} derive(Eq, Debug)
///|
pub fn AuditFinding::kind(self : AuditFinding) -> AuditFindingKind {
self.kind
}
///|
pub fn AuditFinding::context(self : AuditFinding) -> String {
self.context
}
///|
pub fn AuditReport::findings(self : AuditReport) -> Array[AuditFinding] {
self.findings.copy()
}
///|
pub fn AuditReport::is_clean(self : AuditReport) -> Bool {
self.findings.is_empty()
}
///|
pub fn AuditFindingKind::name(self : AuditFindingKind) -> String {
match self {
TooManyRanges => "TooManyRanges"
OverlappingRanges => "OverlappingRanges"
ManySmallRanges => "ManySmallRanges"
DescendingOrder => "DescendingOrder"
DuplicateRanges => "DuplicateRanges"
ZeroLengthSuffix => "ZeroLengthSuffix"
LargeNumericValue => "LargeNumericValue"
LargeHeader => "LargeHeader"
UnknownRangeUnit => "UnknownRangeUnit"
AdjacentRanges => "AdjacentRanges"
InefficientRangeSet => "InefficientRangeSet"
}
}
///|
/// Audit raw request shape and, when available, resolved byte ranges.
pub fn audit_range_request(
request : RangeRequest,
raw_input? : String,
representation_length? : Int64,
) -> AuditReport {
let findings : Array[AuditFinding] = []
match raw_input {
Some(raw) if utf8_length(raw) > 1_024 =>
add_finding(findings, LargeHeader, "Range field exceeds 1024 UTF-8 bytes")
_ => ()
}
if !request.unit().is_bytes() {
add_finding(findings, UnknownRangeUnit, "range-unit semantics are unknown")
return { findings, }
}
let specs = request.specs()
if specs.length() > 16 {
add_finding(findings, TooManyRanges, "request contains more than 16 ranges")
}
let mut small = 0
for spec in specs {
match spec {
Suffix(0L) =>
add_finding(findings, ZeroLengthSuffix, "suffix range has zero length")
Closed(first, last) => {
if first > 1_000_000_000_000L || last > 1_000_000_000_000L {
add_finding(
findings,
LargeNumericValue,
"range uses a very large byte position",
)
}
if last - first + 1L <= 16L {
small = small + 1
}
}
OpenEnded(first) if first > 1_000_000_000_000L =>
add_finding(
findings,
LargeNumericValue,
"open range uses a very large byte position",
)
Suffix(length) if length > 1_000_000_000_000L =>
add_finding(
findings,
LargeNumericValue,
"suffix uses a very large length",
)
_ => ()
}
}
if small >= 4 {
add_finding(
findings,
ManySmallRanges,
"request contains at least four ranges of 16 bytes or fewer",
)
}
match representation_length {
Some(length) =>
match resolve_byte_ranges(request, length) {
Ok(resolved) => audit_concrete_ranges(resolved.ranges(), findings)
Err(_) => ()
}
None => ()
}
if findings.length() >= 3 {
add_finding(
findings,
InefficientRangeSet,
"multiple audit signals indicate an inefficient range set",
)
}
{ findings, }
}
///|
fn audit_concrete_ranges(
ranges : Array[ConcreteRange],
findings : Array[AuditFinding],
) -> Unit {
for i = 1; i < ranges.length(); i = i + 1 {
let previous = ranges[i - 1]
let current = ranges[i]
if current.first() < previous.first() {
add_finding_once(
findings,
DescendingOrder,
"resolved ranges are not ascending",
)
}
if current == previous {
add_finding_once(
findings,
DuplicateRanges,
"duplicate resolved ranges are present",
)
}
if current.first() <= previous.last() {
add_finding_once(findings, OverlappingRanges, "resolved ranges overlap")
}
if previous.last() != @int64.MAX_VALUE &&
current.first() == previous.last() + 1L {
add_finding_once(
findings,
AdjacentRanges,
"resolved ranges are adjacent and may be coalesced",
)
}
}
}
///|
fn add_finding(
findings : Array[AuditFinding],
kind : AuditFindingKind,
context : String,
) -> Unit {
findings.push({ kind, context })
}
///|
fn add_finding_once(
findings : Array[AuditFinding],
kind : AuditFindingKind,
context : String,
) -> Unit {
for finding in findings {
if finding.kind() == kind {
return
}
}
add_finding(findings, kind, context)
}