Skip to main content

actix_web/http/header/
range.rs

1use std::{
2    cmp,
3    fmt::{self, Display, Write},
4    str::FromStr,
5};
6
7use actix_http::{error::ParseError, header, HttpMessage};
8
9use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
10
11/// `Range` header, defined
12/// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1)
13///
14/// The "Range" header field on a GET request modifies the method semantics to request transfer of
15/// only one or more sub-ranges of the selected representation data, rather than the entire selected
16/// representation data.
17///
18/// # Note
19/// This is a request header. Servers should not send `Range` in responses; use
20/// [`ContentRange`](super::ContentRange) / the `Content-Range` header for partial responses.
21///
22/// # ABNF
23/// ```plain
24/// Range = byte-ranges-specifier / other-ranges-specifier
25/// other-ranges-specifier = other-range-unit "=" other-range-set
26/// other-range-set = 1*VCHAR
27///
28/// bytes-unit = "bytes"
29///
30/// byte-ranges-specifier = bytes-unit "=" byte-range-set
31/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec)
32/// byte-range-spec = first-byte-pos "-" [last-byte-pos]
33/// suffix-byte-range-spec = "-" suffix-length
34/// suffix-length = 1*DIGIT
35/// first-byte-pos = 1*DIGIT
36/// last-byte-pos = 1*DIGIT
37/// ```
38///
39/// # Example Values
40/// * `bytes=1000-`
41/// * `bytes=-50`
42/// * `bytes=0-1,30-40`
43/// * `bytes=0-10,20-90,-100`
44/// * `custom_unit=0-123`
45/// * `custom_unit=xxx-yyy`
46///
47/// # Examples
48/// ```
49/// use actix_web::{http::header::{ByteRangeSpec, Range}, test};
50///
51/// let req = test::TestRequest::default()
52///     .insert_header(Range::Bytes(vec![
53///         ByteRangeSpec::FromTo(1, 100),
54///         ByteRangeSpec::From(200),
55///     ]))
56///     .insert_header(Range::Unregistered("letters".to_owned(), "a-f".to_owned()))
57///     .insert_header(Range::bytes(1, 100))
58///     .insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)]))
59///     .to_http_request();
60/// # let _ = req;
61/// ```
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum Range {
64    /// Byte range.
65    Bytes(Vec<ByteRangeSpec>),
66
67    /// Custom range, with unit not registered at IANA.
68    ///
69    /// (`other-range-unit`: String , `other-range-set`: String)
70    Unregistered(String, String),
71}
72
73/// A range of bytes to fetch.
74///
75/// Each [`Range::Bytes`] header can contain one or more `ByteRangeSpec`s.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum ByteRangeSpec {
78    /// All bytes from `x` to `y`, inclusive.
79    ///
80    /// Serialized as `x-y`.
81    ///
82    /// Example: `bytes=500-999` would represent the second 500 bytes.
83    FromTo(u64, u64),
84
85    /// All bytes starting from `x`, inclusive.
86    ///
87    /// Serialized as `x-`.
88    ///
89    /// Example: For a file of 1000 bytes, `bytes=950-` would represent bytes 950-999, inclusive.
90    From(u64),
91
92    /// The last `y` bytes, inclusive.
93    ///
94    /// Using the spec terminology, this is `suffix-byte-range-spec`. Serialized as `-y`.
95    ///
96    /// Example: For a file of 1000 bytes, `bytes=-50` is equivalent to `bytes=950-`.
97    Last(u64),
98}
99
100impl ByteRangeSpec {
101    /// Given the full length of the entity, attempt to normalize the byte range into an satisfiable
102    /// end-inclusive `(from, to)` range.
103    ///
104    /// The resulting range is guaranteed to be a satisfiable range within the bounds
105    /// of `0 <= from <= to < full_length`.
106    ///
107    /// If the byte range is deemed unsatisfiable, `None` is returned. An unsatisfiable range is
108    /// generally cause for a server to either reject the client request with a
109    /// `416 Range Not Satisfiable` status code, or to simply ignore the range header and serve the
110    /// full entity using a `200 OK` status code.
111    ///
112    /// This function closely follows [RFC 7233 §2.1]. As such, it considers ranges to be
113    /// satisfiable if they meet the following conditions:
114    ///
115    /// > If a valid byte-range-set includes at least one byte-range-spec with a first-byte-pos that
116    /// > is less than the current length of the representation, or at least one
117    /// > suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set is
118    /// > satisfiable. Otherwise, the byte-range-set is unsatisfiable.
119    ///
120    /// The function also computes remainder ranges based on the RFC:
121    ///
122    /// > If the last-byte-pos value is absent, or if the value is greater than or equal to the
123    /// > current length of the representation data, the byte range is interpreted as the remainder
124    /// > of the representation (i.e., the server replaces the value of last-byte-pos with a value
125    /// > that is one less than the current length of the selected representation).
126    ///
127    /// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233
128    pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> {
129        // If the full length is zero, there is no satisfiable end-inclusive range.
130        if full_length == 0 {
131            return None;
132        }
133
134        match *self {
135            ByteRangeSpec::FromTo(from, to) => {
136                if from < full_length && from <= to {
137                    Some((from, cmp::min(to, full_length - 1)))
138                } else {
139                    None
140                }
141            }
142
143            ByteRangeSpec::From(from) => {
144                if from < full_length {
145                    Some((from, full_length - 1))
146                } else {
147                    None
148                }
149            }
150
151            ByteRangeSpec::Last(last) => {
152                if last > 0 {
153                    // From the RFC: If the selected representation is shorter than the specified
154                    // suffix-length, the entire representation is used.
155                    if last > full_length {
156                        Some((0, full_length - 1))
157                    } else {
158                        Some((full_length - last, full_length - 1))
159                    }
160                } else {
161                    None
162                }
163            }
164        }
165    }
166}
167
168impl Range {
169    /// Constructs a common byte range header.
170    ///
171    /// Eg: `bytes=from-to`
172    pub fn bytes(from: u64, to: u64) -> Range {
173        Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)])
174    }
175
176    /// Constructs a byte range header with multiple subranges.
177    ///
178    /// Eg: `bytes=from1-to1,from2-to2,fromX-toX`
179    pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range {
180        Range::Bytes(
181            ranges
182                .into_iter()
183                .map(|(from, to)| ByteRangeSpec::FromTo(from, to))
184                .collect(),
185        )
186    }
187}
188
189impl fmt::Display for ByteRangeSpec {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match *self {
192            ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to),
193            ByteRangeSpec::Last(pos) => write!(f, "-{}", pos),
194            ByteRangeSpec::From(pos) => write!(f, "{}-", pos),
195        }
196    }
197}
198
199impl fmt::Display for Range {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            Range::Bytes(ranges) => {
203                write!(f, "bytes=")?;
204
205                for (i, range) in ranges.iter().enumerate() {
206                    if i != 0 {
207                        f.write_str(",")?;
208                    }
209
210                    Display::fmt(range, f)?;
211                }
212                Ok(())
213            }
214
215            Range::Unregistered(unit, range_str) => {
216                write!(f, "{}={}", unit, range_str)
217            }
218        }
219    }
220}
221
222impl FromStr for Range {
223    type Err = ParseError;
224
225    fn from_str(s: &str) -> Result<Range, ParseError> {
226        let (unit, val) = s.split_once('=').ok_or(ParseError::Header)?;
227
228        match (unit, val) {
229            ("bytes", ranges) => {
230                let ranges = from_comma_delimited(ranges);
231
232                if ranges.is_empty() {
233                    return Err(ParseError::Header);
234                }
235
236                Ok(Range::Bytes(ranges))
237            }
238
239            (_, "") => Err(ParseError::Header),
240            ("", _) => Err(ParseError::Header),
241
242            (unit, range_str) => Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())),
243        }
244    }
245}
246
247impl FromStr for ByteRangeSpec {
248    type Err = ParseError;
249
250    fn from_str(s: &str) -> Result<ByteRangeSpec, ParseError> {
251        let (start, end) = s.split_once('-').ok_or(ParseError::Header)?;
252
253        match (start, end) {
254            ("", end) => end
255                .parse()
256                .or(Err(ParseError::Header))
257                .map(ByteRangeSpec::Last),
258
259            (start, "") => start
260                .parse()
261                .or(Err(ParseError::Header))
262                .map(ByteRangeSpec::From),
263
264            (start, end) => match (start.parse(), end.parse()) {
265                (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)),
266                _ => Err(ParseError::Header),
267            },
268        }
269    }
270}
271
272impl Header for Range {
273    fn name() -> HeaderName {
274        header::RANGE
275    }
276
277    #[inline]
278    fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> {
279        header::from_one_raw_str(msg.headers().get(Self::name()))
280    }
281}
282
283impl TryIntoHeaderValue for Range {
284    type Error = InvalidHeaderValue;
285
286    fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
287        let mut wrt = Writer::new();
288        let _ = write!(wrt, "{}", self);
289        HeaderValue::from_maybe_shared(wrt.take())
290    }
291}
292
293/// Parses 0 or more items out of a comma delimited string, ignoring invalid items.
294fn from_comma_delimited<T: FromStr>(s: &str) -> Vec<T> {
295    s.split(',')
296        .filter_map(|x| match x.trim() {
297            "" => None,
298            y => Some(y),
299        })
300        .filter_map(|x| x.parse().ok())
301        .collect()
302}
303
304#[cfg(test)]
305mod tests {
306    use actix_http::{test::TestRequest, Request};
307
308    use super::*;
309
310    fn req(s: &str) -> Request {
311        TestRequest::default()
312            .insert_header((header::RANGE, s))
313            .finish()
314    }
315
316    #[test]
317    fn test_parse_bytes_range_valid() {
318        let r: Range = Header::parse(&req("bytes=1-100")).unwrap();
319        let r2: Range = Header::parse(&req("bytes=1-100,-")).unwrap();
320        let r3 = Range::bytes(1, 100);
321        assert_eq!(r, r2);
322        assert_eq!(r2, r3);
323
324        let r: Range = Header::parse(&req("bytes=1-100,200-")).unwrap();
325        let r2: Range = Header::parse(&req("bytes= 1-100 , 101-xxx,  200- ")).unwrap();
326        let r3 = Range::Bytes(vec![
327            ByteRangeSpec::FromTo(1, 100),
328            ByteRangeSpec::From(200),
329        ]);
330        assert_eq!(r, r2);
331        assert_eq!(r2, r3);
332
333        let r: Range = Header::parse(&req("bytes=1-100,-100")).unwrap();
334        let r2: Range = Header::parse(&req("bytes=1-100, ,,-100")).unwrap();
335        let r3 = Range::Bytes(vec![
336            ByteRangeSpec::FromTo(1, 100),
337            ByteRangeSpec::Last(100),
338        ]);
339        assert_eq!(r, r2);
340        assert_eq!(r2, r3);
341
342        let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap();
343        let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned());
344        assert_eq!(r, r2);
345    }
346
347    #[test]
348    fn test_parse_unregistered_range_valid() {
349        let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap();
350        let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned());
351        assert_eq!(r, r2);
352
353        let r: Range = Header::parse(&req("custom=abcd")).unwrap();
354        let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned());
355        assert_eq!(r, r2);
356
357        let r: Range = Header::parse(&req("custom=xxx-yyy")).unwrap();
358        let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned());
359        assert_eq!(r, r2);
360    }
361
362    #[test]
363    fn test_parse_invalid() {
364        let r: Result<Range, ParseError> = Header::parse(&req("bytes=1-a,-"));
365        assert_eq!(r.ok(), None);
366
367        let r: Result<Range, ParseError> = Header::parse(&req("bytes=1-2-3"));
368        assert_eq!(r.ok(), None);
369
370        let r: Result<Range, ParseError> = Header::parse(&req("abc"));
371        assert_eq!(r.ok(), None);
372
373        let r: Result<Range, ParseError> = Header::parse(&req("bytes=1-100="));
374        assert_eq!(r.ok(), None);
375
376        let r: Result<Range, ParseError> = Header::parse(&req("bytes="));
377        assert_eq!(r.ok(), None);
378
379        let r: Result<Range, ParseError> = Header::parse(&req("custom="));
380        assert_eq!(r.ok(), None);
381
382        let r: Result<Range, ParseError> = Header::parse(&req("=1-100"));
383        assert_eq!(r.ok(), None);
384    }
385
386    #[test]
387    fn test_fmt() {
388        let range = Range::Bytes(vec![
389            ByteRangeSpec::FromTo(0, 1000),
390            ByteRangeSpec::From(2000),
391        ]);
392        assert_eq!(&range.to_string(), "bytes=0-1000,2000-");
393
394        let range = Range::Bytes(vec![]);
395
396        assert_eq!(&range.to_string(), "bytes=");
397
398        let range = Range::Unregistered("custom".to_owned(), "1-xxx".to_owned());
399
400        assert_eq!(&range.to_string(), "custom=1-xxx");
401    }
402
403    #[test]
404    fn test_byte_range_spec_to_satisfiable_range() {
405        assert_eq!(
406            Some((0, 0)),
407            ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3)
408        );
409        assert_eq!(
410            Some((1, 2)),
411            ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3)
412        );
413        assert_eq!(
414            Some((1, 2)),
415            ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3)
416        );
417        assert_eq!(None, ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3));
418        assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3));
419        assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0));
420
421        assert_eq!(Some((0, 2)), ByteRangeSpec::From(0).to_satisfiable_range(3));
422        assert_eq!(Some((2, 2)), ByteRangeSpec::From(2).to_satisfiable_range(3));
423        assert_eq!(None, ByteRangeSpec::From(3).to_satisfiable_range(3));
424        assert_eq!(None, ByteRangeSpec::From(5).to_satisfiable_range(3));
425        assert_eq!(None, ByteRangeSpec::From(0).to_satisfiable_range(0));
426
427        assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3));
428        assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3));
429        assert_eq!(Some((0, 2)), ByteRangeSpec::Last(5).to_satisfiable_range(3));
430        assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3));
431        assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0));
432    }
433}