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