feat(headers): Content-Range header
This commit is contained in:
189
src/header/common/content_range.rs
Normal file
189
src/header/common/content_range.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::str::FromStr;
|
||||
|
||||
header! {
|
||||
#[doc="`Content-Range` header, defined in"]
|
||||
#[doc="[RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2)"]
|
||||
(ContentRange, "Content-Range") => [ContentRangeSpec]
|
||||
|
||||
test_content_range {
|
||||
test_header!(test_bytes,
|
||||
vec![b"bytes 0-499/500"],
|
||||
Some(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: Some((0, 499)),
|
||||
instance_length: Some(500)
|
||||
})));
|
||||
|
||||
test_header!(test_bytes_unknown_len,
|
||||
vec![b"bytes 0-499/*"],
|
||||
Some(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: Some((0, 499)),
|
||||
instance_length: None
|
||||
})));
|
||||
|
||||
test_header!(test_bytes_unknown_range,
|
||||
vec![b"bytes */500"],
|
||||
Some(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: None,
|
||||
instance_length: Some(500)
|
||||
})));
|
||||
|
||||
test_header!(test_unregistered,
|
||||
vec![b"seconds 1-2"],
|
||||
Some(ContentRange(ContentRangeSpec::Unregistered {
|
||||
unit: "seconds".to_string(),
|
||||
resp: "1-2".to_string()
|
||||
})));
|
||||
|
||||
test_header!(test_no_len,
|
||||
vec![b"bytes 0-499"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_only_unit,
|
||||
vec![b"bytes"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_end_less_than_start,
|
||||
vec![b"bytes 499-0/500"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_blank,
|
||||
vec![b""],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_bytes_many_spaces,
|
||||
vec![b"bytes 1-2/500 3"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_bytes_many_slashes,
|
||||
vec![b"bytes 1-2/500/600"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_bytes_many_dashes,
|
||||
vec![b"bytes 1-2-3/500"],
|
||||
None::<ContentRange>);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2)
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// Content-Range = byte-content-range
|
||||
/// / other-content-range
|
||||
///
|
||||
/// byte-content-range = bytes-unit SP
|
||||
/// ( byte-range-resp / unsatisfied-range )
|
||||
///
|
||||
/// byte-range-resp = byte-range "/" ( complete-length / "*" )
|
||||
/// byte-range = first-byte-pos "-" last-byte-pos
|
||||
/// unsatisfied-range = "*/" complete-length
|
||||
///
|
||||
/// complete-length = 1*DIGIT
|
||||
///
|
||||
/// other-content-range = other-range-unit SP other-range-resp
|
||||
/// other-range-resp = *CHAR
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum ContentRangeSpec {
|
||||
/// Byte range
|
||||
Bytes {
|
||||
/// First and last bytes of the range, omitted if request could not be
|
||||
/// satisfied
|
||||
range: Option<(u64, u64)>,
|
||||
|
||||
/// Total length of the instance, can be omitted if unknown
|
||||
instance_length: Option<u64>
|
||||
},
|
||||
|
||||
/// Custom range, with unit not registered at IANA
|
||||
Unregistered {
|
||||
/// other-range-unit
|
||||
unit: String,
|
||||
|
||||
/// other-range-resp
|
||||
resp: String
|
||||
}
|
||||
}
|
||||
|
||||
fn split_in_two<'a>(s: &'a str, separator: char) -> Option<(&'a str, &'a str)> {
|
||||
let mut iter = s.splitn(2, separator);
|
||||
match (iter.next(), iter.next()) {
|
||||
(Some(a), Some(b)) => Some((a, b)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ContentRangeSpec {
|
||||
type Err = ::Error;
|
||||
|
||||
fn from_str(s: &str) -> ::Result<Self> {
|
||||
let res = match split_in_two(s, ' ') {
|
||||
Some(("bytes", resp)) => {
|
||||
let (range, instance_length) = try!(split_in_two(resp, '/').ok_or(::Error::Header));
|
||||
|
||||
let instance_length = if instance_length == "*" {
|
||||
None
|
||||
} else {
|
||||
Some(try!(instance_length.parse().map_err(|_| ::Error::Header)))
|
||||
};
|
||||
|
||||
let range = if range == "*" {
|
||||
None
|
||||
} else {
|
||||
let (first_byte, last_byte) = try!(split_in_two(range, '-').ok_or(::Error::Header));
|
||||
let first_byte = try!(first_byte.parse().map_err(|_| ::Error::Header));
|
||||
let last_byte = try!(last_byte.parse().map_err(|_| ::Error::Header));
|
||||
if last_byte < first_byte {
|
||||
return Err(::Error::Header);
|
||||
}
|
||||
Some((first_byte, last_byte))
|
||||
};
|
||||
|
||||
ContentRangeSpec::Bytes {
|
||||
range: range,
|
||||
instance_length: instance_length
|
||||
}
|
||||
}
|
||||
Some((unit, resp)) => {
|
||||
ContentRangeSpec::Unregistered {
|
||||
unit: unit.to_string(),
|
||||
resp: resp.to_string()
|
||||
}
|
||||
}
|
||||
_ => return Err(::Error::Header)
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContentRangeSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ContentRangeSpec::Bytes { range, instance_length } => {
|
||||
try!(f.write_str("bytes "));
|
||||
match range {
|
||||
Some((first_byte, last_byte)) => {
|
||||
try!(write!(f, "{}-{}", first_byte, last_byte));
|
||||
},
|
||||
None => {
|
||||
try!(f.write_str("*"));
|
||||
}
|
||||
};
|
||||
try!(f.write_str("/"));
|
||||
if let Some(v) = instance_length {
|
||||
write!(f, "{}", v)
|
||||
} else {
|
||||
f.write_str("*")
|
||||
}
|
||||
}
|
||||
ContentRangeSpec::Unregistered { ref unit, ref resp } => {
|
||||
try!(f.write_str(&unit));
|
||||
try!(f.write_str(" "));
|
||||
f.write_str(&resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ pub use self::connection::{Connection, ConnectionOption};
|
||||
pub use self::content_length::ContentLength;
|
||||
pub use self::content_encoding::ContentEncoding;
|
||||
pub use self::content_language::ContentLanguage;
|
||||
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
||||
pub use self::content_type::ContentType;
|
||||
pub use self::cookie::Cookie;
|
||||
pub use self::date::Date;
|
||||
@@ -368,6 +369,7 @@ mod connection;
|
||||
mod content_encoding;
|
||||
mod content_language;
|
||||
mod content_length;
|
||||
mod content_range;
|
||||
mod content_type;
|
||||
mod date;
|
||||
mod etag;
|
||||
|
||||
Reference in New Issue
Block a user