use http::header::HeaderValue;
use std::cmp;
use std::ops::Range;
use std::str::FromStr;
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum ResolvedRanges {
None,
NotSatisfiable,
Single(Range<u64>),
Multiple(Vec<Range<u64>>),
}
enum ParsedRange {
Unparsable,
NotSatisfiable,
Parsed(Range<u64>),
}
pub(crate) fn parse(range: Option<&HeaderValue>, len: u64) -> ResolvedRanges {
let range = match range.and_then(|v| v.to_str().ok()) {
None => return ResolvedRanges::None,
Some(r) => r,
};
let Some(bytes) = range.strip_prefix("bytes=") else {
return ResolvedRanges::None;
};
let mut ranges: Vec<Range<u64>> = Vec::with_capacity(2);
for r in bytes.split(',') {
match parse_range(r, len) {
ParsedRange::Unparsable => return ResolvedRanges::None,
ParsedRange::NotSatisfiable => continue,
ParsedRange::Parsed(range) => ranges.push(range),
}
}
match ranges.len() {
0 => ResolvedRanges::NotSatisfiable,
1 => ResolvedRanges::Single(ranges.remove(0)),
_ => ResolvedRanges::Multiple(ranges),
}
}
fn parse_range(r: &str, len: u64) -> ParsedRange {
let r = r.trim_start_matches([' ', '\t']);
let hyphen = match r.find('-') {
None => return ParsedRange::Unparsable,
Some(h) => h,
};
if hyphen == 0 {
let last = match u64::from_str(&r[1..]) {
Err(_) => return ParsedRange::Unparsable,
Ok(l) => l,
};
if last >= len {
return ParsedRange::NotSatisfiable;
}
ParsedRange::Parsed((len - last)..len)
} else {
let first = match u64::from_str(&r[0..hyphen]) {
Err(_) => return ParsedRange::Unparsable,
Ok(f) => f,
};
let end = if r.len() > hyphen + 1 {
cmp::min(
match u64::from_str(&r[hyphen + 1..]) {
Err(_) => return ParsedRange::Unparsable,
Ok(l) => l,
} + 1,
len,
)
} else {
len };
if first >= end {
return ParsedRange::NotSatisfiable;
}
ParsedRange::Parsed(first..end)
}
}
#[cfg(test)]
mod tests {
use super::{parse, ResolvedRanges};
use http::header::HeaderValue;
#[test]
fn test_resolve_ranges_rfc() {
assert_eq!(
ResolvedRanges::Single(0..500),
parse(Some(&HeaderValue::from_static("bytes=0-499")), 10000)
);
assert_eq!(
ResolvedRanges::Single(500..1000),
parse(Some(&HeaderValue::from_static("bytes=500-999")), 10000)
);
assert_eq!(
ResolvedRanges::Single(9500..10000),
parse(Some(&HeaderValue::from_static("bytes=-500")), 10000)
);
assert_eq!(
ResolvedRanges::Single(9500..10000),
parse(Some(&HeaderValue::from_static("bytes=9500-")), 10000)
);
assert_eq!(
ResolvedRanges::Multiple(vec![0..1, 9999..10000]),
parse(Some(&HeaderValue::from_static("bytes=0-0,-1")), 10000)
);
assert_eq!(
ResolvedRanges::Multiple(vec![500..601, 601..1000]),
parse(
Some(&HeaderValue::from_static("bytes=500-600, 601-999")),
10000
)
);
assert_eq!(
ResolvedRanges::Multiple(vec![500..701, 601..1000]),
parse(
Some(&HeaderValue::from_static("bytes=500-700, 601-999")),
10000
)
);
}
#[test]
fn test_resolve_ranges_satisfiability() {
assert_eq!(
ResolvedRanges::NotSatisfiable,
parse(Some(&HeaderValue::from_static("bytes=10000-")), 10000)
);
assert_eq!(
ResolvedRanges::Single(0..500),
parse(Some(&HeaderValue::from_static("bytes=0-499,10000-")), 10000)
);
assert_eq!(
ResolvedRanges::NotSatisfiable,
parse(Some(&HeaderValue::from_static("bytes=-1")), 0)
);
assert_eq!(
ResolvedRanges::NotSatisfiable,
parse(Some(&HeaderValue::from_static("bytes=0-0")), 0)
);
assert_eq!(
ResolvedRanges::NotSatisfiable,
parse(Some(&HeaderValue::from_static("bytes=0-")), 0)
);
assert_eq!(
ResolvedRanges::Single(0..1),
parse(Some(&HeaderValue::from_static("bytes=0-0")), 1)
);
assert_eq!(
ResolvedRanges::Single(0..500),
parse(Some(&HeaderValue::from_static("bytes=0-10000")), 500)
);
}
#[test]
fn test_resolve_ranges_absent_or_invalid() {
assert_eq!(ResolvedRanges::None, parse(None, 10000));
}
#[test]
fn test_nonascii() {
assert_eq!(
ResolvedRanges::None,
parse(Some(&HeaderValue::from_bytes(b"\xff").unwrap()), 10000)
);
}
}