rama_http_headers/common/content_range.rs
1use std::fmt;
2use std::ops::{Bound, RangeBounds};
3
4use rama_http_types::{HeaderName, HeaderValue};
5
6use crate::{Error, Header, util};
7
8/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2)
9///
10/// # ABNF
11///
12/// ```text
13/// Content-Range = byte-content-range
14/// / other-content-range
15///
16/// byte-content-range = bytes-unit SP
17/// ( byte-range-resp / unsatisfied-range )
18///
19/// byte-range-resp = byte-range "/" ( complete-length / "*" )
20/// byte-range = first-byte-pos "-" last-byte-pos
21/// unsatisfied-range = "*/" complete-length
22///
23/// complete-length = 1*DIGIT
24///
25/// other-content-range = other-range-unit SP other-range-resp
26/// other-range-resp = *CHAR
27/// ```
28///
29/// # Example
30///
31/// ```
32/// use rama_http_headers::ContentRange;
33///
34/// // 100 bytes (included byte 199), with a full length of 3,400
35/// let cr = ContentRange::bytes(100..200, 3400).unwrap();
36/// ```
37//NOTE: only supporting bytes-content-range, YAGNI the extension
38#[derive(Clone, Debug, PartialEq)]
39pub struct ContentRange {
40 /// First and last bytes of the range, omitted if request could not be
41 /// satisfied
42 range: Option<(u64, u64)>,
43
44 /// Total length of the instance, can be omitted if unknown
45 complete_length: Option<u64>,
46}
47
48rama_utils::macros::error::static_str_error! {
49 #[doc = "content range is not valid"]
50 pub struct InvalidContentRange;
51}
52
53impl ContentRange {
54 /// Construct a new `Content-Range: bytes ..` header.
55 pub fn bytes(
56 range: impl RangeBounds<u64>,
57 complete_length: impl Into<Option<u64>>,
58 ) -> Result<ContentRange, InvalidContentRange> {
59 let complete_length = complete_length.into();
60
61 let start = match range.start_bound() {
62 Bound::Included(&s) => s,
63 Bound::Excluded(&s) => s + 1,
64 Bound::Unbounded => 0,
65 };
66
67 let end = match range.end_bound() {
68 Bound::Included(&e) => e,
69 Bound::Excluded(&e) => e - 1,
70 Bound::Unbounded => match complete_length {
71 Some(max) => max - 1,
72 None => return Err(InvalidContentRange),
73 },
74 };
75
76 Ok(ContentRange {
77 range: Some((start, end)),
78 complete_length,
79 })
80 }
81
82 /// Create a new `ContentRange` stating the range could not be satisfied.
83 ///
84 /// The passed argument is the complete length of the entity.
85 pub fn unsatisfied_bytes(complete_length: u64) -> Self {
86 ContentRange {
87 range: None,
88 complete_length: Some(complete_length),
89 }
90 }
91
92 /// Get the byte range if satisified.
93 ///
94 /// Note that these byte ranges are inclusive on both ends.
95 pub fn bytes_range(&self) -> Option<(u64, u64)> {
96 self.range
97 }
98
99 /// Get the bytes complete length if available.
100 pub fn bytes_len(&self) -> Option<u64> {
101 self.complete_length
102 }
103}
104
105impl Header for ContentRange {
106 fn name() -> &'static HeaderName {
107 &::rama_http_types::header::CONTENT_RANGE
108 }
109
110 fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
111 values
112 .next()
113 .and_then(|v| v.to_str().ok())
114 .and_then(|s| split_in_two(s, ' '))
115 .and_then(|(unit, spec)| {
116 if unit != "bytes" {
117 // For now, this only supports bytes-content-range. nani?
118 return None;
119 }
120
121 let (range, complete_length) = split_in_two(spec, '/')?;
122
123 let complete_length = if complete_length == "*" {
124 None
125 } else {
126 Some(complete_length.parse().ok()?)
127 };
128
129 let range = if range == "*" {
130 None
131 } else {
132 let (first_byte, last_byte) = split_in_two(range, '-')?;
133 let first_byte = first_byte.parse().ok()?;
134 let last_byte = last_byte.parse().ok()?;
135 if last_byte < first_byte {
136 return None;
137 }
138 Some((first_byte, last_byte))
139 };
140
141 Some(ContentRange {
142 range,
143 complete_length,
144 })
145 })
146 .ok_or_else(Error::invalid)
147 }
148
149 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
150 struct Adapter<'a>(&'a ContentRange);
151
152 impl fmt::Display for Adapter<'_> {
153 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154 f.write_str("bytes ")?;
155
156 if let Some((first_byte, last_byte)) = self.0.range {
157 write!(f, "{}-{}", first_byte, last_byte)?;
158 } else {
159 f.write_str("*")?;
160 }
161
162 f.write_str("/")?;
163
164 if let Some(v) = self.0.complete_length {
165 write!(f, "{}", v)
166 } else {
167 f.write_str("*")
168 }
169 }
170 }
171
172 values.extend(::std::iter::once(util::fmt(Adapter(self))));
173 }
174}
175
176fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> {
177 let mut iter = s.splitn(2, separator);
178 match (iter.next(), iter.next()) {
179 (Some(a), Some(b)) => Some((a, b)),
180 _ => None,
181 }
182}
183
184/*
185test_header!(test_bytes,
186 vec![b"bytes 0-499/500"],
187 Some(ContentRange(ContentRangeSpec::Bytes {
188 range: Some((0, 499)),
189 complete_length: Some(500)
190 })));
191
192test_header!(test_bytes_unknown_len,
193 vec![b"bytes 0-499/*"],
194 Some(ContentRange(ContentRangeSpec::Bytes {
195 range: Some((0, 499)),
196 complete_length: None
197 })));
198
199test_header!(test_bytes_unknown_range,
200 vec![b"bytes */
201500"],
202 Some(ContentRange(ContentRangeSpec::Bytes {
203 range: None,
204 complete_length: Some(500)
205 })));
206
207 test_header!(test_unregistered,
208 vec![b"seconds 1-2"],
209 Some(ContentRange(ContentRangeSpec::Unregistered {
210 unit: "seconds".to_owned(),
211 resp: "1-2".to_owned()
212 })));
213
214 test_header!(test_no_len,
215 vec![b"bytes 0-499"],
216 None::<ContentRange>);
217
218 test_header!(test_only_unit,
219 vec![b"bytes"],
220 None::<ContentRange>);
221
222 test_header!(test_end_less_than_start,
223 vec![b"bytes 499-0/500"],
224 None::<ContentRange>);
225
226 test_header!(test_blank,
227 vec![b""],
228 None::<ContentRange>);
229
230 test_header!(test_bytes_many_spaces,
231 vec![b"bytes 1-2/500 3"],
232 None::<ContentRange>);
233
234 test_header!(test_bytes_many_slashes,
235 vec![b"bytes 1-2/500/600"],
236 None::<ContentRange>);
237
238 test_header!(test_bytes_many_dashes,
239 vec![b"bytes 1-2-3/500"],
240 None::<ContentRange>);
241*/