update changelog

This commit is contained in:
Rob Ede 2021-12-04 23:34:53 +00:00
parent 2af600416f
commit c3b2d9118c
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
3 changed files with 120 additions and 97 deletions

View File

@ -4,6 +4,7 @@
### Added ### Added
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
* `AcceptEncoding` typed header. [#2482] * `AcceptEncoding` typed header. [#2482]
* `Range` typed header. [#2324]
* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `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] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
@ -17,6 +18,7 @@
* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468]
* Typed headers containing lists that require one or more items now enforce this minimum. [#2482] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
[#2324]: https://github.com/actix/actix-web/pull/2324
[#2468]: https://github.com/actix/actix-web/pull/2468 [#2468]: https://github.com/actix/actix-web/pull/2468
[#2480]: https://github.com/actix/actix-web/pull/2480 [#2480]: https://github.com/actix/actix-web/pull/2480
[#2482]: https://github.com/actix/actix-web/pull/2482 [#2482]: https://github.com/actix/actix-web/pull/2482

View File

@ -77,10 +77,12 @@ struct Writer {
} }
impl Writer { impl Writer {
/// Constructs new bytes writer.
pub fn new() -> Writer { pub fn new() -> Writer {
Writer::default() Writer::default()
} }
/// Splits bytes out of writer, leaving writer buffer empty.
pub fn take(&mut self) -> Bytes { pub fn take(&mut self) -> Bytes {
self.buf.split().freeze() self.buf.split().freeze()
} }

View File

@ -1,14 +1,12 @@
use std::{ use std::{
cmp,
fmt::{self, Display, Write}, fmt::{self, Display, Write},
str::FromStr, str::FromStr,
}; };
use actix_http::{error::ParseError, header, HttpMessage}; use actix_http::{error::ParseError, header, HttpMessage};
use super::{ use super::{Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue,
Raw, Writer,
};
/// `Range` header, defined /// `Range` header, defined
/// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1) /// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1)
@ -28,13 +26,15 @@ use super::{
/// byte-ranges-specifier = bytes-unit "=" byte-range-set /// byte-ranges-specifier = bytes-unit "=" byte-range-set
/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) /// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec)
/// byte-range-spec = first-byte-pos "-" [last-byte-pos] /// byte-range-spec = first-byte-pos "-" [last-byte-pos]
/// suffix-byte-range-spec = "-" suffix-length
/// suffix-length = 1*DIGIT
/// first-byte-pos = 1*DIGIT /// first-byte-pos = 1*DIGIT
/// last-byte-pos = 1*DIGIT /// last-byte-pos = 1*DIGIT
/// ``` /// ```
/// ///
/// # Example Values /// # Example Values
/// * `bytes=1000-` /// * `bytes=1000-`
/// * `bytes=-2000` /// * `bytes=-50`
/// * `bytes=0-1,30-40` /// * `bytes=0-1,30-40`
/// * `bytes=0-10,20-90,-100` /// * `bytes=0-10,20-90,-100`
/// * `custom_unit=0-123` /// * `custom_unit=0-123`
@ -47,73 +47,76 @@ use super::{
/// ///
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header(Range::Bytes( /// builder.insert_header(Range::Bytes(
/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] /// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::From(200)]
/// )); /// ));
/// builder.insert_header( /// builder.insert_header(Range::Unregistered("letters".to_owned(), "a-f".to_owned()));
/// 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)]));
/// builder.insert_header(
/// Range::bytes(1, 100)
/// );
/// builder.insert_header(
/// Range::bytes_multi(vec![(1, 100), (200, 300)])
/// );
/// ``` /// ```
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
pub enum Range { pub enum Range {
/// Byte range /// Byte range.
Bytes(Vec<ByteRangeSpec>), Bytes(Vec<ByteRangeSpec>),
/// Custom range, with unit not registered at IANA /// Custom range, with unit not registered at IANA.
///
/// (`other-range-unit`: String , `other-range-set`: String) /// (`other-range-unit`: String , `other-range-set`: String)
Unregistered(String, String), Unregistered(String, String),
} }
/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. /// A range of bytes to fetch.
/// Each `ByteRangeSpec` defines a range of bytes to fetch ///
#[derive(PartialEq, Clone, Debug)] /// Each [`Range::Bytes`] header can contain one or more `ByteRangeSpec`s.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ByteRangeSpec { 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), FromTo(u64, u64),
/// Get all bytes starting from x ("x-") /// All bytes starting from `x`, inclusive.
AllFrom(u64), ///
/// 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), Last(u64),
} }
impl ByteRangeSpec { impl ByteRangeSpec {
/// Given the full length of the entity, attempt to normalize the byte range /// Given the full length of the entity, attempt to normalize the byte range into an satisfiable
/// into an satisfiable end-inclusive (from, to) range. /// end-inclusive `(from, to)` range.
/// ///
/// The resulting range is guaranteed to be a satisfiable range within the /// The resulting range is guaranteed to be a satisfiable range within the bounds
/// bounds of `0 <= from <= to < full_length`. /// of `0 <= from <= to < full_length`.
/// ///
/// If the byte range is deemed unsatisfiable, `None` is returned. /// If the byte range is deemed unsatisfiable, `None` is returned. An unsatisfiable range is
/// An unsatisfiable range is generally cause for a server to either reject /// generally cause for a server to either reject the client request with a
/// the client request with a `416 Range Not Satisfiable` status code, or to /// `416 Range Not Satisfiable` status code, or to simply ignore the range header and serve the
/// simply ignore the range header and serve the full entity using a `200 /// full entity using a `200 OK` status code.
/// OK` status code.
/// ///
/// This function closely follows [RFC 7233 §2.1]. /// This function closely follows [RFC 7233 §2.1]. As such, it considers ranges to be
/// As such, it considers ranges to be satisfiable if they meet the /// satisfiable if they meet the following conditions:
/// following conditions:
/// ///
/// > If a valid byte-range-set includes at least one byte-range-spec with /// > If a valid byte-range-set includes at least one byte-range-spec with a first-byte-pos that
/// a first-byte-pos that is less than the current length of the /// is less than the current length of the representation, or at least one
/// representation, or at least one suffix-byte-range-spec with a /// suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set
/// non-zero suffix-length, then the byte-range-set is satisfiable. /// is satisfiable. Otherwise, the byte-range-set is unsatisfiable.
/// Otherwise, the byte-range-set is unsatisfiable.
/// ///
/// The function also computes remainder ranges based on the RFC: /// The function also computes remainder ranges based on the RFC:
/// ///
/// > If the last-byte-pos value is /// > If the last-byte-pos value is absent, or if the value is greater than or equal to the
/// absent, or if the value is greater than or equal to the current /// current length of the representation data, the byte range is interpreted as the remainder
/// length of the representation data, the byte range is interpreted as /// of the representation (i.e., the server replaces the value of last-byte-pos with a value
/// the remainder of the representation (i.e., the server replaces the /// that is one less than the current length of the selected representation).
/// 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 /// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233
pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> {
@ -121,26 +124,28 @@ impl ByteRangeSpec {
if full_length == 0 { if full_length == 0 {
return None; return None;
} }
match self {
&ByteRangeSpec::FromTo(from, to) => { match *self {
ByteRangeSpec::FromTo(from, to) => {
if from < full_length && 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 { } else {
None None
} }
} }
&ByteRangeSpec::AllFrom(from) => {
ByteRangeSpec::From(from) => {
if from < full_length { if from < full_length {
Some((from, full_length - 1)) Some((from, full_length - 1))
} else { } else {
None None
} }
} }
&ByteRangeSpec::Last(last) => {
ByteRangeSpec::Last(last) => {
if last > 0 { if last > 0 {
// From the RFC: If the selected representation is shorter // From the RFC: If the selected representation is shorter than the specified
// than the specified suffix-length, // suffix-length, the entire representation is used.
// the entire representation is used.
if last > full_length { if last > full_length {
Some((0, full_length - 1)) Some((0, full_length - 1))
} else { } else {
@ -155,18 +160,21 @@ impl ByteRangeSpec {
} }
impl Range { 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 { pub fn bytes(from: u64, to: u64) -> Range {
Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)])
} }
/// Get byte range header with multiple subranges /// Constructs a byte range header with multiple subranges.
/// ("bytes=from1-to1,from2-to2,fromX-toX") ///
/// Eg: `bytes=from1-to1,from2-to2,fromX-toX`
pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range {
Range::Bytes( Range::Bytes(
ranges ranges
.iter() .into_iter()
.map(|r| ByteRangeSpec::FromTo(r.0, r.1)) .map(|(from, to)| ByteRangeSpec::FromTo(from, to))
.collect(), .collect(),
) )
} }
@ -177,26 +185,28 @@ impl fmt::Display for ByteRangeSpec {
match *self { match *self {
ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to),
ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), ByteRangeSpec::Last(pos) => write!(f, "-{}", pos),
ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), ByteRangeSpec::From(pos) => write!(f, "{}-", pos),
} }
} }
} }
impl fmt::Display for Range { impl fmt::Display for Range {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match self {
Range::Bytes(ref ranges) => { Range::Bytes(ranges) => {
write!(f, "bytes=")?; write!(f, "bytes=")?;
for (i, range) in ranges.iter().enumerate() { for (i, range) in ranges.iter().enumerate() {
if i != 0 { if i != 0 {
f.write_str(",")?; f.write_str(",")?;
} }
Display::fmt(range, f)?; Display::fmt(range, f)?;
} }
Ok(()) Ok(())
} }
Range::Unregistered(ref unit, ref range_str) => {
Range::Unregistered(unit, range_str) => {
write!(f, "{}={}", unit, range_str) write!(f, "{}={}", unit, range_str)
} }
} }
@ -207,20 +217,23 @@ impl FromStr for Range {
type Err = ParseError; type Err = ParseError;
fn from_str(s: &str) -> Result<Range, ParseError> { fn from_str(s: &str) -> Result<Range, ParseError> {
let mut iter = s.splitn(2, '='); let (unit, val) = s.split_once('=').ok_or(ParseError::Header)?;
match (iter.next(), iter.next()) { match (unit, val) {
(Some("bytes"), Some(ranges)) => { ("bytes", ranges) => {
let ranges = from_comma_delimited(ranges); let ranges = from_comma_delimited(ranges);
if ranges.is_empty() { if ranges.is_empty() {
return Err(ParseError::Header); return Err(ParseError::Header);
} }
Ok(Range::Bytes(ranges)) Ok(Range::Bytes(ranges))
} }
(Some(unit), Some(range_str)) if unit != "" && range_str != "" => {
Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) (_, "") => Err(ParseError::Header),
} ("", _) => Err(ParseError::Header),
_ => Err(ParseError::Header),
(unit, range_str) => Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())),
} }
} }
} }
@ -229,22 +242,23 @@ impl FromStr for ByteRangeSpec {
type Err = ParseError; type Err = ParseError;
fn from_str(s: &str) -> Result<ByteRangeSpec, ParseError> { fn from_str(s: &str) -> Result<ByteRangeSpec, ParseError> {
let mut parts = s.splitn(2, '-'); let (start, end) = s.split_once('-').ok_or(ParseError::Header)?;
match (parts.next(), parts.next()) { match (start, end) {
(Some(""), Some(end)) => end ("", end) => end
.parse() .parse()
.or(Err(ParseError::Header)) .or(Err(ParseError::Header))
.map(ByteRangeSpec::Last), .map(ByteRangeSpec::Last),
(Some(start), Some("")) => start
(start, "") => start
.parse() .parse()
.or(Err(ParseError::Header)) .or(Err(ParseError::Header))
.map(ByteRangeSpec::AllFrom), .map(ByteRangeSpec::From),
(Some(start), Some(end)) => match (start.parse(), end.parse()) {
(start, end) => match (start.parse(), end.parse()) {
(Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)),
_ => Err(ParseError::Header), _ => Err(ParseError::Header),
}, },
_ => Err(ParseError::Header),
} }
} }
} }
@ -256,7 +270,7 @@ impl Header for Range {
#[inline] #[inline]
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> { fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> {
from_one_raw_str(msg.headers().get(&header::RANGE)) header::from_one_raw_str(msg.headers().get(&Self::name()))
} }
} }
@ -264,17 +278,28 @@ impl IntoHeaderValue for Range {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> { fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut wrt = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(wrt, "{}", self);
HeaderValue::from_maybe_shared(writer.take()) HeaderValue::from_maybe_shared(wrt.take())
} }
} }
/// Parses 0 or more items out of a comma delimited string, ignoring invalid items.
fn from_comma_delimited<T: FromStr>(s: &str) -> Vec<T> {
s.split(',')
.filter_map(|x| match x.trim() {
"" => None,
y => Some(y),
})
.filter_map(|x| x.parse().ok())
.collect()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_http::{test::TestRequest, Request};
use super::*; use super::*;
use actix_http::test::TestRequest;
use actix_http::Request;
fn req(s: &str) -> Request { fn req(s: &str) -> Request {
TestRequest::default() TestRequest::default()
@ -294,7 +319,7 @@ mod tests {
let r2: Range = Header::parse(&req("bytes= 1-100 , 101-xxx, 200- ")).unwrap(); let r2: Range = Header::parse(&req("bytes= 1-100 , 101-xxx, 200- ")).unwrap();
let r3 = Range::Bytes(vec![ let r3 = Range::Bytes(vec![
ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::FromTo(1, 100),
ByteRangeSpec::AllFrom(200), ByteRangeSpec::From(200),
]); ]);
assert_eq!(r, r2); assert_eq!(r, r2);
assert_eq!(r2, r3); assert_eq!(r2, r3);
@ -356,7 +381,7 @@ mod tests {
fn test_fmt() { fn test_fmt() {
let range = Range::Bytes(vec![ let range = Range::Bytes(vec![
ByteRangeSpec::FromTo(0, 1000), ByteRangeSpec::FromTo(0, 1000),
ByteRangeSpec::AllFrom(2000), ByteRangeSpec::From(2000),
]); ]);
assert_eq!(&range.to_string(), "bytes=0-1000,2000-"); assert_eq!(&range.to_string(), "bytes=0-1000,2000-");
@ -387,17 +412,11 @@ mod tests {
assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3));
assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0));
assert_eq!( assert_eq!(Some((0, 2)), ByteRangeSpec::From(0).to_satisfiable_range(3));
Some((0, 2)), assert_eq!(Some((2, 2)), ByteRangeSpec::From(2).to_satisfiable_range(3));
ByteRangeSpec::AllFrom(0).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!( assert_eq!(None, ByteRangeSpec::From(0).to_satisfiable_range(0));
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((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3));
assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3)); assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3));