actix_web/http/header/
content_range.rs

1use std::{
2    fmt::{self, Display, Write},
3    str::FromStr,
4};
5
6use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer, CONTENT_RANGE};
7use crate::error::ParseError;
8
9crate::http::header::common_header! {
10    /// `Content-Range` header, defined
11    /// in [RFC 7233 §4.2](https://datatracker.ietf.org/doc/html/rfc7233#section-4.2)
12    (ContentRange, CONTENT_RANGE) => [ContentRangeSpec]
13
14    test_parse_and_format {
15        crate::http::header::common_header_test!(test_bytes,
16            [b"bytes 0-499/500"],
17            Some(ContentRange(ContentRangeSpec::Bytes {
18                range: Some((0, 499)),
19                instance_length: Some(500)
20            })));
21
22        crate::http::header::common_header_test!(test_bytes_unknown_len,
23            [b"bytes 0-499/*"],
24            Some(ContentRange(ContentRangeSpec::Bytes {
25                range: Some((0, 499)),
26                instance_length: None
27            })));
28
29        crate::http::header::common_header_test!(test_bytes_unknown_range,
30            [b"bytes */500"],
31            Some(ContentRange(ContentRangeSpec::Bytes {
32                range: None,
33                instance_length: Some(500)
34            })));
35
36        crate::http::header::common_header_test!(test_unregistered,
37            [b"seconds 1-2"],
38            Some(ContentRange(ContentRangeSpec::Unregistered {
39                unit: "seconds".to_owned(),
40                resp: "1-2".to_owned()
41            })));
42
43        crate::http::header::common_header_test!(test_no_len,
44            [b"bytes 0-499"],
45            None::<ContentRange>);
46
47        crate::http::header::common_header_test!(test_only_unit,
48            [b"bytes"],
49            None::<ContentRange>);
50
51        crate::http::header::common_header_test!(test_end_less_than_start,
52            [b"bytes 499-0/500"],
53            None::<ContentRange>);
54
55        crate::http::header::common_header_test!(test_blank,
56            [b""],
57            None::<ContentRange>);
58
59        crate::http::header::common_header_test!(test_bytes_many_spaces,
60            [b"bytes 1-2/500 3"],
61            None::<ContentRange>);
62
63        crate::http::header::common_header_test!(test_bytes_many_slashes,
64            [b"bytes 1-2/500/600"],
65            None::<ContentRange>);
66
67        crate::http::header::common_header_test!(test_bytes_many_dashes,
68            [b"bytes 1-2-3/500"],
69            None::<ContentRange>);
70    }
71}
72
73/// Content-Range header, defined
74/// in [RFC 7233 §4.2](https://datatracker.ietf.org/doc/html/rfc7233#section-4.2)
75///
76/// # ABNF
77/// ```plain
78/// Content-Range       = byte-content-range
79///                     / other-content-range
80///
81/// byte-content-range  = bytes-unit SP
82///                       ( byte-range-resp / unsatisfied-range )
83///
84/// byte-range-resp     = byte-range "/" ( complete-length / "*" )
85/// byte-range          = first-byte-pos "-" last-byte-pos
86/// unsatisfied-range   = "*/" complete-length
87///
88/// complete-length     = 1*DIGIT
89///
90/// other-content-range = other-range-unit SP other-range-resp
91/// other-range-resp    = *CHAR
92/// ```
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum ContentRangeSpec {
95    /// Byte range
96    Bytes {
97        /// First and last bytes of the range, omitted if request could not be
98        /// satisfied
99        range: Option<(u64, u64)>,
100
101        /// Total length of the instance, can be omitted if unknown
102        instance_length: Option<u64>,
103    },
104
105    /// Custom range, with unit not registered at IANA
106    Unregistered {
107        /// other-range-unit
108        unit: String,
109
110        /// other-range-resp
111        resp: String,
112    },
113}
114
115impl FromStr for ContentRangeSpec {
116    type Err = ParseError;
117
118    fn from_str(s: &str) -> Result<Self, ParseError> {
119        let res = match s.split_once(' ') {
120            Some(("bytes", resp)) => {
121                let (range, instance_length) = resp.split_once('/').ok_or(ParseError::Header)?;
122
123                let instance_length = if instance_length == "*" {
124                    None
125                } else {
126                    Some(instance_length.parse().map_err(|_| ParseError::Header)?)
127                };
128
129                let range = if range == "*" {
130                    None
131                } else {
132                    let (first_byte, last_byte) =
133                        range.split_once('-').ok_or(ParseError::Header)?;
134                    let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?;
135                    let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?;
136                    if last_byte < first_byte {
137                        return Err(ParseError::Header);
138                    }
139                    Some((first_byte, last_byte))
140                };
141
142                ContentRangeSpec::Bytes {
143                    range,
144                    instance_length,
145                }
146            }
147            Some((unit, resp)) => ContentRangeSpec::Unregistered {
148                unit: unit.to_owned(),
149                resp: resp.to_owned(),
150            },
151            _ => return Err(ParseError::Header),
152        };
153        Ok(res)
154    }
155}
156
157impl Display for ContentRangeSpec {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        match *self {
160            ContentRangeSpec::Bytes {
161                range,
162                instance_length,
163            } => {
164                f.write_str("bytes ")?;
165                match range {
166                    Some((first_byte, last_byte)) => {
167                        write!(f, "{}-{}", first_byte, last_byte)?;
168                    }
169                    None => {
170                        f.write_str("*")?;
171                    }
172                };
173                f.write_str("/")?;
174                if let Some(v) = instance_length {
175                    write!(f, "{}", v)
176                } else {
177                    f.write_str("*")
178                }
179            }
180            ContentRangeSpec::Unregistered { ref unit, ref resp } => {
181                f.write_str(unit)?;
182                f.write_str(" ")?;
183                f.write_str(resp)
184            }
185        }
186    }
187}
188
189impl TryIntoHeaderValue for ContentRangeSpec {
190    type Error = InvalidHeaderValue;
191
192    fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
193        let mut writer = Writer::new();
194        let _ = write!(&mut writer, "{}", self);
195        HeaderValue::from_maybe_shared(writer.take())
196    }
197}