128 lines
3.0 KiB
Rust
128 lines
3.0 KiB
Rust
use std::ops::Range;
|
|
|
|
use regex::Regex;
|
|
|
|
fn parse (range_str: &str) -> (Option <u64>, Option <u64>)
|
|
{
|
|
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 <u64>,
|
|
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);
|
|
}
|
|
}
|
|
}
|