use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RangeError {
Empty,
InvalidFormat,
InvalidUnit,
InvalidRange,
InvalidBounds,
}
impl fmt::Display for RangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RangeError::Empty => write!(f, "empty range header"),
RangeError::InvalidFormat => write!(f, "invalid range header format"),
RangeError::InvalidUnit => write!(f, "invalid range unit"),
RangeError::InvalidRange => write!(f, "invalid range specification"),
RangeError::InvalidBounds => write!(f, "invalid range bounds (start > end)"),
}
}
}
impl std::error::Error for RangeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RangeSpec {
Range { start: u64, end: u64 },
FromStart { start: u64 },
Suffix { length: u64 },
}
impl RangeSpec {
pub fn to_bounds(&self, total_length: u64) -> Option<(u64, u64)> {
if total_length == 0 {
return None;
}
match *self {
RangeSpec::Range { start, end } => {
if start > end || start >= total_length {
return None;
}
let end = end.min(total_length - 1);
Some((start, end))
}
RangeSpec::FromStart { start } => {
if start >= total_length {
return None;
}
Some((start, total_length - 1))
}
RangeSpec::Suffix { length } => {
if length == 0 {
return None;
}
let start = total_length.saturating_sub(length);
Some((start, total_length - 1))
}
}
}
}
impl fmt::Display for RangeSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RangeSpec::Range { start, end } => write!(f, "{}-{}", start, end),
RangeSpec::FromStart { start } => write!(f, "{}-", start),
RangeSpec::Suffix { length } => write!(f, "-{}", length),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Range {
unit: String,
ranges: Vec<RangeSpec>,
}
impl Range {
pub fn parse(input: &str) -> Result<Self, RangeError> {
let input = input.trim();
if input.is_empty() {
return Err(RangeError::Empty);
}
let eq_pos = input.find('=').ok_or(RangeError::InvalidFormat)?;
let unit = input[..eq_pos].trim();
let ranges_str = input[eq_pos + 1..].trim();
if !is_valid_token(unit) {
return Err(RangeError::InvalidUnit);
}
let mut ranges = Vec::new();
for part in ranges_str.split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
ranges.push(parse_range_spec(part)?);
}
if ranges.is_empty() {
return Err(RangeError::Empty);
}
Ok(Range {
unit: unit.to_string(),
ranges,
})
}
pub fn unit(&self) -> &str {
&self.unit
}
pub fn is_bytes(&self) -> bool {
self.unit.eq_ignore_ascii_case("bytes")
}
pub fn ranges(&self) -> &[RangeSpec] {
&self.ranges
}
pub fn first(&self) -> Option<&RangeSpec> {
self.ranges.first()
}
}
impl fmt::Display for Range {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}=", self.unit)?;
let specs: Vec<String> = self.ranges.iter().map(|r| r.to_string()).collect();
write!(f, "{}", specs.join(", "))
}
}
fn parse_range_spec(s: &str) -> Result<RangeSpec, RangeError> {
let dash_pos = s.find('-').ok_or(RangeError::InvalidRange)?;
let start_str = s[..dash_pos].trim();
let end_str = s[dash_pos + 1..].trim();
if start_str.is_empty() && end_str.is_empty() {
return Err(RangeError::InvalidRange);
}
if start_str.is_empty() {
let length = end_str
.parse::<u64>()
.map_err(|_| RangeError::InvalidRange)?;
return Ok(RangeSpec::Suffix { length });
}
let start = start_str
.parse::<u64>()
.map_err(|_| RangeError::InvalidRange)?;
if end_str.is_empty() {
return Ok(RangeSpec::FromStart { start });
}
let end = end_str
.parse::<u64>()
.map_err(|_| RangeError::InvalidRange)?;
if start > end {
return Err(RangeError::InvalidBounds);
}
Ok(RangeSpec::Range { start, end })
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentRange {
unit: String,
start: Option<u64>,
end: Option<u64>,
complete_length: Option<u64>,
}
impl ContentRange {
pub fn parse(input: &str) -> Result<Self, RangeError> {
let input = input.trim();
if input.is_empty() {
return Err(RangeError::Empty);
}
let space_pos = input.find(' ').ok_or(RangeError::InvalidFormat)?;
let unit = input[..space_pos].trim();
let rest = input[space_pos + 1..].trim();
if !is_valid_token(unit) {
return Err(RangeError::InvalidUnit);
}
let slash_pos = rest.find('/').ok_or(RangeError::InvalidFormat)?;
let range_str = rest[..slash_pos].trim();
let length_str = rest[slash_pos + 1..].trim();
let complete_length = if length_str == "*" {
None
} else {
Some(
length_str
.parse::<u64>()
.map_err(|_| RangeError::InvalidFormat)?,
)
};
if range_str == "*" {
if complete_length.is_none() {
return Err(RangeError::InvalidFormat);
}
return Ok(ContentRange {
unit: unit.to_string(),
start: None,
end: None,
complete_length,
});
}
let dash_pos = range_str.find('-').ok_or(RangeError::InvalidFormat)?;
let start = range_str[..dash_pos]
.parse::<u64>()
.map_err(|_| RangeError::InvalidFormat)?;
let end = range_str[dash_pos + 1..]
.parse::<u64>()
.map_err(|_| RangeError::InvalidFormat)?;
if start > end {
return Err(RangeError::InvalidBounds);
}
if let Some(len) = complete_length
&& len <= end
{
return Err(RangeError::InvalidBounds);
}
Ok(ContentRange {
unit: unit.to_string(),
start: Some(start),
end: Some(end),
complete_length,
})
}
pub fn new_bytes(start: u64, end: u64, complete_length: Option<u64>) -> Self {
ContentRange {
unit: "bytes".to_string(),
start: Some(start),
end: Some(end),
complete_length,
}
}
pub fn unsatisfied(unit: &str, complete_length: u64) -> Self {
ContentRange {
unit: unit.to_string(),
start: None,
end: None,
complete_length: Some(complete_length),
}
}
pub fn unit(&self) -> &str {
&self.unit
}
pub fn start(&self) -> Option<u64> {
self.start
}
pub fn end(&self) -> Option<u64> {
self.end
}
pub fn complete_length(&self) -> Option<u64> {
self.complete_length
}
pub fn length(&self) -> Option<u64> {
match (self.start, self.end) {
(Some(s), Some(e)) => Some(e - s + 1),
_ => None,
}
}
pub fn is_unsatisfied(&self) -> bool {
self.start.is_none() && self.end.is_none()
}
}
impl fmt::Display for ContentRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (self.start, self.end) {
(Some(s), Some(e)) => {
write!(f, "{} {}-{}/", self.unit, s, e)?;
}
_ => {
write!(f, "{} */", self.unit)?;
}
}
match self.complete_length {
Some(len) => write!(f, "{}", len),
None => write!(f, "*"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AcceptRanges {
units: Vec<String>,
}
impl AcceptRanges {
pub fn parse(input: &str) -> Result<Self, RangeError> {
let input = input.trim();
if input.is_empty() {
return Err(RangeError::Empty);
}
let units: Vec<String> = input
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if units.is_empty() {
return Err(RangeError::Empty);
}
for unit in &units {
if !is_valid_token(unit) {
return Err(RangeError::InvalidUnit);
}
}
if units.len() > 1 && units.iter().any(|u| u.eq_ignore_ascii_case("none")) {
return Err(RangeError::InvalidUnit);
}
Ok(AcceptRanges { units })
}
pub fn bytes() -> Self {
AcceptRanges {
units: vec!["bytes".to_string()],
}
}
pub fn none() -> Self {
AcceptRanges {
units: vec!["none".to_string()],
}
}
pub fn units(&self) -> &[String] {
&self.units
}
pub fn accepts_bytes(&self) -> bool {
self.units.iter().any(|u| u.eq_ignore_ascii_case("bytes"))
}
pub fn is_none(&self) -> bool {
self.units.len() == 1 && self.units[0].eq_ignore_ascii_case("none")
}
}
impl fmt::Display for AcceptRanges {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.units.join(", "))
}
}
fn is_valid_token(s: &str) -> bool {
!s.is_empty() && s.bytes().all(is_token_char)
}
fn is_token_char(b: u8) -> bool {
matches!(
b,
b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+' | b'-' | b'.' |
b'0'..=b'9' | b'A'..=b'Z' | b'^' | b'_' | b'`' | b'a'..=b'z' | b'|' | b'~'
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_range_single() {
let range = Range::parse("bytes=0-499").unwrap();
assert_eq!(range.unit(), "bytes");
assert!(range.is_bytes());
let specs = range.ranges();
assert_eq!(specs.len(), 1);
match specs[0] {
RangeSpec::Range { start, end } => {
assert_eq!(start, 0);
assert_eq!(end, 499);
}
_ => panic!("expected Range"),
}
}
#[test]
fn test_parse_range_multiple() {
let range = Range::parse("bytes=0-499, 1000-1499").unwrap();
assert_eq!(range.ranges().len(), 2);
}
#[test]
fn test_parse_range_suffix() {
let range = Range::parse("bytes=-500").unwrap();
match range.first().unwrap() {
RangeSpec::Suffix { length } => assert_eq!(*length, 500),
_ => panic!("expected Suffix"),
}
}
#[test]
fn test_parse_range_from_start() {
let range = Range::parse("bytes=500-").unwrap();
match range.first().unwrap() {
RangeSpec::FromStart { start } => assert_eq!(*start, 500),
_ => panic!("expected FromStart"),
}
}
#[test]
fn test_parse_range_invalid_bounds() {
assert!(Range::parse("bytes=500-100").is_err());
}
#[test]
fn test_parse_range_invalid_unit() {
assert!(Range::parse("byt es=0-499").is_err()); assert!(Range::parse("bytes/foo=0-499").is_err()); assert!(Range::parse("=0-499").is_err()); assert!(Range::parse("by\tes=0-499").is_err()); assert!(Range::parse("byt(es=0-499").is_err()); }
#[test]
fn test_range_spec_to_bounds() {
let total = 1000;
let spec = RangeSpec::Range { start: 0, end: 499 };
assert_eq!(spec.to_bounds(total), Some((0, 499)));
let spec = RangeSpec::FromStart { start: 500 };
assert_eq!(spec.to_bounds(total), Some((500, 999)));
let spec = RangeSpec::Suffix { length: 200 };
assert_eq!(spec.to_bounds(total), Some((800, 999)));
let spec = RangeSpec::Range {
start: 1000,
end: 1500,
};
assert_eq!(spec.to_bounds(total), None);
}
#[test]
fn test_range_display() {
let range = Range::parse("bytes=0-499, 1000-1499").unwrap();
assert_eq!(range.to_string(), "bytes=0-499, 1000-1499");
}
#[test]
fn test_content_range_parse() {
let cr = ContentRange::parse("bytes 0-499/1000").unwrap();
assert_eq!(cr.unit(), "bytes");
assert_eq!(cr.start(), Some(0));
assert_eq!(cr.end(), Some(499));
assert_eq!(cr.complete_length(), Some(1000));
assert_eq!(cr.length(), Some(500));
}
#[test]
fn test_content_range_unknown_length() {
let cr = ContentRange::parse("bytes 0-499/*").unwrap();
assert_eq!(cr.complete_length(), None);
}
#[test]
fn test_content_range_unsatisfied() {
let cr = ContentRange::parse("bytes */1000").unwrap();
assert!(cr.is_unsatisfied());
assert_eq!(cr.complete_length(), Some(1000));
}
#[test]
fn test_content_range_unsatisfied_requires_length() {
assert!(ContentRange::parse("bytes */*").is_err());
}
#[test]
fn test_content_range_complete_length_must_exceed_last_pos() {
assert!(ContentRange::parse("bytes 0-999/500").is_err());
assert!(ContentRange::parse("bytes 0-999/999").is_err());
assert!(ContentRange::parse("bytes 0-999/1000").is_ok());
}
#[test]
fn test_content_range_display() {
let cr = ContentRange::new_bytes(0, 499, Some(1000));
assert_eq!(cr.to_string(), "bytes 0-499/1000");
let cr = ContentRange::unsatisfied("bytes", 1000);
assert_eq!(cr.to_string(), "bytes */1000");
}
#[test]
fn test_accept_ranges_bytes() {
let ar = AcceptRanges::parse("bytes").unwrap();
assert!(ar.accepts_bytes());
assert!(!ar.is_none());
}
#[test]
fn test_accept_ranges_none() {
let ar = AcceptRanges::parse("none").unwrap();
assert!(ar.is_none());
assert!(!ar.accepts_bytes());
}
#[test]
fn test_accept_ranges_display() {
let ar = AcceptRanges::bytes();
assert_eq!(ar.to_string(), "bytes");
}
#[test]
fn test_accept_ranges_invalid_token() {
assert!(AcceptRanges::parse("inv@lid").is_err());
assert!(AcceptRanges::parse("bytes, inv@lid").is_err());
}
}