Skip to main content

range_requests/headers/
range.rs

1use std::{
2    fmt::{self, Display},
3    str::FromStr,
4};
5
6use http::HeaderValue;
7
8use crate::headers::{OrderedRange, ParseHttpRangeOrContentRangeError, UNIT, u64_unprefixed_parse};
9
10/// A typed HTTP `Range` header that only supports a __single__ range.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum HttpRange {
13    StartingPoint(u64),
14    Range(OrderedRange),
15    Suffix(u64),
16}
17
18impl FromStr for HttpRange {
19    type Err = ParseHttpRangeOrContentRangeError;
20
21    fn from_str(s: &str) -> Result<Self, Self::Err> {
22        let s = s.trim();
23        if s.is_empty() {
24            return Err(ParseHttpRangeOrContentRangeError::Empty);
25        }
26
27        let (unit_str, range_str) = s
28            .split_once("=")
29            .ok_or(ParseHttpRangeOrContentRangeError::Malformed)?;
30        if unit_str != UNIT {
31            return Err(ParseHttpRangeOrContentRangeError::InvalidUnit);
32        }
33
34        let (start_str, end_str) = range_str
35            .split_once("-")
36            .ok_or(ParseHttpRangeOrContentRangeError::MalformedRange)?;
37
38        match (start_str.is_empty(), end_str.is_empty()) {
39            (false, false) => {
40                let start = u64_unprefixed_parse(start_str)
41                    .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
42                let end = u64_unprefixed_parse(end_str)
43                    .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
44
45                let range = OrderedRange::new(start..=end)?;
46                Ok(Self::Range(range))
47            }
48            (false, true) => {
49                let start = u64_unprefixed_parse(start_str)
50                    .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
51
52                Ok(Self::StartingPoint(start))
53            }
54            (true, false) => {
55                let suffix = u64_unprefixed_parse(end_str)
56                    .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
57
58                Ok(Self::Suffix(suffix))
59            }
60            (true, true) => Err(ParseHttpRangeOrContentRangeError::Malformed),
61        }
62    }
63}
64
65impl From<&HttpRange> for HeaderValue {
66    fn from(value: &HttpRange) -> Self {
67        HeaderValue::from_maybe_shared(value.to_string())
68            .expect("`HttpRange` Display produced non-visible ASCII characters")
69    }
70}
71
72impl TryFrom<&HeaderValue> for HttpRange {
73    type Error = ParseHttpRangeOrContentRangeError;
74    fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
75        value
76            .to_str()
77            .map_err(|_| ParseHttpRangeOrContentRangeError::ContainsNonVisibleASCII)?
78            .parse::<Self>()
79    }
80}
81
82#[cfg(feature = "axum")]
83impl<S> axum_core::extract::OptionalFromRequestParts<S> for HttpRange
84where
85    S: Send + Sync,
86{
87    type Rejection = ParseHttpRangeOrContentRangeError;
88
89    async fn from_request_parts(
90        parts: &mut http::request::Parts,
91        _state: &S,
92    ) -> Result<Option<Self>, Self::Rejection> {
93        match parts.headers.get(http::header::RANGE) {
94            Some(range) => {
95                let range = HttpRange::try_from(range)?;
96                Ok(Some(range))
97            }
98            None => Ok(None),
99        }
100    }
101}
102
103impl Display for HttpRange {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            HttpRange::StartingPoint(start) => write!(f, "{UNIT}={start}-"),
107            HttpRange::Range(range) => write!(f, "{UNIT}={range}"),
108            HttpRange::Suffix(suffix) => write!(f, "{UNIT}=-{suffix}"),
109        }
110    }
111}