use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use crate::validate::{is_valid_token, trim_ows};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
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"),
}
}
}
impl core::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 = trim_ows(input);
if input.is_empty() {
return Err(RangeError::Empty);
}
let eq_pos = input.find('=').ok_or(RangeError::InvalidFormat)?;
let unit = trim_ows(&input[..eq_pos]);
let ranges_str = trim_ows(&input[eq_pos + 1..]);
if !is_valid_token(unit) {
return Err(RangeError::InvalidUnit);
}
let mut ranges = Vec::new();
for part in ranges_str.split(',') {
let part = trim_ows(part);
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 = trim_ows(&s[..dash_pos]);
let end_str = trim_ows(&s[dash_pos + 1..]);
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 })
}
fn validate_content_range_parts(
start: u64,
end: u64,
complete_length: Option<u64>,
) -> Result<(), RangeError> {
if start > end {
return Err(RangeError::InvalidBounds);
}
if let Some(len) = complete_length
&& len <= end
{
return Err(RangeError::InvalidBounds);
}
Ok(())
}
#[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 = trim_ows(input);
if input.is_empty() {
return Err(RangeError::Empty);
}
let space_pos = input.find(' ').ok_or(RangeError::InvalidFormat)?;
let unit = trim_ows(&input[..space_pos]);
let rest = trim_ows(&input[space_pos + 1..]);
if !is_valid_token(unit) {
return Err(RangeError::InvalidUnit);
}
let slash_pos = rest.find('/').ok_or(RangeError::InvalidFormat)?;
let range_str = trim_ows(&rest[..slash_pos]);
let length_str = trim_ows(&rest[slash_pos + 1..]);
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)?;
validate_content_range_parts(start, end, complete_length)?;
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 {
assert!(start <= end, "ContentRange: start must be <= end");
if let Some(len) = complete_length {
assert!(
len > end,
"ContentRange: complete_length must be > last-pos"
);
}
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)) => e.checked_sub(s).and_then(|d| d.checked_add(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 = trim_ows(input);
if input.is_empty() {
return Err(RangeError::Empty);
}
let units: Vec<String> = input
.split(',')
.map(|s| trim_ows(s).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: alloc::vec!["bytes".to_string()],
}
}
pub fn none() -> Self {
AcceptRanges {
units: alloc::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(", "))
}
}