///|
/// Parse a Range field value using default resource limits.
pub fn parse_range(input : String) -> Result[RangeRequest, RangeError] {
parse_range_with_limits(input, Limits::default())
}
///|
/// Strict RFC 9110-oriented Range parser. It preserves byte-spec order.
pub fn parse_range_with_limits(
input : String,
limits : Limits,
) -> Result[RangeRequest, RangeError] {
if utf8_length(input) > limits.max_input_bytes() {
return Err(
range_error(
Limit,
LimitExceeded,
0,
"Range field exceeds max_input_bytes",
),
)
}
if input.length() == 0 {
return Err(range_error(Input, EmptyInput, 0, "Range field is empty"))
}
for i = 0; i < input.length(); i = i + 1 {
let code = input[i].to_int()
if (code < 0x20 && !is_ows(code.unsafe_to_char())) || code == 0x7F {
return Err(
range_error(
Input,
UnexpectedCharacter,
i,
"control character in Range field",
),
)
}
}
let equals = find_char(input, '=')
if equals < 0 {
return Err(
range_error(
Syntax,
MissingEquals,
input.length(),
"Range field has no '='",
),
)
}
let unit_text = trim_ows(input[:equals].to_owned())
if unit_text.length() == 0 {
return Err(range_error(Unit, InvalidUnit, 0, "range-unit is empty"))
}
if utf8_length(unit_text) > limits.max_unit_bytes() {
return Err(
range_error(Limit, LimitExceeded, 0, "range-unit exceeds max_unit_bytes"),
)
}
for i = 0; i < unit_text.length(); i = i + 1 {
if !is_tchar(unit_text[i].to_int().unsafe_to_char()) {
return Err(
range_error(Unit, InvalidUnit, i, "invalid range-unit character"),
)
}
}
let raw_set = trim_ows(input[equals + 1:].to_owned())
if raw_set.length() == 0 {
return Err(
range_error(RangeSpec, EmptyRangeSet, equals + 1, "range-set is empty"),
)
}
if !ascii_equal(unit_text, "bytes") {
if utf8_length(raw_set) > limits.max_raw_other_range_bytes() {
return Err(
range_error(
Limit,
LimitExceeded,
equals + 1,
"unknown range-set exceeds limit",
),
)
}
return Ok(range_request(Other(unit_text), [], raw_other_range_set=raw_set))
}
let specs = parse_byte_range_set(raw_set, equals + 1, limits) catch {
error => return Err(unwrap_range_error(error))
}
Ok(range_request(Bytes, specs))
}
///|
fn parse_byte_range_set(
input : String,
base_offset : Int,
limits : Limits,
) -> Array[ByteRangeSpec] raise RangeError {
let specs : Array[ByteRangeSpec] = []
let mut start = 0
for i = 0; i <= input.length(); i = i + 1 {
if i == input.length() || input[i] == ','.to_int().to_uint16() {
let raw = input[start:i].to_owned()
let part = trim_ows(raw)
if part.length() == 0 {
raise range_error(
RangeSpec,
InvalidRange,
base_offset + start,
"empty byte-range member",
)
}
if specs.length() >= limits.max_ranges() {
raise range_error(
Limit,
LimitExceeded,
base_offset + start,
"byte range count exceeds limit",
)
}
specs.push(parse_byte_range_spec(part, base_offset + start, limits))
start = i + 1
}
}
specs
}
///|
fn parse_byte_range_spec(
input : String,
base_offset : Int,
limits : Limits,
) -> ByteRangeSpec raise RangeError {
let dash = find_char(input, '-')
if dash < 0 {
raise range_error(
RangeSpec,
InvalidRange,
base_offset,
"byte-range member has no '-'",
)
}
if find_char_from(input, '-', dash + 1) >= 0 {
raise range_error(
RangeSpec,
InvalidRange,
base_offset + dash + 1,
"byte-range member has multiple '-' characters",
)
}
let left = trim_ows(input[:dash].to_owned())
let right = trim_ows(input[dash + 1:].to_owned())
if left.length() == 0 {
let length = unwrap_decimal(
parse_decimal_int64_at(right, base_offset + dash + 1, limits),
)
return Suffix(length)
}
let first = unwrap_decimal(parse_decimal_int64_at(left, base_offset, limits))
if right.length() == 0 {
return OpenEnded(first)
}
let last = unwrap_decimal(
parse_decimal_int64_at(right, base_offset + dash + 1, limits),
)
if last < first {
raise range_error(
RangeSpec,
LastBeforeFirst,
base_offset + dash + 1,
"last-byte-pos is before first-byte-pos",
)
}
Closed(first, last)
}
///|
fn find_char(input : String, target : Char) -> Int {
find_char_from(input, target, 0)
}
///|
fn find_char_from(input : String, target : Char, start : Int) -> Int {
let target_code = target.to_int().to_uint16()
for i = start; i < input.length(); i = i + 1 {
if input[i] == target_code {
return i
}
}
-1
}
///|
fn unwrap_decimal(result : Result[Int64, RangeError]) -> Int64 raise RangeError {
match result {
Ok(value) => value
Err(error) => raise error
}
}