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