diff --git a/src/headers/constants.rs b/src/headers/constants.rs index 51622ebf..d88e66a6 100644 --- a/src/headers/constants.rs +++ b/src/headers/constants.rs @@ -128,6 +128,9 @@ pub const PROXY_AUTHENTICATE: HeaderName = HeaderName::from_lowercase_str("proxy /// The `Proxy-Authorization` Header pub const PROXY_AUTHORIZATION: HeaderName = HeaderName::from_lowercase_str("proxy-authorization"); +/// The `Range` Header +pub const RANGE: HeaderName = HeaderName::from_lowercase_str("range"); + /// The `Referer` Header pub const REFERER: HeaderName = HeaderName::from_lowercase_str("referer"); diff --git a/src/headers/mod.rs b/src/headers/mod.rs index 41cfbd15..ce217dd3 100644 --- a/src/headers/mod.rs +++ b/src/headers/mod.rs @@ -9,6 +9,7 @@ mod into_iter; mod iter; mod iter_mut; mod names; +pub mod range; mod to_header_values; mod values; diff --git a/src/headers/range.rs b/src/headers/range.rs new file mode 100644 index 00000000..a8ff765d --- /dev/null +++ b/src/headers/range.rs @@ -0,0 +1,596 @@ +//! Utilities for handling HTTP range request headers. +//! +//! The `http_types::headers::range` module contains a number of utilities +//! needed to handle HTTP range requests as defined in +//! [RFC 7233](https://tools.ietf.org/html/rfc7233). +//! +//! # Advertising range request support. +//! +//! ```no_run +//! # fn main() -> Result<(), http_types::Error> { +//! # +//! use http_types::{Response, StatusCode}; +//! use http_types::headers::{self, range::RangeHeader}; +//! +//! let mut resp = Response::new(StatusCode::Ok); +//! // Accept range requests with "bytes" unit. +//! resp.insert_header(headers::ACCEPT_RANGES, RangeHeader::UNIT); +//! // The ressource size is 6000 bytes. +//! resp.insert_header(headers::CONTENT_LENGTH, "6000"); +//! # +//! # Ok(()) } +//! ``` +//! +//! # Encoding a range request +//! +//! ```no_run +//! # fn main() -> Result<(), http_types::Error> { +//! # +//! use http_types::{Url, Method, Request}; +//! use http_types::headers::{self, range::RangeHeader}; +//! +//! let mut req = Request::new(Method::Get, Url::parse("https://example.com/video.mp4")?); +//! // range starts at 1000 bytes to the size of the ressource. +//! let range_header = RangeHeader::new(1000, None); +//! req.insert_header(headers::RANGE, range_header); +//! # +//! # Ok(()) } +//! ``` +//! +//! # Decoding a range request +//! +//! ```no_run +//! # fn main() -> Result<(), http_types::Error> { +//! use http_types::{Url, Method, Request, Response, StatusCode}; +//! use http_types::headers::{self, range::RangeHeader}; +//! use std::convert::TryFrom; +//! +//! let mut req = Request::new(Method::Get, Url::parse("https://example.com/video.mp4")?); +//! req.insert_header(headers::RANGE, "bytes=1000-"); +//! +//! // convert `HeaderValues` to `RangeHeader` with format validation, +//! // returning an error if it does not conform to RFC 7233. +//! let value = req.header(headers::RANGE).map(|v| v.last()).unwrap(); +//! let range_header = RangeHeader::try_from(value)?; +//! +//! // Validate and resolve range's bounds against a given size. +//! // returning an error if it does not conform to RFC 7233. +//! let range_set = range_header.resolve(6000)?; +//! +//! let range = range_set.first(); +//! assert_eq!(range.start(), 1000); +//! assert_eq!(range.end(), 5999); +//! assert_eq!(range.size(), 5000); +//! # Ok(()) } +//! ``` +//! +//! # Responding to a range request +//! +//! ```no_run +//! # fn main() -> Result<(), http_types::Error> { +//! # +//! use http_types::{Response, StatusCode, headers}; +//! use http_types::headers::range::{RangeHeader, RangeSet}; +//! +//! let mut resp = Response::new(StatusCode::PartialContent); +//! let range_headers = RangeHeader::new(1000, None); +//! let range_set = range_headers.resolve(6000)?; +//! // handling multipart range requests is out of +//! // the example's scope. +//! let range = range_set.first(); +//! resp.insert_header(headers::CONTENT_RANGE, range); +//! resp.insert_header(headers::CONTENT_LENGTH, range.size().to_string()); +//! # +//! # Ok(()) } +//! ``` + +use crate::headers::{HeaderValue, ToHeaderValues}; +use crate::StatusCode; +use std::str::FromStr; + +/// The `RangeHeader` struct allows encoding and decoding +/// of HTTP Range header. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct RangeHeader { + subs: Vec, +} + +impl RangeHeader { + /// This implementation only supports "bytes" + /// as a range request unit. + /// + /// This is the only unit officially specified + /// by [IANA](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#range-units), + /// and accepted by the large majority of HTTP clients and servers. + pub const UNIT: &'static str = "bytes"; + + /// Returns a new range header with a single sub-range. + pub fn new(start: T, end: U) -> Self + where + T: Into>, + U: Into>, + { + Self { + subs: vec![SubRangeHeader::new(start, end)], + } + } + + /// Adds a new sub-range to the end of the range header. + pub fn add(&mut self, start: T, end: U) + where + T: Into>, + U: Into>, + { + let p = SubRangeHeader::new(start, end); + self.subs.push(p); + } + + /// Validate range parts boundaries against a given size, + /// following [RFC 7233](https://tools.ietf.org/html/rfc7233#section-3), + /// and returns corresponding `RangeSet`. + /// + /// # Security + /// + /// This only performs the minimal validation specified by RFC 7233. + /// In particular it does not perform checks to mitigate + /// denial of service attacks, suck constraining the + /// order, counts and overlaps of multiple range items. + /// This is left as an implementation to the HTTP server. + /// + /// # Errors + /// + /// This will return a `StatusCode::RequestedRangeNotSatisfiable` + /// error if the range header does not conforms to RFC 7233. + /// + /// Resolving with a zero size is considered an error. The handling + /// of a zero sized content is left to the HTTP client or server + /// implementation. + pub fn resolve(&self, size: u64) -> crate::Result { + let fn_err = || { + crate::Error::from_str( + StatusCode::RequestedRangeNotSatisfiable, + "Range Not Satisfiable", + ) + }; + + let mut rs = RangeSet::new(size); + + if size == 0 { + return Err(fn_err()); + } + + for sub in &self.subs { + let mut r = Range::default(); + r.content_size = size; + match sub.start { + Some(start) => { + r.start = start; + r.end = sub.end.unwrap_or(size - 1); + if r.end > size - 1 { + r.end = size - 1; + } + } + None => { + let mut length = sub.end.ok_or_else(fn_err)?; + if length > size { + length = size; + } + r.start = size - length; + r.end = size - 1; + } + } + // None overlapping range are ignored + if r.start > r.end { + continue; + } + rs.ranges.push(r); + } + + if rs.ranges.is_empty() { + // if no ranges overlap + return Err(fn_err()); + } + + Ok(rs) + } +} + +impl std::convert::TryFrom<&HeaderValue> for RangeHeader { + type Error = crate::Error; + + /// Convert from a `HeaderValue`, returning a + /// `StatusCode::RequestedRangeNotSatisfiable` error + /// if the value is not compliant with the Range + /// header format specified by RFC 7233. + fn try_from(value: &HeaderValue) -> Result { + let err = crate::Error::from_str( + StatusCode::RequestedRangeNotSatisfiable, + "Range Not Satisfiable", + ); + + let unit: String = format!("{}=", RangeHeader::UNIT); + let value = value.as_str(); + let mut range_header = RangeHeader::default(); + + if !value.starts_with(&unit) { + return Err(err); + } + + let ranges_str: Vec<&str> = value + .trim_start_matches(&unit) + .split(',') + .map(|s| s.trim()) + .collect(); + + if ranges_str.is_empty() { + return Err(err); + } + + for sub_str in ranges_str { + let sub = SubRangeHeader::try_from(sub_str)?; + range_header.subs.push(sub); + } + + Ok(range_header) + } +} + +impl ToHeaderValues for RangeHeader { + type Iter = std::iter::Once; + + fn to_header_values(&self) -> crate::Result { + let mut value_str = format!("{}=", RangeHeader::UNIT); + for (i, sub) in self.subs.iter().enumerate() { + if i > 0 { + value_str.push_str(", "); + } + value_str.push_str(&sub.to_string()); + } + let value = HeaderValue::from_str(&value_str)?; + Ok(std::iter::once(value)) + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +struct SubRangeHeader { + start: Option, + end: Option, +} + +impl SubRangeHeader { + fn new(start: T, end: U) -> Self + where + T: Into>, + U: Into>, + { + Self { + start: start.into(), + end: end.into(), + } + } +} + +impl std::string::ToString for SubRangeHeader { + fn to_string(&self) -> String { + let mut val = String::new(); + if let Some(start) = self.start { + val.push_str(&start.to_string()); + } + val.push('-'); + if let Some(end) = self.end { + val.push_str(&end.to_string()); + } + val + } +} + +impl std::convert::TryFrom<&str> for SubRangeHeader { + type Error = crate::Error; + + fn try_from(s: &str) -> Result { + let fn_err = || { + crate::Error::from_str( + StatusCode::RequestedRangeNotSatisfiable, + "Range Not Satisfiable", + ) + }; + + let mut items = s.split('-'); + + let start_str = items.next().ok_or_else(fn_err)?; + let start = str_to_range_bound(start_str)?; + + let end_str = items.next().ok_or_else(fn_err)?; + let end = str_to_range_bound(end_str)?; + + if start.is_none() && end.is_none() { + return Err(fn_err()); + } + + Ok(SubRangeHeader::new(start, end)) + } +} + +fn str_to_range_bound(s: &str) -> crate::Result> { + if s.is_empty() { + return Ok(None); + } + let u = u64::from_str(s).map_err(|_| { + crate::Error::from_str( + StatusCode::RequestedRangeNotSatisfiable, + "Range Not Satisfiable", + ) + })?; + Ok(Some(u)) +} + +/// A set of `Range` compliant with [RFC 7233](https://tools.ietf.org/html/rfc7233#section-3). +/// +/// Generated by `RangeHeader::resolve`, a `RangeSet` always holds +/// a minimum of one `Range` item. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RangeSet { + ranges: Vec, + content_size: u64, +} + +impl RangeSet { + fn new(size: u64) -> Self { + RangeSet { + ranges: Vec::new(), + content_size: size, + } + } + + /// Returns the number of `Range` elements + /// + /// Size is always > 0. + pub fn size(&self) -> usize { + self.ranges.len() + } + + /// Returns an iterator over the range set. + pub fn iter<'a>(&'a self) -> impl Iterator + 'a { + self.ranges.iter().copied() + } + + /// Returns the first `Range` item. + pub fn first(&self) -> Range { + self.ranges[0] + } +} + +/// A `Range` holds the boundaries of a single range request. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Range { + start: u64, + end: u64, + content_size: u64, +} + +impl Range { + /// The range's start. + pub fn start(&self) -> u64 { + self.start + } + + /// The range's end. + pub fn end(&self) -> u64 { + self.end + } + + /// The range's size. + pub fn size(&self) -> u64 { + self.end - self.start + 1 + } + + /// The total size of the content to which the range applies. + pub fn content_size(&self) -> u64 { + self.content_size + } +} + +impl ToHeaderValues for Range { + type Iter = std::iter::Once; + + fn to_header_values(&self) -> crate::Result { + let value_str = format!( + "{} {}-{}/{}", + RangeHeader::UNIT, + self.start, + self.end, + self.content_size + ); + let value = HeaderValue::from_str(&value_str)?; + Ok(std::iter::once(value)) + } +} + +#[cfg(test)] +mod tests { + + use super::{Range, RangeHeader, RangeSet, SubRangeHeader}; + use crate::headers::{HeaderValue, ToHeaderValues}; + use std::convert::TryFrom; + use std::str::FromStr; + + macro_rules! test_try_from_header_value { + ($(($name:ident, $header:expr, $want:expr)),+) => { + $( + #[test] + fn $name() { + let value = HeaderValue::from_str($header).unwrap(); + let want = match $want.len() { + 0 => None, + _ => Some(RangeHeader { + subs: $want, + }) + }; + let got = RangeHeader::try_from(&value).ok(); + assert_eq!(want, got); + } + )* + }; + } + + test_try_from_header_value!( + ( + range_header_try_from_single_range, + "bytes=10-100", + vec![SubRangeHeader::new(10, 100)] + ), + ( + range_header_try_from_single_range_no_end, + "bytes=10-", + vec![SubRangeHeader::new(10, None)] + ), + ( + range_header_try_from_single_range_no_start, + "bytes=-100", + vec![SubRangeHeader::new(None, 100)] + ), + ( + range_header_try_from_single_range_no_start_no_end, + "bytes=-", + Vec::::new() + ), + ( + range_header_try_from_no_units, + "=10-100", + Vec::::new() + ), + ( + range_header_try_from_invalid_start, + "bytes=foo-100", + Vec::::new() + ), + ( + range_header_try_from_invalid_end, + "bytes=10-foo", + Vec::::new() + ), + ( + range_header_try_from_multi_range, + "bytes=10-100, 20-50, 30-, -80", + vec![ + SubRangeHeader::new(10, 100), + SubRangeHeader::new(20, 50), + SubRangeHeader::new(30, None), + SubRangeHeader::new(None, 80) + ] + ) + ); + + macro_rules! test_resolve { + ($(($name:ident, $header:expr, $size:expr, $want:expr)),+) => { + $( + #[test] + fn $name() { + let value = HeaderValue::from_str($header).unwrap(); + let range_header = RangeHeader::try_from(&value).unwrap(); + let want = match $want.len() { + 0 => None, + _ => Some(RangeSet { + ranges: $want, + content_size: $size, + }) + }; + let got = range_header.resolve($size).ok(); + assert_eq!(want, got); + } + )* + }; + } + + test_resolve!( + ( + header_range_resolve_single_range, + "bytes=10-100", + 1000, + vec![Range { + start: 10, + end: 100, + content_size: 1000 + }] + ), + ( + header_range_resolve_no_start, + "bytes=-100", + 1000, + vec![Range { + start: 900, + end: 999, + content_size: 1000 + }] + ), + ( + header_range_resolve_no_start_over_size, + "bytes=-9999", + 1000, + vec![Range { + start: 0, + end: 999, + content_size: 1000 + }] + ), + ( + header_range_resolve_no_start_under_size, + "bytes=-0", + 1000, + Vec::::new() + ), + ( + header_range_resolve_no_end, + "bytes=100-", + 1000, + vec![Range { + start: 100, + end: 999, + content_size: 1000 + }] + ), + ( + header_range_resolve_no_end_over_size, + "bytes=9999-", + 1000, + Vec::::new() + ), + ( + header_range_resolve_multi_range, + "bytes=10-100, 200-300", + 1000, + vec![ + Range { + start: 10, + end: 100, + content_size: 1000 + }, + Range { + start: 200, + end: 300, + content_size: 1000 + } + ] + ) + ); + + #[test] + fn range_header_to_header_value() { + let mut rh = RangeHeader::new(10, 100); + rh.add(200, None); + rh.add(None, 300); + let value = rh.to_header_values().unwrap().next().unwrap(); + assert_eq!("bytes=10-100, 200-, -300", value.as_str()) + } + + #[test] + fn range_to_header_value() { + let r = Range { + start: 10, + end: 100, + content_size: 1000, + }; + let value = r.to_header_values().unwrap().next().unwrap(); + assert_eq!("bytes 10-100/1000", value.as_str()) + } +}