diff --git a/CHANGES.md b/CHANGES.md index 2b5a71001..78aa729df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Added * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] * `AcceptEncoding` typed header. [#2482] +* `Range` typed header. [#2485] * `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] @@ -21,6 +22,7 @@ [#2480]: https://github.com/actix/actix-web/pull/2480 [#2482]: https://github.com/actix/actix-web/pull/2482 [#2484]: https://github.com/actix/actix-web/pull/2484 +[#2485]: https://github.com/actix/actix-web/pull/2485 ## 4.0.0-beta.13 - 2021-11-30 diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 98548dadd..07b7592d7 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -40,7 +40,7 @@ mod if_unmodified_since; mod last_modified; mod macros; mod preference; -// mod range; +mod range; #[cfg(test)] pub(crate) use macros::common_header_test; @@ -68,7 +68,7 @@ pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; pub use self::last_modified::LastModified; pub use self::preference::Preference; -//pub use self::range::{Range, ByteRangeSpec}; +pub use self::range::{ByteRangeSpec, Range}; /// Format writer ([`fmt::Write`]) for a [`BytesMut`]. #[derive(Debug, Default)] @@ -77,10 +77,12 @@ struct Writer { } impl Writer { + /// Constructs new bytes writer. pub fn new() -> Writer { Writer::default() } + /// Splits bytes out of writer, leaving writer buffer empty. pub fn take(&mut self) -> Bytes { self.buf.split().freeze() } diff --git a/src/http/header/range.rs b/src/http/header/range.rs index 11006ffff..c1d60f1ee 100644 --- a/src/http/header/range.rs +++ b/src/http/header/range.rs @@ -1,11 +1,12 @@ -// TODO: reinstate module - use std::{ - fmt::{self, Display}, + cmp, + fmt::{self, Display, Write}, str::FromStr, }; -use super::{parsing::from_one_raw_str, Header, Raw}; +use actix_http::{error::ParseError, header, HttpMessage}; + +use super::{Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// `Range` header, defined /// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1) @@ -16,7 +17,7 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// /// # ABNF /// ```plain -/// Range = byte-ranges-specifier / other-ranges-specifier +/// Range = byte-ranges-specifier / other-ranges-specifier /// other-ranges-specifier = other-range-unit "=" other-range-set /// other-range-set = 1*VCHAR /// @@ -25,13 +26,15 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// byte-ranges-specifier = bytes-unit "=" byte-range-set /// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) /// byte-range-spec = first-byte-pos "-" [last-byte-pos] +/// suffix-byte-range-spec = "-" suffix-length +/// suffix-length = 1*DIGIT /// first-byte-pos = 1*DIGIT /// last-byte-pos = 1*DIGIT /// ``` /// /// # Example Values /// * `bytes=1000-` -/// * `bytes=-2000` +/// * `bytes=-50` /// * `bytes=0-1,30-40` /// * `bytes=0-10,20-90,-100` /// * `custom_unit=0-123` @@ -39,81 +42,81 @@ use super::{parsing::from_one_raw_str, Header, Raw}; /// /// # Examples /// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; +/// use actix_web::http::header::{Range, ByteRangeSpec}; +/// use actix_web::HttpResponse; /// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] +/// let mut builder = HttpResponse::Ok(); +/// builder.insert_header(Range::Bytes( +/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::From(200)] /// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); +/// builder.insert_header(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); +/// builder.insert_header(Range::bytes(1, 100)); +/// builder.insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)])); /// ``` #[derive(PartialEq, Clone, Debug)] pub enum Range { - /// Byte range + /// Byte range. Bytes(Vec), - /// Custom range, with unit not registered at IANA + /// Custom range, with unit not registered at IANA. + /// /// (`other-range-unit`: String , `other-range-set`: String) Unregistered(String, String), } -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] +/// A range of bytes to fetch. +/// +/// Each [`Range::Bytes`] header can contain one or more `ByteRangeSpec`s. +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") + /// All bytes from `x` to `y`, inclusive. + /// + /// Serialized as `x-y`. + /// + /// Example: `bytes=500-999` would represent the second 500 bytes. FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), + /// All bytes starting from `x`, inclusive. + /// + /// Serialized as `x-`. + /// + /// Example: For a file of 1000 bytes, `bytes=950-` would represent bytes 950-999, inclusive. + From(u64), - /// Get last x bytes ("-x") + /// The last `y` bytes, inclusive. + /// + /// Using the spec terminology, this is `suffix-byte-range-spec`. Serialized as `-y`. + /// + /// Example: For a file of 1000 bytes, `bytes=-50` is equivalent to `bytes=950-`. Last(u64), } impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. + /// Given the full length of the entity, attempt to normalize the byte range into an satisfiable + /// end-inclusive `(from, to)` range. /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. + /// The resulting range is guaranteed to be a satisfiable range within the bounds + /// of `0 <= from <= to < full_length`. /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. + /// If the byte range is deemed unsatisfiable, `None` is returned. An unsatisfiable range is + /// generally cause for a server to either reject the client request with a + /// `416 Range Not Satisfiable` status code, or to simply ignore the range header and serve the + /// full entity using a `200 OK` status code. /// - /// This function closely follows [RFC 7233 §2.1]. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: + /// This function closely follows [RFC 7233 §2.1]. As such, it considers ranges to be + /// satisfiable if they meet the following conditions: /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. + /// > If a valid byte-range-set includes at least one byte-range-spec with a first-byte-pos that + /// is less than the current length of the representation, or at least one + /// suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set + /// is satisfiable. Otherwise, the byte-range-set is unsatisfiable. /// /// The function also computes remainder ranges based on the RFC: /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). + /// > If the last-byte-pos value is absent, or if the value is greater than or equal to the + /// current length of the representation data, the byte range is interpreted as the remainder + /// of the representation (i.e., the server replaces the value of last-byte-pos with a value + /// that is one less than the current length of the selected representation). /// /// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233 pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { @@ -121,26 +124,28 @@ impl ByteRangeSpec { if full_length == 0 { return None; } - match self { - &ByteRangeSpec::FromTo(from, to) => { + + match *self { + ByteRangeSpec::FromTo(from, to) => { if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) + Some((from, cmp::min(to, full_length - 1))) } else { None } } - &ByteRangeSpec::AllFrom(from) => { + + ByteRangeSpec::From(from) => { if from < full_length { Some((from, full_length - 1)) } else { None } } - &ByteRangeSpec::Last(last) => { + + ByteRangeSpec::Last(last) => { if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. + // From the RFC: If the selected representation is shorter than the specified + // suffix-length, the entire representation is used. if last > full_length { Some((0, full_length - 1)) } else { @@ -155,48 +160,53 @@ impl ByteRangeSpec { } impl Range { - /// Get the most common byte range header ("bytes=from-to") + /// Constructs a common byte range header. + /// + /// Eg: `bytes=from-to` pub fn bytes(from: u64, to: u64) -> Range { Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) } - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") + /// Constructs a byte range header with multiple subranges. + /// + /// Eg: `bytes=from1-to1,from2-to2,fromX-toX` pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { Range::Bytes( ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) + .into_iter() + .map(|(from, to)| ByteRangeSpec::FromTo(from, to)) .collect(), ) } } impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), + ByteRangeSpec::From(pos) => write!(f, "{}-", pos), } } } impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Range::Bytes(ranges) => { write!(f, "bytes=")?; for (i, range) in ranges.iter().enumerate() { if i != 0 { f.write_str(",")?; } + Display::fmt(range, f)?; } Ok(()) } - Range::Unregistered(ref unit, ref range_str) => { + + Range::Unregistered(unit, range_str) => { write!(f, "{}={}", unit, range_str) } } @@ -204,89 +214,118 @@ impl fmt::Display for Range { } impl FromStr for Range { - type Err = ::Error; + type Err = ParseError; - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); + fn from_str(s: &str) -> Result { + let (unit, val) = s.split_once('=').ok_or(ParseError::Header)?; - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { + match (unit, val) { + ("bytes", ranges) => { let ranges = from_comma_delimited(ranges); + if ranges.is_empty() { - return Err(::Error::Header); + return Err(ParseError::Header); } + Ok(Range::Bytes(ranges)) } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { - Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) - } - _ => Err(::Error::Header), + + (_, "") => Err(ParseError::Header), + ("", _) => Err(ParseError::Header), + + (unit, range_str) => Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())), } } } impl FromStr for ByteRangeSpec { - type Err = ::Error; + type Err = ParseError; - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); + fn from_str(s: &str) -> Result { + let (start, end) = s.split_once('-').ok_or(ParseError::Header)?; - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end + match (start, end) { + ("", end) => end .parse() - .or(Err(::Error::Header)) + .or(Err(ParseError::Header)) .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start + + (start, "") => start .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { + .or(Err(ParseError::Header)) + .map(ByteRangeSpec::From), + + (start, end) => match (start.parse(), end.parse()) { (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), - _ => Err(::Error::Header), + _ => Err(ParseError::Header), }, - _ => Err(::Error::Header), } } } impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME + fn name() -> HeaderName { + header::RANGE } - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) + #[inline] + fn parse(msg: &T) -> Result { + header::from_one_raw_str(msg.headers().get(&Self::name())) } +} - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) +impl IntoHeaderValue for Range { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + let mut wrt = Writer::new(); + let _ = write!(wrt, "{}", self); + HeaderValue::from_maybe_shared(wrt.take()) } } +/// Parses 0 or more items out of a comma delimited string, ignoring invalid items. +fn from_comma_delimited(s: &str) -> Vec { + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.parse().ok()) + .collect() +} + #[cfg(test)] mod tests { + use actix_http::{test::TestRequest, Request}; + use super::*; + fn req(s: &str) -> Request { + TestRequest::default() + .insert_header((header::RANGE, s)) + .finish() + } + #[test] fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100")).unwrap(); + let r2: Range = Header::parse(&req("bytes=1-100,-")).unwrap(); let r3 = Range::bytes(1, 100); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100,200-")).unwrap(); + let r2: Range = Header::parse(&req("bytes= 1-100 , 101-xxx, 200- ")).unwrap(); let r3 = Range::Bytes(vec![ ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), + ByteRangeSpec::From(200), ]); assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); + let r: Range = Header::parse(&req("bytes=1-100,-100")).unwrap(); + let r2: Range = Header::parse(&req("bytes=1-100, ,,-100")).unwrap(); let r3 = Range::Bytes(vec![ ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100), @@ -294,71 +333,65 @@ mod tests { assert_eq!(r, r2); assert_eq!(r2, r3); - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r: Range = Header::parse(&req("custom=1-100,-100")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); + let r: Range = Header::parse(&req("custom=abcd")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); assert_eq!(r, r2); - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); + let r: Range = Header::parse(&req("custom=xxx-yyy")).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); + let r: Result = Header::parse(&req("bytes=1-a,-")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); + let r: Result = Header::parse(&req("bytes=1-2-3")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"abc".into()); + let r: Result = Header::parse(&req("abc")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); + let r: Result = Header::parse(&req("bytes=1-100=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"bytes=".into()); + let r: Result = Header::parse(&req("bytes=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"custom=".into()); + let r: Result = Header::parse(&req("custom=")); assert_eq!(r.ok(), None); - let r: ::Result = Header::parse_header(&"=1-100".into()); + let r: Result = Header::parse(&req("=1-100")); assert_eq!(r.ok(), None); } #[test] fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ + let range = Range::Bytes(vec![ ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); + ByteRangeSpec::From(2000), + ]); + assert_eq!(&range.to_string(), "bytes=0-1000,2000-"); - headers.clear(); - headers.set(Range::Bytes(vec![])); + let range = Range::Bytes(vec![]); - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); + assert_eq!(&range.to_string(), "bytes="); - headers.clear(); - headers.set(Range::Unregistered("custom".to_owned(), "1-xxx".to_owned())); + let range = Range::Unregistered("custom".to_owned(), "1-xxx".to_owned()); - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); + assert_eq!(&range.to_string(), "custom=1-xxx"); } #[test] @@ -379,17 +412,11 @@ mod tests { assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)); + assert_eq!(Some((0, 2)), ByteRangeSpec::From(0).to_satisfiable_range(3)); + assert_eq!(Some((2, 2)), ByteRangeSpec::From(2).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(3).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(5).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::From(0).to_satisfiable_range(0)); assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3));