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_length::ContentLength; | ||||||
| pub use self::content_encoding::ContentEncoding; | pub use self::content_encoding::ContentEncoding; | ||||||
| pub use self::content_language::ContentLanguage; | pub use self::content_language::ContentLanguage; | ||||||
|  | pub use self::content_range::{ContentRange, ContentRangeSpec}; | ||||||
| pub use self::content_type::ContentType; | pub use self::content_type::ContentType; | ||||||
| pub use self::cookie::Cookie; | pub use self::cookie::Cookie; | ||||||
| pub use self::date::Date; | pub use self::date::Date; | ||||||
| @@ -368,6 +369,7 @@ mod connection; | |||||||
| mod content_encoding; | mod content_encoding; | ||||||
| mod content_language; | mod content_language; | ||||||
| mod content_length; | mod content_length; | ||||||
|  | mod content_range; | ||||||
| mod content_type; | mod content_type; | ||||||
| mod date; | mod date; | ||||||
| mod etag; | mod etag; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user