1use std::{
2 fmt::{self, Display},
3 ops::RangeInclusive,
4 str::FromStr,
5};
6
7use http::HeaderValue;
8
9use crate::headers::{
10 InvalidHttpU64, InvalidOrderedRange, OrderedRange, ParseHttpRangeOrContentRangeError, UNIT,
11 range::HttpRange, u64_unprefixed_parse,
12};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum HttpContentRange {
17 Bound(Bound),
18 Unsatisfiable(Unsatisfiable),
19}
20
21impl HttpContentRange {
22 pub fn matches_requested_range(&self, expected_range: HttpRange) -> bool {
26 match (expected_range, self) {
27 (HttpRange::StartingPoint(start), HttpContentRange::Bound(Bound { range, .. })) => {
28 start == range.start()
29 }
30 (
31 HttpRange::Range(OrderedRange { start, end }),
32 HttpContentRange::Bound(Bound { range, .. }),
33 ) => start == range.start() && end >= range.end(),
34 (HttpRange::Suffix(suffix), HttpContentRange::Bound(Bound { range, size })) => {
35 let length = (range.end() - range.start()).checked_add(1);
36 let length_matches = length.is_some_and(|len| len <= suffix);
37 let ends_at_boundary = size.is_none_or(|size| range.end() + 1 == size);
38 length_matches && ends_at_boundary
39 }
40 (
45 HttpRange::StartingPoint(n),
46 HttpContentRange::Unsatisfiable(Unsatisfiable { size }),
47 )
48 | (
49 HttpRange::Range(OrderedRange { start: n, .. }),
50 HttpContentRange::Unsatisfiable(Unsatisfiable { size }),
51 ) => n >= *size,
52 (HttpRange::Suffix(suffix), HttpContentRange::Unsatisfiable(Unsatisfiable { .. })) => {
53 suffix == 0
54 }
55 }
56 }
57}
58
59impl FromStr for HttpContentRange {
60 type Err = ParseHttpRangeOrContentRangeError;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 let s = s.trim();
64 if s.is_empty() {
65 return Err(ParseHttpRangeOrContentRangeError::Empty);
66 }
67
68 let (unit_str, range_and_size_str) = s
69 .split_once(" ")
70 .ok_or(ParseHttpRangeOrContentRangeError::Malformed)?;
71
72 if !unit_str.eq_ignore_ascii_case(UNIT) {
74 return Err(ParseHttpRangeOrContentRangeError::InvalidUnit);
75 }
76
77 let (range_str, size_str) = range_and_size_str
78 .split_once('/')
79 .ok_or(ParseHttpRangeOrContentRangeError::Malformed)?;
80
81 let range = range_str.parse::<ParsedRange>()?;
82 let size = size_str
83 .parse::<ParsedSize>()
84 .map_err(ParseHttpRangeOrContentRangeError::InvalidSize)?;
85
86 match (range, size) {
87 (ParsedRange::Star, ParsedSize::Star) => {
88 Err(ParseHttpRangeOrContentRangeError::Malformed)
89 }
90 (ParsedRange::Star, ParsedSize::Value(size)) => {
91 Ok(Self::Unsatisfiable(Unsatisfiable { size }))
92 }
93 (ParsedRange::Range(range), ParsedSize::Star) => {
94 Ok(Self::Bound(Bound { range, size: None }))
95 }
96 (ParsedRange::Range(range), ParsedSize::Value(size)) if range.end() < size => {
97 Ok(Self::Bound(Bound {
98 range,
99 size: Some(size),
100 }))
101 }
102 (ParsedRange::Range(_), ParsedSize::Value(_)) => {
103 Err(ParseHttpRangeOrContentRangeError::MalformedRange)
104 }
105 }
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
111pub enum InvalidBound {
112 #[error(transparent)]
113 InvalidRange(#[from] InvalidOrderedRange),
114 #[error("The provided range `end`: {} is greater than or equal to `size`: {size}", range.end)]
115 InvalidSize { range: OrderedRange, size: u64 },
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub struct Bound {
120 range: OrderedRange,
121 size: Option<u64>,
122}
123
124impl Bound {
125 pub fn new(range: RangeInclusive<u64>, size: Option<u64>) -> Result<Self, InvalidBound> {
127 let range = OrderedRange::new(range)?;
128
129 if let Some(size) = size
130 && range.end() >= size
131 {
132 return Err(InvalidBound::InvalidSize { range, size });
133 }
134
135 Ok(Self { range, size })
136 }
137
138 pub fn range(&self) -> OrderedRange {
140 self.range
141 }
142
143 pub fn size(&self) -> Option<u64> {
145 self.size
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub struct Unsatisfiable {
152 size: u64,
153}
154
155impl Unsatisfiable {
156 pub fn new(size: u64) -> Self {
158 Self { size }
159 }
160}
161
162#[derive(Debug, Clone, Copy)]
163enum ParsedRange {
164 Star,
165 Range(OrderedRange),
166}
167
168impl FromStr for ParsedRange {
169 type Err = ParseHttpRangeOrContentRangeError;
170
171 fn from_str(s: &str) -> Result<Self, Self::Err> {
172 if s == "*" {
173 return Ok(ParsedRange::Star);
174 }
175
176 let (start_str, end_str) = s
177 .split_once('-')
178 .ok_or(ParseHttpRangeOrContentRangeError::MalformedRange)?;
179
180 let start = u64_unprefixed_parse(start_str)
181 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
182 let end = u64_unprefixed_parse(end_str)
183 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
184
185 let range = OrderedRange::new(start..=end)?;
186 Ok(ParsedRange::Range(range))
187 }
188}
189
190#[derive(Debug, Clone, Copy)]
191enum ParsedSize {
192 Star,
193 Value(u64),
194}
195
196impl FromStr for ParsedSize {
197 type Err = InvalidHttpU64;
198
199 fn from_str(s: &str) -> Result<Self, Self::Err> {
200 Ok(if s == "*" {
201 ParsedSize::Star
202 } else {
203 let size = u64_unprefixed_parse(s)?;
204 ParsedSize::Value(size)
205 })
206 }
207}
208
209impl From<&HttpContentRange> for HeaderValue {
210 fn from(value: &HttpContentRange) -> Self {
211 HeaderValue::from_maybe_shared(value.to_string())
212 .expect("`HttpContentRange` Display produced non-visible ASCII characters")
213 }
214}
215
216impl TryFrom<&HeaderValue> for HttpContentRange {
217 type Error = ParseHttpRangeOrContentRangeError;
218 fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
219 value
220 .to_str()
221 .map_err(|_| ParseHttpRangeOrContentRangeError::ContainsNonVisibleASCII)?
222 .parse::<Self>()
223 }
224}
225
226#[cfg(feature = "axum")]
227impl<S> axum_core::extract::OptionalFromRequestParts<S> for HttpContentRange
228where
229 S: Send + Sync,
230{
231 type Rejection = ParseHttpRangeOrContentRangeError;
232
233 async fn from_request_parts(
234 parts: &mut http::request::Parts,
235 _state: &S,
236 ) -> Result<Option<Self>, Self::Rejection> {
237 match parts.headers.get(http::header::CONTENT_RANGE) {
238 Some(content_range) => {
239 let content_range = HttpContentRange::try_from(content_range)?;
240 Ok(Some(content_range))
241 }
242 None => Ok(None),
243 }
244 }
245}
246
247impl Display for HttpContentRange {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 match self {
250 HttpContentRange::Bound(Bound { range, size }) => match size {
251 Some(size) => write!(f, "{UNIT} {range}/{size}"),
252 None => write!(f, "{UNIT} {range}/*"),
253 },
254 HttpContentRange::Unsatisfiable(Unsatisfiable { size }) => write!(f, "{UNIT} */{size}"),
255 }
256 }
257}