headers/common/
content_range.rs

1use std::fmt;
2use std::ops::{Bound, RangeBounds};
3
4use http::{HeaderName, HeaderValue};
5
6use crate::{util, Error, Header};
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 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
48error_type!(InvalidContentRange);
49
50impl ContentRange {
51    /// Construct a new `Content-Range: bytes ..` header.
52    pub fn bytes(
53        range: impl RangeBounds<u64>,
54        complete_length: impl Into<Option<u64>>,
55    ) -> Result<ContentRange, InvalidContentRange> {
56        let complete_length = complete_length.into();
57
58        let start = match range.start_bound() {
59            Bound::Included(&s) => s,
60            Bound::Excluded(&s) => s + 1,
61            Bound::Unbounded => 0,
62        };
63
64        let end = match range.end_bound() {
65            Bound::Included(&e) => e,
66            Bound::Excluded(&e) => e - 1,
67            Bound::Unbounded => match complete_length {
68                Some(max) => max - 1,
69                None => return Err(InvalidContentRange { _inner: () }),
70            },
71        };
72
73        Ok(ContentRange {
74            range: Some((start, end)),
75            complete_length,
76        })
77    }
78
79    /// Create a new `ContentRange` stating the range could not be satisfied.
80    ///
81    /// The passed argument is the complete length of the entity.
82    pub fn unsatisfied_bytes(complete_length: u64) -> Self {
83        ContentRange {
84            range: None,
85            complete_length: Some(complete_length),
86        }
87    }
88
89    /// Get the byte range if satisified.
90    ///
91    /// Note that these byte ranges are inclusive on both ends.
92    pub fn bytes_range(&self) -> Option<(u64, u64)> {
93        self.range
94    }
95
96    /// Get the bytes complete length if available.
97    pub fn bytes_len(&self) -> Option<u64> {
98        self.complete_length
99    }
100}
101
102impl Header for ContentRange {
103    fn name() -> &'static HeaderName {
104        &::http::header::CONTENT_RANGE
105    }
106
107    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
108        values
109            .next()
110            .and_then(|v| v.to_str().ok())
111            .and_then(|s| split_in_two(s, ' '))
112            .and_then(|(unit, spec)| {
113                if unit != "bytes" {
114                    // For now, this only supports bytes-content-range. nani?
115                    return None;
116                }
117
118                let (range, complete_length) = split_in_two(spec, '/')?;
119
120                let complete_length = if complete_length == "*" {
121                    None
122                } else {
123                    Some(complete_length.parse().ok()?)
124                };
125
126                let range = if range == "*" {
127                    None
128                } else {
129                    let (first_byte, last_byte) = split_in_two(range, '-')?;
130                    let first_byte = first_byte.parse().ok()?;
131                    let last_byte = last_byte.parse().ok()?;
132                    if last_byte < first_byte {
133                        return None;
134                    }
135                    Some((first_byte, last_byte))
136                };
137
138                Some(ContentRange {
139                    range,
140                    complete_length,
141                })
142            })
143            .ok_or_else(Error::invalid)
144    }
145
146    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
147        struct Adapter<'a>(&'a ContentRange);
148
149        impl fmt::Display for Adapter<'_> {
150            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151                f.write_str("bytes ")?;
152
153                if let Some((first_byte, last_byte)) = self.0.range {
154                    write!(f, "{}-{}", first_byte, last_byte)?;
155                } else {
156                    f.write_str("*")?;
157                }
158
159                f.write_str("/")?;
160
161                if let Some(v) = self.0.complete_length {
162                    write!(f, "{}", v)
163                } else {
164                    f.write_str("*")
165                }
166            }
167        }
168
169        values.extend(::std::iter::once(util::fmt(Adapter(self))));
170    }
171}
172
173fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> {
174    let mut iter = s.splitn(2, separator);
175    match (iter.next(), iter.next()) {
176        (Some(a), Some(b)) => Some((a, b)),
177        _ => None,
178    }
179}
180
181/*
182test_header!(test_bytes,
183    vec![b"bytes 0-499/500"],
184    Some(ContentRange(ContentRangeSpec::Bytes {
185        range: Some((0, 499)),
186        complete_length: Some(500)
187    })));
188
189test_header!(test_bytes_unknown_len,
190    vec![b"bytes 0-499/*"],
191    Some(ContentRange(ContentRangeSpec::Bytes {
192        range: Some((0, 499)),
193        complete_length: None
194    })));
195
196test_header!(test_bytes_unknown_range,
197    vec![b"bytes */
198500"],
199            Some(ContentRange(ContentRangeSpec::Bytes {
200                range: None,
201                complete_length: Some(500)
202            })));
203
204        test_header!(test_unregistered,
205            vec![b"seconds 1-2"],
206            Some(ContentRange(ContentRangeSpec::Unregistered {
207                unit: "seconds".to_owned(),
208                resp: "1-2".to_owned()
209            })));
210
211        test_header!(test_no_len,
212            vec![b"bytes 0-499"],
213            None::<ContentRange>);
214
215        test_header!(test_only_unit,
216            vec![b"bytes"],
217            None::<ContentRange>);
218
219        test_header!(test_end_less_than_start,
220            vec![b"bytes 499-0/500"],
221            None::<ContentRange>);
222
223        test_header!(test_blank,
224            vec![b""],
225            None::<ContentRange>);
226
227        test_header!(test_bytes_many_spaces,
228            vec![b"bytes 1-2/500 3"],
229            None::<ContentRange>);
230
231        test_header!(test_bytes_many_slashes,
232            vec![b"bytes 1-2/500/600"],
233            None::<ContentRange>);
234
235        test_header!(test_bytes_many_dashes,
236            vec![b"bytes 1-2-3/500"],
237            None::<ContentRange>);
238*/