hyperx/header/common/
content_range.rs

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