Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/common/content_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,29 @@ impl ContentRange {
) -> Result<ContentRange, InvalidContentRange> {
let complete_length = complete_length.into();

// Use checked arithmetic so degenerate/empty bounds return an error
// instead of overflowing (e.g. an exclusive end of 0). (#231)
let err = || InvalidContentRange { _inner: () };

let start = match range.start_bound() {
Bound::Included(&s) => s,
Bound::Excluded(&s) => s + 1,
Bound::Excluded(&s) => s.checked_add(1).ok_or_else(err)?,
Bound::Unbounded => 0,
};

let end = match range.end_bound() {
Bound::Included(&e) => e,
Bound::Excluded(&e) => e - 1,
Bound::Excluded(&e) => e.checked_sub(1).ok_or_else(err)?,
Bound::Unbounded => match complete_length {
Some(max) => max - 1,
None => return Err(InvalidContentRange { _inner: () }),
Some(max) => max.checked_sub(1).ok_or_else(err)?,
None => return Err(err()),
},
};

if start > end {
return Err(err());
}

Ok(ContentRange {
range: Some((start, end)),
complete_length,
Expand Down Expand Up @@ -236,3 +244,20 @@ test_header!(test_bytes_unknown_range,
vec![b"bytes 1-2-3/500"],
None::<ContentRange>);
*/

#[cfg(test)]
mod tests {
use super::ContentRange;

#[test]
fn bytes_rejects_degenerate_bounds() {
// #231: bounds that would underflow or produce start > end must return
// an error instead of overflowing the u64 arithmetic.
assert!(ContentRange::bytes(0u64..0u64, 500u64).is_err());
assert!(ContentRange::bytes(3u64..3u64, 500u64).is_err());
assert!(ContentRange::bytes(.., 0u64).is_err());
// Valid ranges still work.
assert!(ContentRange::bytes(0u64..500u64, 500u64).is_ok());
assert!(ContentRange::bytes(0u64..=499u64, 500u64).is_ok());
}
}
17 changes: 17 additions & 0 deletions src/common/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ impl Range {
let v = match (bounds.start_bound(), bounds.end_bound()) {
(Bound::Included(start), Bound::Included(end)) => format!("bytes={}-{}", start, end),
(Bound::Included(start), Bound::Excluded(&end)) => {
// An exclusive end of 0 (or <= start) has no last byte, so
// `end - 1` would underflow / produce an invalid range. (#231)
if end <= *start {
return Err(InvalidRange { _inner: () });
}
format!("bytes={}-{}", start, end - 1)
}
(Bound::Included(start), Bound::Unbounded) => format!("bytes={}-", start),
Expand Down Expand Up @@ -456,3 +461,15 @@ fn test_to_unsatisfiable_range_suffix() {
let bounds = range.satisfiable_ranges(100).next();
assert_eq!(bounds, None);
}

#[test]
fn test_bytes_rejects_degenerate_bounds() {
// #231: an exclusive end that would underflow (`0`) or produce an invalid
// start > last range must error rather than panic or emit a bogus range.
assert!(Range::bytes(0u64..0u64).is_err());
assert!(Range::bytes(3u64..3u64).is_err());
assert!(Range::bytes(5u64..3u64).is_err());
// Valid ranges still work.
assert!(Range::bytes(0u64..500u64).is_ok());
assert!(Range::bytes(0u64..=500u64).is_ok());
}