#![warn(clippy::pedantic)]
use core::fmt::{Debug, Display, Formatter};
use core::ops::RangeInclusive;
const UNIT_SEP: &str = "bytes=";
pub fn parse_range_header(
range_header_value: &str,
) -> Result<ParsedRanges, RangeUnsatisfiableError> {
const COMMA: char = ',';
if let Some((prefix, indicated_range)) = range_header_value.split_once(UNIT_SEP) {
if indicated_range.starts_with(char::is_whitespace) {
return Err(RangeUnsatisfiableError::StartsWithWhitespace);
}
if !prefix.is_empty() {
return Err(RangeUnsatisfiableError::DoesNotStartWithToken);
}
let mut last_err = None;
let ranges = indicated_range
.split(COMMA)
.filter_map(|range| {
if let Some(trimmed) = trim(range) {
match parse_inner(trimmed) {
Ok(parsed) => Some(parsed),
Err(e) => {
last_err = Some(e);
None
}
}
} else {
last_err = Some(RangeUnsatisfiableError::IllegalWhitespace);
None
}
})
.collect::<Vec<SyntacticallyCorrectRange>>();
if let Some(last_err) = last_err {
return Err(last_err);
}
if ranges.is_empty() {
Err(RangeUnsatisfiableError::Empty)
} else {
Ok(ParsedRanges::new(ranges))
}
} else {
Err(RangeUnsatisfiableError::DoesNotStartWithToken)
}
}
fn trim(s: &str) -> Option<&str> {
if s.ends_with(char::is_whitespace) || s.match_indices(char::is_whitespace).count() > 1 {
None
} else {
Some(s.trim())
}
}
#[inline]
fn parse_inner(range: &str) -> Result<SyntacticallyCorrectRange, RangeUnsatisfiableError> {
if let Some((start, end)) = range.split_once('-') {
if start.is_empty() {
if let Some(end) = strict_parse_u64(end) {
if end == 0 {
return Err(RangeUnsatisfiableError::ZeroSuffix);
}
return Ok(SyntacticallyCorrectRange::new(
StartPosition::FromLast(end),
EndPosition::LastByte,
));
}
return Err(RangeUnsatisfiableError::BadEndOfRange);
}
if let Some(start) = strict_parse_u64(start) {
if end.is_empty() {
return Ok(SyntacticallyCorrectRange::new(
StartPosition::Index(start),
EndPosition::LastByte,
));
}
if let Some(end) = strict_parse_u64(end) {
return Ok(SyntacticallyCorrectRange::new(
StartPosition::Index(start),
EndPosition::Index(end),
));
}
return Err(RangeUnsatisfiableError::BadEndOfRange);
}
return Err(RangeUnsatisfiableError::BadStartOfRange);
}
Err(RangeUnsatisfiableError::UnexpectedNumberOfDashes)
}
fn strict_parse_u64(s: &str) -> Option<u64> {
if !s.starts_with('+') && (s.len() == 1 || !s.starts_with('0')) {
return s.parse::<u64>().ok();
}
None
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParsedRanges {
ranges: Vec<SyntacticallyCorrectRange>,
}
impl ParsedRanges {
fn new(ranges: Vec<SyntacticallyCorrectRange>) -> Self {
ParsedRanges { ranges }
}
pub fn validate(
&self,
file_size_bytes: u64,
) -> Result<Vec<RangeInclusive<u64>>, RangeUnsatisfiableError> {
let len = self.ranges.len();
let mut validated = Vec::with_capacity(len);
for parsed in &self.ranges {
let start = match parsed.start {
StartPosition::Index(i) => i,
StartPosition::FromLast(i) => {
if i > file_size_bytes {
return Err(RangeUnsatisfiableError::FileSuffixOutOfBounds);
}
file_size_bytes - i
}
};
let end = match parsed.end {
EndPosition::Index(i) => core::cmp::min(i, file_size_bytes - 1),
EndPosition::LastByte => file_size_bytes - 1,
};
let valid = RangeInclusive::new(start, end);
validated.push(valid);
}
#[allow(clippy::match_same_arms)]
match validate_ranges(validated.as_slice()) {
RangeValidationResult::Valid => Ok(validated),
RangeValidationResult::Overlapping => Err(RangeUnsatisfiableError::OverlappingRanges),
RangeValidationResult::Reversed => Err(RangeUnsatisfiableError::RangeReversed),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum RangeUnsatisfiableError {
OverlappingRanges,
RangeReversed,
FileSuffixOutOfBounds,
IllegalWhitespace,
StartsWithWhitespace,
DoesNotStartWithToken,
ZeroSuffix,
BadStartOfRange,
BadEndOfRange,
UnexpectedNumberOfDashes,
Empty,
}
impl Display for RangeUnsatisfiableError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
RangeUnsatisfiableError::OverlappingRanges => {
f.write_str("RangeUnsatisfiable: Ranges overlap")
}
RangeUnsatisfiableError::RangeReversed => {
f.write_str("RangeUnsatisfiable: Reversed range")
}
RangeUnsatisfiableError::FileSuffixOutOfBounds => f.write_str(
"RangeUnsatisfiable: File suffix out of bounds (larger than file bytes)",
),
RangeUnsatisfiableError::IllegalWhitespace => {
f.write_str("RangeUnsatisfiable: Illegal whitespaces in range")
}
RangeUnsatisfiableError::StartsWithWhitespace => {
f.write_str("RangeUnsatisfiable: Range starts with whitespace")
}
RangeUnsatisfiableError::DoesNotStartWithToken => f.write_fmt(format_args!(
"RangeUnsatisfiable: Range does not start with token '{UNIT_SEP}'"
)),
RangeUnsatisfiableError::ZeroSuffix => {
f.write_str("RangeUnsatisfiable: Range ends at 0")
}
RangeUnsatisfiableError::BadStartOfRange => {
f.write_str("RangeUnsatisfiable: Unparseable start of range")
}
RangeUnsatisfiableError::BadEndOfRange => {
f.write_str("RangeUnsatisfiable: Unparseable end of range")
}
RangeUnsatisfiableError::UnexpectedNumberOfDashes => {
f.write_str("RangeUnsatisfiable: Unexpected number of dashes")
}
RangeUnsatisfiableError::Empty => f.write_str(
"RangeUnsatisfiable: Failed to parse range fallback error, please file an issue",
),
}
}
}
impl std::error::Error for RangeUnsatisfiableError {}
enum RangeValidationResult {
Valid,
Overlapping,
Reversed,
}
fn validate_ranges(ranges: &[RangeInclusive<u64>]) -> RangeValidationResult {
let mut bounds = Vec::new();
for range in ranges {
let start = range.start();
let end = range.end();
if start > end {
return RangeValidationResult::Reversed;
} else if ranges.len() == 1 {
return RangeValidationResult::Valid;
}
bounds.push((range.start(), range.end()));
}
for i in 0..bounds.len() {
for j in i + 1..bounds.len() {
if bounds[i].0 <= bounds[j].1 && bounds[j].0 <= bounds[i].1 {
return RangeValidationResult::Overlapping;
}
}
}
RangeValidationResult::Valid
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
struct SyntacticallyCorrectRange {
start: StartPosition,
end: EndPosition,
}
impl SyntacticallyCorrectRange {
fn new(start: StartPosition, end: EndPosition) -> Self {
SyntacticallyCorrectRange { start, end }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum StartPosition {
Index(u64),
FromLast(u64),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum EndPosition {
Index(u64),
LastByte,
}
#[cfg(test)]
mod tests {
use crate::{
parse_range_header, EndPosition, ParsedRanges, RangeUnsatisfiableError, StartPosition,
SyntacticallyCorrectRange,
};
use core::ops::RangeInclusive;
const TEST_FILE_LENGTH: u64 = 10_000;
#[test]
fn rfc_7233_standard_test1() {
let input = "bytes=0-499";
let expect =
SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(499));
let actual = parse_range_header(input).unwrap();
assert_eq!(single_range(expect), actual);
let expect = RangeInclusive::new(0, 499);
let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
assert_eq!(expect, actual)
}
#[test]
fn rfc_7233_standard_test2() {
let input = "bytes=500-999";
let expect =
SyntacticallyCorrectRange::new(StartPosition::Index(500), EndPosition::Index(999));
let actual = parse_range_header(input).unwrap();
assert_eq!(single_range(expect), actual);
let expect = RangeInclusive::new(500, 999);
let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
assert_eq!(expect, actual)
}
#[test]
fn rfc_7233_suffixed_test() {
let input = "bytes=-500";
let expect =
SyntacticallyCorrectRange::new(StartPosition::FromLast(500), EndPosition::LastByte);
let actual = parse_range_header(input).unwrap();
assert_eq!(single_range(expect), actual);
let expect = RangeInclusive::new(9500, 9999);
let actual = actual.validate(10_000).unwrap()[0].clone();
assert_eq!(expect, actual)
}
#[test]
fn rfc_7233_open_range_test() {
let input = "bytes=9500-";
let expect =
SyntacticallyCorrectRange::new(StartPosition::Index(9500), EndPosition::LastByte);
let actual = parse_range_header(input).unwrap();
assert_eq!(single_range(expect), actual);
let expect = RangeInclusive::new(9500, 9999);
let actual = actual.validate(10_000).unwrap()[0].clone();
assert_eq!(expect, actual)
}
#[test]
fn rfc_7233_first_and_last() {
let input = "bytes=0-0, -1";
let expect = vec![
SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(0)),
SyntacticallyCorrectRange::new(StartPosition::FromLast(1), EndPosition::LastByte),
];
let actual = parse_range_header(input).unwrap();
assert_eq!(expect, actual.ranges);
let expect = vec![0..=0, 9999..=9999];
let actual = actual.validate(10_000).unwrap();
assert_eq!(expect, actual)
}
#[test]
fn parse_standard_range() {
let input = "bytes=0-1023";
let expect =
SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023));
let actual = parse_range_header(input).unwrap();
assert_eq!(single_range(expect), actual);
let expect = RangeInclusive::new(0, 1023);
let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
assert_eq!(expect, actual)
}
#[test]
fn parse_open_ended_range() {
let input = "bytes=0-";
let expect = SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::LastByte);
let actual = parse_range_header(input).unwrap();
assert_eq!(single_range(expect), actual);
let expect = RangeInclusive::new(0, TEST_FILE_LENGTH - 1);
let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
assert_eq!(expect, actual);
}
#[test]
fn parse_suffix_range_edge() {
let input = &format!("bytes=-{}", TEST_FILE_LENGTH);
let expect = SyntacticallyCorrectRange::new(
StartPosition::FromLast(TEST_FILE_LENGTH),
EndPosition::LastByte,
);
let actual = parse_range_header(input).unwrap();
assert_eq!(single_range(expect), actual);
let expect = RangeInclusive::new(0, TEST_FILE_LENGTH - 1);
let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
assert_eq!(expect, actual);
}
#[test]
fn parse_empty_as_invalid() {
let input = "";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
}
#[test]
fn parse_empty_range_as_invalid() {
let input = "bytes=";
let parsed = parse_range_header(input);
assert_eq!(
parsed,
Err(RangeUnsatisfiableError::UnexpectedNumberOfDashes)
);
}
#[test]
fn parse_range_starting_with_whitespace_as_invalid() {
let input = "bytes= 0-15";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::StartsWithWhitespace));
}
#[test]
fn parse_range_token_starting_with_whitespace_as_invalid() {
let input = " bytes=0-15";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
}
#[test]
fn parse_range_strict_parse_numerical() {
let input = "bytes=+0-15";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::BadStartOfRange));
}
#[test]
fn parse_bad_unit_as_invalid() {
let input = "abcde=0-10";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
}
#[test]
fn parse_missing_equals_as_malformed() {
let input = "bytes0-10";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
}
#[test]
fn parse_negative_bad_characters_in_range_as_malformed() {
let input = "bytes=1-10a";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::BadEndOfRange));
}
#[test]
fn parse_negative_numbers_as_malformed() {
let input = "bytes=-1-10";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::BadEndOfRange));
}
#[test]
fn parse_bad_characters_in_start_of_range() {
let input = "bytes=a1-10";
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::BadStartOfRange));
}
#[test]
fn parse_out_of_bounds_overrun_as_content_length() {
let input = &format!("bytes=0-{}", TEST_FILE_LENGTH);
let expect = vec![RangeInclusive::new(0, TEST_FILE_LENGTH - 1)];
let actual = parse_range_header(input)
.unwrap()
.validate(TEST_FILE_LENGTH)
.unwrap();
assert_eq!(expect, actual);
}
#[test]
fn parse_out_of_bounds_suffix_overrun_as_unsatisfiable() {
let input = &format!("bytes=-{}", TEST_FILE_LENGTH + 1);
let parsed = parse_range_header(input)
.unwrap()
.validate(TEST_FILE_LENGTH);
assert_eq!(parsed, Err(RangeUnsatisfiableError::FileSuffixOutOfBounds));
}
#[test]
fn parse_zero_length_suffix_as_unsatisfiable() {
let input = &format!("bytes=-0");
let parsed = parse_range_header(input);
assert_eq!(parsed, Err(RangeUnsatisfiableError::ZeroSuffix));
}
#[test]
fn parse_single_reversed_as_invalid() {
let input = &format!("bytes=15-0");
let parsed = parse_range_header(input).unwrap();
assert_eq!(
parsed.validate(TEST_FILE_LENGTH),
Err(RangeUnsatisfiableError::RangeReversed)
);
}
#[test]
fn parse_multi_range() {
let input = "bytes=0-1023, 2015-3000, 4000-4500, 8000-9999";
let expected_ranges = vec![
SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023)),
SyntacticallyCorrectRange::new(StartPosition::Index(2015), EndPosition::Index(3000)),
SyntacticallyCorrectRange::new(StartPosition::Index(4000), EndPosition::Index(4500)),
SyntacticallyCorrectRange::new(StartPosition::Index(8000), EndPosition::Index(9999)),
];
let parsed = parse_range_header(input).unwrap();
assert_eq!(expected_ranges, parsed.ranges);
let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
assert_eq!(
vec![0..=1023, 2015..=3000, 4000..=4500, 8000..=9999],
validated
)
}
#[test]
fn parse_multi_range_with_open() {
let input = "bytes=0-1023, 1024-";
let expected_ranges = vec![
SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023)),
SyntacticallyCorrectRange::new(StartPosition::Index(1024), EndPosition::LastByte),
];
let parsed = parse_range_header(input).unwrap();
assert_eq!(expected_ranges, parsed.ranges);
let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
assert_eq!(vec![0..=1023, 1024..=9999], validated);
}
#[test]
fn parse_multi_range_with_suffix() {
let input = "bytes=0-1023, -1000";
let expected_ranges = vec![
SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023)),
SyntacticallyCorrectRange::new(StartPosition::FromLast(1000), EndPosition::LastByte),
];
let parsed = parse_range_header(input).unwrap();
assert_eq!(expected_ranges, parsed.ranges);
assert_eq!(expected_ranges, parsed.ranges);
let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
assert_eq!(vec![0..=1023, 9000..=9999], validated);
}
#[test]
fn parse_overlapping_multi_range_as_unsatisfiable_standard() {
let input = "bytes=0-1023, 500-800";
assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
let input = "bytes=0-0, 0-15";
assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
let input = "bytes=0-20, 20-35";
assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
}
#[test]
fn parse_overlapping_multi_range_as_unsatisfiable_open() {
let input = "bytes=0-, 5000-6000";
assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
}
#[test]
fn parse_overlapping_multi_range_as_unsatisfiable_suffixed() {
let input = "bytes=8000-9000, -1001";
assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
let input = "bytes=8000-9000, -1000";
assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
let input = "bytes=8000-9000, -999";
parse_range_header(input)
.unwrap()
.validate(TEST_FILE_LENGTH)
.unwrap();
}
#[test]
fn parse_overlapping_multi_range_as_unsatisfiable_suffixed_open() {
let input = "bytes=0-, -1";
assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
}
#[test]
fn parse_multi_range_with_a_reversed_as_invalid() {
let input = "bytes=0-15, 30-20";
assert_validation_err(input, RangeUnsatisfiableError::RangeReversed);
}
fn assert_validation_err(input: &str, err: RangeUnsatisfiableError) {
let parsed = parse_range_header(input)
.unwrap()
.validate(TEST_FILE_LENGTH);
assert_eq!(Err(err), parsed);
}
#[test]
fn parse_multi_range_rejects_invalid() {
let input = "bytes=0-15, 25, 9, ";
let parsed = parse_range_header(input);
assert!(parsed.is_err())
}
#[quickcheck_macros::quickcheck]
fn always_errs_on_random_input(input: String) -> quickcheck::TestResult {
let acceptable = regex::Regex::new(
"^bytes=((\\d+-\\d+,\\s?)|(\\d+-,\\s?)|(-\\d+,\\s?))*((\\d+-\\d+)|(\\d+-)|(-\\d+))+$",
)
.unwrap();
if acceptable.is_match(&input) {
quickcheck::TestResult::discard()
} else {
if let Ok(passed_first_pass) = parse_range_header(&input) {
quickcheck::TestResult::from_bool(passed_first_pass.validate(u64::MAX).is_err())
} else {
quickcheck::TestResult::passed()
}
}
}
fn single_range(syntactically_correct: SyntacticallyCorrectRange) -> ParsedRanges {
ParsedRanges::new(vec![syntactically_correct])
}
}