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 = start_str
50                    .parse()
51                    .map_err(|_| ParseHttpRangeOrContentRangeError::MalformedRange)?;
52
53                Ok(Self::StartingPoint(start))
54            }
55            (true, false) => {
56                let suffix = end_str
57                    .parse()
58                    .map_err(|_| ParseHttpRangeOrContentRangeError::MalformedRange)?;
59
60                Ok(Self::Suffix(suffix))
61            }
62            (true, true) => Err(ParseHttpRangeOrContentRangeError::Malformed),
63        }
64    }
65}
66
67impl From<&HttpRange> for HeaderValue {
68    fn from(value: &HttpRange) -> Self {
69        HeaderValue::from_maybe_shared(value.to_string())
70            .expect("The `HttpRange` Display implementation produces nonvisible ASCII characters")
71    }
72}
73
74impl TryFrom<&HeaderValue> for HttpRange {
75    type Error = ParseHttpRangeOrContentRangeError;
76    fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
77        value
78            .to_str()
79            .map_err(|_| ParseHttpRangeOrContentRangeError::ContainsNonVisibleASCII)?
80            .parse::<Self>()
81    }
82}
83
84#[cfg(feature = "axum")]
85impl<S> axum_core::extract::OptionalFromRequestParts<S> for HttpRange
86where
87    S: Send + Sync,
88{
89    type Rejection = ParseHttpRangeOrContentRangeError;
90
91    async fn from_request_parts(
92        parts: &mut http::request::Parts,
93        _state: &S,
94    ) -> Result<Option<Self>, Self::Rejection> {
95        match parts.headers.get(http::header::RANGE) {
96            Some(range) => {
97                let range = HttpRange::try_from(range)?;
98                Ok(Some(range))
99            }
100            None => Ok(None),
101        }
102    }
103}
104
105impl Display for HttpRange {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self {
108            HttpRange::StartingPoint(start) => write!(f, "{UNIT}={start}-"),
109            HttpRange::Range(range) => write!(f, "{UNIT}={range}"),
110            HttpRange::Suffix(suffix) => write!(f, "{UNIT}=-{suffix}"),
111        }
112    }
113}