|
| 1 | +use std::fmt::{self, Display}; |
| 2 | +use std::str::FromStr; |
| 3 | + |
| 4 | +header! { |
| 5 | + #[doc="`Content-Range` header, defined in"] |
| 6 | + #[doc="[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_string(), |
| 35 | + resp: "1-2".to_string() |
| 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 | + |
| 70 | +/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) |
| 71 | +/// |
| 72 | +/// # ABNF |
| 73 | +/// ```plain |
| 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)] |
| 90 | +pub 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 | + |
| 111 | +fn split_in_two<'a>(s: &'a str, separator: char) -> Option<(&'a str, &'a 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 | + |
| 119 | +impl 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) = try!(split_in_two(resp, '/').ok_or(::Error::Header)); |
| 126 | + |
| 127 | + let instance_length = if instance_length == "*" { |
| 128 | + None |
| 129 | + } else { |
| 130 | + Some(try!(instance_length.parse().map_err(|_| ::Error::Header))) |
| 131 | + }; |
| 132 | + |
| 133 | + let range = if range == "*" { |
| 134 | + None |
| 135 | + } else { |
| 136 | + let (first_byte, last_byte) = try!(split_in_two(range, '-').ok_or(::Error::Header)); |
| 137 | + let first_byte = try!(first_byte.parse().map_err(|_| ::Error::Header)); |
| 138 | + let last_byte = try!(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_string(), |
| 153 | + resp: resp.to_string() |
| 154 | + } |
| 155 | + } |
| 156 | + _ => return Err(::Error::Header) |
| 157 | + }; |
| 158 | + Ok(res) |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +impl Display for ContentRangeSpec { |
| 163 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 164 | + match *self { |
| 165 | + ContentRangeSpec::Bytes { range, instance_length } => { |
| 166 | + try!(f.write_str("bytes ")); |
| 167 | + match range { |
| 168 | + Some((first_byte, last_byte)) => { |
| 169 | + try!(write!(f, "{}-{}", first_byte, last_byte)); |
| 170 | + }, |
| 171 | + None => { |
| 172 | + try!(f.write_str("*")); |
| 173 | + } |
| 174 | + }; |
| 175 | + try!(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 | + try!(f.write_str(&unit)); |
| 184 | + try!(f.write_str(" ")); |
| 185 | + f.write_str(&resp) |
| 186 | + } |
| 187 | + } |
| 188 | + } |
| 189 | +} |
0 commit comments