use std::ops::Range; use regex::Regex; fn parse (range_str: &str) -> (Option , Option ) { use lazy_static::lazy_static; lazy_static! { static ref RE: Regex = Regex::new (r"^bytes=(\d*)-(\d*)$").expect ("Couldn't compile regex for Range header"); } let caps = match RE.captures (range_str) { Some (x) => x, None => return (None, None), }; let start = caps.get (1).map (|x| x.as_str ()); let end = caps.get (2).map (|x| x.as_str ()); let start = start.and_then (|x| u64::from_str_radix (x, 10).ok ()); // HTTP specifies ranges as [start inclusive, end inclusive] // But that's dumb and [start inclusive, end exclusive) is better let end = end.and_then (|x| u64::from_str_radix (x, 10).ok ().map (|x| x + 1)); (start, end) } #[derive (Debug, PartialEq)] pub struct ValidParsed { pub range: Range , pub range_requested: bool, } #[derive (Debug, PartialEq)] pub enum Parsed { Valid (ValidParsed), NotSatisfiable (u64), } pub fn check (range_str: Option <&str>, file_len: u64) -> Parsed { use Parsed::*; let not_satisfiable = NotSatisfiable (file_len); let range_str = match range_str { None => return Valid (ValidParsed { range: 0..file_len, range_requested: false, }), Some (x) => x, }; let (start, end) = parse (range_str); let start = start.unwrap_or (0); if start >= file_len { return not_satisfiable; } let end = end.unwrap_or (file_len); if end > file_len { return not_satisfiable; } if end < start { return not_satisfiable; } Valid (ValidParsed { range: start..end, range_requested: true, }) } #[cfg (test)] mod tests { #[test] fn test_byte_ranges () { use super::*; for (input, expected) in vec! [ ("", (None, None)), ("bytes=0-", (Some (0), None)), ("bytes=0-999", (Some (0), Some (1000))), ("bytes=111-999", (Some (111), Some (1000))), ].into_iter () { let actual = parse (input); assert_eq! (actual, expected); } let ok_range = |range| Parsed::Valid (ValidParsed { range, range_requested: false, }); let partial_content = |range| Parsed::Valid (ValidParsed { range, range_requested: true, }); let not_satisfiable = |file_len| Parsed::NotSatisfiable (file_len); for (header, file_len, expected) in vec! [ (None, 0, ok_range (0..0)), (None, 1024, ok_range (0..1024)), (Some (""), 0, not_satisfiable (0)), (Some (""), 1024, partial_content (0..1024)), (Some ("bytes=0-"), 1024, partial_content (0..1024)), (Some ("bytes=0-999"), 1024, partial_content (0..1000)), (Some ("bytes=0-1023"), 1024, partial_content (0..1024)), (Some ("bytes=111-999"), 1024, partial_content (111..1000)), (Some ("bytes=111-1023"), 1024, partial_content (111..1024)), (Some ("bytes=200-100"), 1024, not_satisfiable (1024)), (Some ("bytes=0-"), 512, partial_content (0..512)), (Some ("bytes=0-1023"), 512, not_satisfiable (512)), (Some ("bytes=1000-1023"), 512, not_satisfiable (512)), ].into_iter () { let actual = check (header, file_len); assert_eq! (actual, expected); } } }