Skip to main content

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, HeaderDecode, HeaderEncode, TypedHeader, 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<Self, 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(Self {
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    #[must_use]
86    pub fn unsatisfied_bytes(complete_length: u64) -> Self {
87        Self {
88            range: None,
89            complete_length: Some(complete_length),
90        }
91    }
92
93    /// Get the byte range if satisified.
94    ///
95    /// Note that these byte ranges are inclusive on both ends.
96    #[must_use]
97    pub fn bytes_range(&self) -> Option<(u64, u64)> {
98        self.range
99    }
100
101    /// Get the bytes complete length if available.
102    #[must_use]
103    pub fn bytes_len(&self) -> Option<u64> {
104        self.complete_length
105    }
106}
107
108impl TypedHeader for ContentRange {
109    fn name() -> &'static HeaderName {
110        &::rama_http_types::header::CONTENT_RANGE
111    }
112}
113
114impl HeaderDecode for ContentRange {
115    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
116        values
117            .next()
118            .and_then(|v| v.to_str().ok())
119            .and_then(|s| split_in_two(s, ' '))
120            .and_then(|(unit, spec)| {
121                if unit != "bytes" {
122                    // For now, this only supports bytes-content-range. nani?
123                    return None;
124                }
125
126                let (range, complete_length) = split_in_two(spec, '/')?;
127
128                let complete_length = if complete_length == "*" {
129                    None
130                } else {
131                    Some(complete_length.parse().ok()?)
132                };
133
134                let range = if range == "*" {
135                    None
136                } else {
137                    let (first_byte, last_byte) = split_in_two(range, '-')?;
138                    let first_byte = first_byte.parse().ok()?;
139                    let last_byte = last_byte.parse().ok()?;
140                    if last_byte < first_byte {
141                        return None;
142                    }
143                    Some((first_byte, last_byte))
144                };
145
146                Some(Self {
147                    range,
148                    complete_length,
149                })
150            })
151            .ok_or_else(Error::invalid)
152    }
153}
154
155impl HeaderEncode for ContentRange {
156    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
157        struct Adapter<'a>(&'a ContentRange);
158
159        impl fmt::Display for Adapter<'_> {
160            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161                f.write_str("bytes ")?;
162
163                if let Some((first_byte, last_byte)) = self.0.range {
164                    write!(f, "{first_byte}-{last_byte}")?;
165                } else {
166                    f.write_str("*")?;
167                }
168
169                f.write_str("/")?;
170
171                if let Some(v) = self.0.complete_length {
172                    write!(f, "{v}")
173                } else {
174                    f.write_str("*")
175                }
176            }
177        }
178
179        values.extend(::std::iter::once(util::fmt(Adapter(self))));
180    }
181}
182
183fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> {
184    let mut iter = s.splitn(2, separator);
185    match (iter.next(), iter.next()) {
186        (Some(a), Some(b)) => Some((a, b)),
187        _ => None,
188    }
189}
190
191/*
192test_header!(test_bytes,
193    vec![b"bytes 0-499/500"],
194    Some(ContentRange(ContentRangeSpec::Bytes {
195        range: Some((0, 499)),
196        complete_length: Some(500)
197    })));
198
199test_header!(test_bytes_unknown_len,
200    vec![b"bytes 0-499/*"],
201    Some(ContentRange(ContentRangeSpec::Bytes {
202        range: Some((0, 499)),
203        complete_length: None
204    })));
205
206test_header!(test_bytes_unknown_range,
207    vec![b"bytes */
208500"],
209            Some(ContentRange(ContentRangeSpec::Bytes {
210                range: None,
211                complete_length: Some(500)
212            })));
213
214        test_header!(test_unregistered,
215            vec![b"seconds 1-2"],
216            Some(ContentRange(ContentRangeSpec::Unregistered {
217                unit: "seconds".to_owned(),
218                resp: "1-2".to_owned()
219            })));
220
221        test_header!(test_no_len,
222            vec![b"bytes 0-499"],
223            None::<ContentRange>);
224
225        test_header!(test_only_unit,
226            vec![b"bytes"],
227            None::<ContentRange>);
228
229        test_header!(test_end_less_than_start,
230            vec![b"bytes 499-0/500"],
231            None::<ContentRange>);
232
233        test_header!(test_blank,
234            vec![b""],
235            None::<ContentRange>);
236
237        test_header!(test_bytes_many_spaces,
238            vec![b"bytes 1-2/500 3"],
239            None::<ContentRange>);
240
241        test_header!(test_bytes_many_slashes,
242            vec![b"bytes 1-2/500/600"],
243            None::<ContentRange>);
244
245        test_header!(test_bytes_many_dashes,
246            vec![b"bytes 1-2-3/500"],
247            None::<ContentRange>);
248*/