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