From caa8b4f0a4d158a2e1933f3fc591d1a6cdd2814c Mon Sep 17 00:00:00 2001 From: Gleb Pomykalov Date: Fri, 1 Mar 2024 15:22:48 -0500 Subject: [PATCH 01/20] feat: add `HeaderMap::try_` methods to handle capacity overflow --- src/error.rs | 11 + src/header/map.rs | 574 ++++++++++++++++++++++++++++++++++++--------- src/header/mod.rs | 3 +- src/header/name.rs | 2 +- src/request.rs | 2 +- src/response.rs | 2 +- 6 files changed, 485 insertions(+), 109 deletions(-) diff --git a/src/error.rs b/src/error.rs index ba690841..762ee1c2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,7 @@ use std::fmt; use std::result; use crate::header; +use crate::header::MaxSizeReached; use crate::method; use crate::status; use crate::uri; @@ -27,6 +28,7 @@ enum ErrorKind { UriParts(uri::InvalidUriParts), HeaderName(header::InvalidHeaderName), HeaderValue(header::InvalidHeaderValue), + MaxSizeReached(MaxSizeReached), } impl fmt::Debug for Error { @@ -61,6 +63,7 @@ impl Error { UriParts(ref e) => e, HeaderName(ref e) => e, HeaderValue(ref e) => e, + MaxSizeReached(ref e) => e, } } } @@ -73,6 +76,14 @@ impl error::Error for Error { } } +impl From for Error { + fn from(err: MaxSizeReached) -> Error { + Error { + inner: ErrorKind::MaxSizeReached(err), + } + } +} + impl From for Error { fn from(err: status::InvalidStatusCode) -> Error { Error { diff --git a/src/header/map.rs b/src/header/map.rs index fa56bedc..e1960a0c 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -213,6 +213,11 @@ pub struct ValueDrain<'a, T> { lt: PhantomData<&'a mut HeaderMap>, } +/// Error returned when max capacity of `HeaderMap` is exceeded +pub struct MaxSizeReached { + _priv: (), +} + /// Tracks the value iterator state #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum Cursor { @@ -441,7 +446,7 @@ impl HeaderMap { /// assert_eq!(0, map.capacity()); /// ``` pub fn new() -> Self { - HeaderMap::with_capacity(0) + HeaderMap::try_with_capacity(0).unwrap() } } @@ -457,7 +462,7 @@ impl HeaderMap { /// /// # Panics /// - /// Requested capacity too large: would overflow `usize`. + /// This method panics if capacity exceeds max `HeaderMap` capacity. /// /// # Examples /// @@ -469,32 +474,57 @@ impl HeaderMap { /// assert_eq!(12, map.capacity()); /// ``` pub fn with_capacity(capacity: usize) -> HeaderMap { + Self::try_with_capacity(capacity).expect("size overflows MAX_SIZE") + } + + /// Create an empty `HeaderMap` with the specified capacity. + /// + /// The returned map will allocate internal storage in order to hold about + /// `capacity` elements without reallocating. However, this is a "best + /// effort" as there are usage patterns that could cause additional + /// allocations before `capacity` headers are stored in the map. + /// + /// More capacity than requested may be allocated. + /// + /// # Errors + /// + /// This function may return an error if `HeaderMap` exceeds max capacity + /// + /// # Examples + /// + /// ``` + /// # use http::HeaderMap; + /// let map: HeaderMap = HeaderMap::try_with_capacity(10).unwrap(); + /// + /// assert!(map.is_empty()); + /// assert_eq!(12, map.capacity()); + /// ``` + pub fn try_with_capacity(capacity: usize) -> Result, MaxSizeReached> { if capacity == 0 { - HeaderMap { + Ok(HeaderMap { mask: 0, indices: Box::new([]), // as a ZST, this doesn't actually allocate anything entries: Vec::new(), extra_values: Vec::new(), danger: Danger::Green, - } + }) } else { let raw_cap = match to_raw_capacity(capacity).checked_next_power_of_two() { Some(c) => c, - None => panic!( - "requested capacity {} too large: next power of two would overflow `usize`", - capacity - ), + None => return Err(MaxSizeReached { _priv: () }), }; - assert!(raw_cap <= MAX_SIZE, "requested capacity too large"); + if raw_cap > MAX_SIZE { + return Err(MaxSizeReached { _priv: () }); + } debug_assert!(raw_cap > 0); - HeaderMap { + Ok(HeaderMap { mask: (raw_cap - 1) as Size, indices: vec![Pos::none(); raw_cap].into_boxed_slice(), entries: Vec::with_capacity(raw_cap), extra_values: Vec::new(), danger: Danger::Green, - } + }) } } @@ -629,7 +659,7 @@ impl HeaderMap { /// /// # Panics /// - /// Panics if the new allocation size overflows `usize`. + /// Panics if the new allocation size overflows `HeaderMap` `MAX_SIZE`. /// /// # Examples /// @@ -641,27 +671,60 @@ impl HeaderMap { /// # map.insert(HOST, "bar".parse().unwrap()); /// ``` pub fn reserve(&mut self, additional: usize) { + self.try_reserve(additional) + .expect("size overflows MAX_SIZE") + } + + /// Reserves capacity for at least `additional` more headers to be inserted + /// into the `HeaderMap`. + /// + /// The header map may reserve more space to avoid frequent reallocations. + /// Like with `with_capacity`, this will be a "best effort" to avoid + /// allocations until `additional` more headers are inserted. Certain usage + /// patterns could cause additional allocations before the number is + /// reached. + /// + /// # Errors + /// + /// This method differs from `reserve` by returning an error instead of + /// panicking if the value is too large. + /// + /// # Examples + /// + /// ``` + /// # use http::HeaderMap; + /// # use http::header::HOST; + /// let mut map = HeaderMap::new(); + /// map.try_reserve(10).unwrap(); + /// # map.try_insert(HOST, "bar".parse().unwrap()).unwrap(); + /// ``` + pub fn try_reserve(&mut self, additional: usize) -> Result<(), MaxSizeReached> { // TODO: This can't overflow if done properly... since the max # of // elements is u16::MAX. let cap = self .entries .len() .checked_add(additional) - .expect("reserve overflow"); + .ok_or_else(|| MaxSizeReached::new())?; if cap > self.indices.len() { - let cap = cap.next_power_of_two(); - assert!(cap <= MAX_SIZE, "header map reserve over max capacity"); - assert!(cap != 0, "header map reserve overflowed"); + let cap = cap + .checked_next_power_of_two() + .ok_or_else(|| MaxSizeReached::new())?; + if cap > MAX_SIZE { + return Err(MaxSizeReached::new()); + } if self.entries.is_empty() { self.mask = cap as Size - 1; self.indices = vec![Pos::none(); cap].into_boxed_slice(); self.entries = Vec::with_capacity(usable_capacity(cap)); } else { - self.grow(cap); + self.try_grow(cap)?; } } + + Ok(()) } /// Returns a reference to the value associated with the key. @@ -1031,6 +1094,10 @@ impl HeaderMap { /// Gets the given key's corresponding entry in the map for in-place /// manipulation. /// + /// # Panics + /// + /// This method panics if capacity exceeds max `HeaderMap` capacity + /// /// # Examples /// /// ``` @@ -1056,7 +1123,7 @@ impl HeaderMap { where K: IntoHeaderName, { - key.entry(self) + key.try_entry(self).expect("size overflows MAX_SIZE") } /// Gets the given key's corresponding entry in the map for in-place @@ -1068,22 +1135,35 @@ impl HeaderMap { /// valid `HeaderName`s to passed as the key (such as `String`). If they /// do not parse as a valid `HeaderName`, this returns an /// `InvalidHeaderName` error. + /// + /// If reserving space goes over the maximum, this will also return an + /// error. However, to prevent breaking changes to the return type, the + /// error will still say `InvalidHeaderName`, unlike other `try_*` methods + /// which return a `MaxSizeReached` error. pub fn try_entry(&mut self, key: K) -> Result, InvalidHeaderName> where K: AsHeaderName, { - key.try_entry(self) + key.try_entry(self).map_err(|err| match err { + as_header_name::TryEntryError::InvalidHeaderName(e) => e, + as_header_name::TryEntryError::MaxSizeReached(_e) => { + // Unfortunately, we cannot change the return type of this + // method, so the max size reached error needs to be converted + // into an InvalidHeaderName. Yay. + InvalidHeaderName::new() + } + }) } - fn entry2(&mut self, key: K) -> Entry<'_, T> + fn try_entry2(&mut self, key: K) -> Result, MaxSizeReached> where K: Hash + Into, HeaderName: PartialEq, { // Ensure that there is space in the map - self.reserve_one(); + self.try_reserve_one()?; - insert_phase_one!( + Ok(insert_phase_one!( self, key, probe, @@ -1109,7 +1189,7 @@ impl HeaderMap { probe, danger, }) - ) + )) } /// Inserts a key-value pair into the map. @@ -1127,6 +1207,10 @@ impl HeaderMap { /// The key is not updated, though; this matters for types that can be `==` /// without being identical. /// + /// # Panics + /// + /// This method panics if capacity exceeds max `HeaderMap` capacity + /// /// # Examples /// /// ``` @@ -1143,18 +1227,56 @@ impl HeaderMap { where K: IntoHeaderName, { - key.insert(self, val) + self.try_insert(key, val).expect("size overflows MAX_SIZE") + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not previously have this key present, then `None` is + /// returned. + /// + /// If the map did have this key present, the new value is associated with + /// the key and all previous values are removed. **Note** that only a single + /// one of the previous values is returned. If there are multiple values + /// that have been previously associated with the key, then the first one is + /// returned. See `insert_mult` on `OccupiedEntry` for an API that returns + /// all values. + /// + /// The key is not updated, though; this matters for types that can be `==` + /// without being identical. + /// + /// # Errors + /// + /// This function may return an error if `HeaderMap` exceeds max capacity + /// + /// # Examples + /// + /// ``` + /// # use http::HeaderMap; + /// # use http::header::HOST; + /// let mut map = HeaderMap::new(); + /// assert!(map.try_insert(HOST, "world".parse().unwrap()).unwrap().is_none()); + /// assert!(!map.is_empty()); + /// + /// let mut prev = map.try_insert(HOST, "earth".parse().unwrap()).unwrap().unwrap(); + /// assert_eq!("world", prev); + /// ``` + pub fn try_insert(&mut self, key: K, val: T) -> Result, MaxSizeReached> + where + K: IntoHeaderName, + { + key.try_insert(self, val) } #[inline] - fn insert2(&mut self, key: K, value: T) -> Option + fn try_insert2(&mut self, key: K, value: T) -> Result, MaxSizeReached> where K: Hash + Into, HeaderName: PartialEq, { - self.reserve_one(); + self.try_reserve_one()?; - insert_phase_one!( + Ok(insert_phase_one!( self, key, probe, @@ -1165,7 +1287,7 @@ impl HeaderMap { { let _ = danger; // Make lint happy let index = self.entries.len(); - self.insert_entry(hash, key.into(), value); + self.try_insert_entry(hash, key.into(), value)?; self.indices[probe] = Pos::new(index, hash); None }, @@ -1173,10 +1295,10 @@ impl HeaderMap { Some(self.insert_occupied(pos, value)), // Robinhood { - self.insert_phase_two(key.into(), value, hash, probe, danger); + self.try_insert_phase_two(key.into(), value, hash, probe, danger)?; None } - ) + )) } /// Set an occupied bucket to the given value @@ -1224,6 +1346,10 @@ impl HeaderMap { /// updated, though; this matters for types that can be `==` without being /// identical. /// + /// # Panics + /// + /// This method panics if capacity exceeds max `HeaderMap` capacity + /// /// # Examples /// /// ``` @@ -1244,18 +1370,56 @@ impl HeaderMap { where K: IntoHeaderName, { - key.append(self, value) + self.try_append(key, value) + .expect("size overflows MAX_SIZE") + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not previously have this key present, then `false` is + /// returned. + /// + /// If the map did have this key present, the new value is pushed to the end + /// of the list of values currently associated with the key. The key is not + /// updated, though; this matters for types that can be `==` without being + /// identical. + /// + /// # Errors + /// + /// This function may return an error if `HeaderMap` exceeds max capacity + /// + /// # Examples + /// + /// ``` + /// # use http::HeaderMap; + /// # use http::header::HOST; + /// let mut map = HeaderMap::new(); + /// assert!(map.try_insert(HOST, "world".parse().unwrap()).unwrap().is_none()); + /// assert!(!map.is_empty()); + /// + /// map.try_append(HOST, "earth".parse().unwrap()).unwrap(); + /// + /// let values = map.get_all("host"); + /// let mut i = values.iter(); + /// assert_eq!("world", *i.next().unwrap()); + /// assert_eq!("earth", *i.next().unwrap()); + /// ``` + pub fn try_append(&mut self, key: K, value: T) -> Result + where + K: IntoHeaderName, + { + key.try_append(self, value) } #[inline] - fn append2(&mut self, key: K, value: T) -> bool + fn try_append2(&mut self, key: K, value: T) -> Result where K: Hash + Into, HeaderName: PartialEq, { - self.reserve_one(); + self.try_reserve_one()?; - insert_phase_one!( + Ok(insert_phase_one!( self, key, probe, @@ -1266,7 +1430,7 @@ impl HeaderMap { { let _ = danger; let index = self.entries.len(); - self.insert_entry(hash, key.into(), value); + self.try_insert_entry(hash, key.into(), value)?; self.indices[probe] = Pos::new(index, hash); false }, @@ -1277,11 +1441,11 @@ impl HeaderMap { }, // Robinhood { - self.insert_phase_two(key.into(), value, hash, probe, danger); + self.try_insert_phase_two(key.into(), value, hash, probe, danger)?; false } - ) + )) } #[inline] @@ -1317,17 +1481,17 @@ impl HeaderMap { /// phase 2 is post-insert where we forward-shift `Pos` in the indices. #[inline] - fn insert_phase_two( + fn try_insert_phase_two( &mut self, key: HeaderName, value: T, hash: HashValue, probe: usize, danger: bool, - ) -> usize { + ) -> Result { // Push the value and get the index let index = self.entries.len(); - self.insert_entry(hash, key, value); + self.try_insert_entry(hash, key, value)?; let num_displaced = do_insert_phase_two(&mut self.indices, probe, Pos::new(index, hash)); @@ -1336,7 +1500,7 @@ impl HeaderMap { self.danger.set_yellow(); } - index + Ok(index) } /// Removes a key from the map, returning the value associated with the key. @@ -1458,8 +1622,15 @@ impl HeaderMap { } #[inline] - fn insert_entry(&mut self, hash: HashValue, key: HeaderName, value: T) { - assert!(self.entries.len() < MAX_SIZE, "header map at capacity"); + fn try_insert_entry( + &mut self, + hash: HashValue, + key: HeaderName, + value: T, + ) -> Result<(), MaxSizeReached> { + if self.entries.len() >= MAX_SIZE { + return Err(MaxSizeReached::new()); + } self.entries.push(Bucket { hash, @@ -1467,6 +1638,8 @@ impl HeaderMap { value, links: None, }); + + Ok(()) } fn rebuild(&mut self) { @@ -1516,7 +1689,7 @@ impl HeaderMap { } } - fn reserve_one(&mut self) { + fn try_reserve_one(&mut self) -> Result<(), MaxSizeReached> { let len = self.entries.len(); if self.danger.is_yellow() { @@ -1530,7 +1703,7 @@ impl HeaderMap { let new_cap = self.indices.len() * 2; // Grow the capacity - self.grow(new_cap); + self.try_grow(new_cap)?; } else { self.danger.set_red(); @@ -1549,16 +1722,18 @@ impl HeaderMap { self.entries = Vec::with_capacity(usable_capacity(new_raw_cap)); } else { let raw_cap = self.indices.len(); - self.grow(raw_cap << 1); + self.try_grow(raw_cap << 1)?; } } + + Ok(()) } #[inline] - fn grow(&mut self, new_raw_cap: usize) { - assert!(new_raw_cap <= MAX_SIZE, "requested capacity too large"); - // This path can never be reached when handling the first allocation in - // the map. + fn try_grow(&mut self, new_raw_cap: usize) -> Result<(), MaxSizeReached> { + if new_raw_cap > MAX_SIZE { + return Err(MaxSizeReached::new()); + } // find first ideally placed element -- start of cluster let mut first_ideal = 0; @@ -1591,6 +1766,7 @@ impl HeaderMap { // Reserve additional entry slots let more = self.capacity() - self.entries.len(); self.entries.reserve_exact(more); + Ok(()) } #[inline] @@ -1914,7 +2090,7 @@ impl Extend<(Option, T)> for HeaderMap { }; 'outer: loop { - let mut entry = match self.entry2(key) { + let mut entry = match self.try_entry2(key).expect("size overflows MAX_SIZE") { Entry::Occupied(mut e) => { // Replace all previous values while maintaining a handle to // the entry. @@ -1988,7 +2164,7 @@ impl fmt::Debug for HeaderMap { impl Default for HeaderMap { fn default() -> Self { - HeaderMap::with_capacity(0) + HeaderMap::try_with_capacity(0).expect("zero capacity should never fail") } } @@ -2315,6 +2491,10 @@ impl<'a, T> Entry<'a, T> { /// /// Returns a mutable reference to the **first** value in the entry. /// + /// # Panics + /// + /// This method panics if capacity exceeds max `HeaderMap` capacity + /// /// # Examples /// /// ``` @@ -2338,11 +2518,47 @@ impl<'a, T> Entry<'a, T> { /// assert_eq!(map["x-hello"], 1); /// ``` pub fn or_insert(self, default: T) -> &'a mut T { + self.or_try_insert(default) + .expect("size overflows MAX_SIZE") + } + + /// Ensures a value is in the entry by inserting the default if empty. + /// + /// Returns a mutable reference to the **first** value in the entry. + /// + /// # Errors + /// + /// This function may return an error if `HeaderMap` exceeds max capacity + /// + /// # Examples + /// + /// ``` + /// # use http::HeaderMap; + /// let mut map: HeaderMap = HeaderMap::default(); + /// + /// let headers = &[ + /// "content-length", + /// "x-hello", + /// "Content-Length", + /// "x-world", + /// ]; + /// + /// for &header in headers { + /// let counter = map.entry(header) + /// .or_try_insert(0) + /// .unwrap(); + /// *counter += 1; + /// } + /// + /// assert_eq!(map["content-length"], 2); + /// assert_eq!(map["x-hello"], 1); + /// ``` + pub fn or_try_insert(self, default: T) -> Result<&'a mut T, MaxSizeReached> { use self::Entry::*; match self { - Occupied(e) => e.into_mut(), - Vacant(e) => e.insert(default), + Occupied(e) => Ok(e.into_mut()), + Vacant(e) => e.try_insert(default), } } @@ -2372,20 +2588,66 @@ impl<'a, T> Entry<'a, T> { /// # use http::HeaderMap; /// # use http::header::HOST; /// let mut map = HeaderMap::new(); - /// map.insert(HOST, "world".parse().unwrap()); + /// map.try_insert(HOST, "world".parse().unwrap()).unwrap(); /// - /// let res = map.entry("host") - /// .or_insert_with(|| unreachable!()); + /// let res = map.try_entry("host") + /// .unwrap() + /// .or_try_insert_with(|| unreachable!()) + /// .unwrap(); /// /// /// assert_eq!(res, "world"); /// ``` pub fn or_insert_with T>(self, default: F) -> &'a mut T { + self.or_try_insert_with(default) + .expect("size overflows MAX_SIZE") + } + + /// Ensures a value is in the entry by inserting the result of the default + /// function if empty. + /// + /// The default function is not called if the entry exists in the map. + /// Returns a mutable reference to the **first** value in the entry. + /// + /// # Examples + /// + /// Basic usage. + /// + /// ``` + /// # use http::HeaderMap; + /// let mut map = HeaderMap::new(); + /// + /// let res = map.entry("x-hello") + /// .or_insert_with(|| "world".parse().unwrap()); + /// + /// assert_eq!(res, "world"); + /// ``` + /// + /// The default function is not called if the entry exists in the map. + /// + /// ``` + /// # use http::HeaderMap; + /// # use http::header::HOST; + /// let mut map = HeaderMap::new(); + /// map.try_insert(HOST, "world".parse().unwrap()).unwrap(); + /// + /// let res = map.try_entry("host") + /// .unwrap() + /// .or_try_insert_with(|| unreachable!()) + /// .unwrap(); + /// + /// + /// assert_eq!(res, "world"); + /// ``` + pub fn or_try_insert_with T>( + self, + default: F, + ) -> Result<&'a mut T, MaxSizeReached> { use self::Entry::*; match self { - Occupied(e) => e.into_mut(), - Vacant(e) => e.insert(default()), + Occupied(e) => Ok(e.into_mut()), + Vacant(e) => e.try_insert(default()), } } @@ -2460,12 +2722,33 @@ impl<'a, T> VacantEntry<'a, T> { /// assert_eq!(map["x-hello"], "world"); /// ``` pub fn insert(self, value: T) -> &'a mut T { + self.try_insert(value).expect("size overflows MAX_SIZE") + } + + /// Insert the value into the entry. + /// + /// The value will be associated with this entry's key. A mutable reference + /// to the inserted value will be returned. + /// + /// # Examples + /// + /// ``` + /// # use http::header::{HeaderMap, Entry}; + /// let mut map = HeaderMap::new(); + /// + /// if let Entry::Vacant(v) = map.entry("x-hello") { + /// v.insert("world".parse().unwrap()); + /// } + /// + /// assert_eq!(map["x-hello"], "world"); + /// ``` + pub fn try_insert(self, value: T) -> Result<&'a mut T, MaxSizeReached> { // Ensure that there is space in the map - let index = self - .map - .insert_phase_two(self.key, value, self.hash, self.probe, self.danger); + let index = + self.map + .try_insert_phase_two(self.key, value, self.hash, self.probe, self.danger)?; - &mut self.map.entries[index].value + Ok(&mut self.map.entries[index].value) } /// Insert the value into the entry. @@ -2479,24 +2762,47 @@ impl<'a, T> VacantEntry<'a, T> { /// # use http::header::*; /// let mut map = HeaderMap::new(); /// - /// if let Entry::Vacant(v) = map.entry("x-hello") { - /// let mut e = v.insert_entry("world".parse().unwrap()); + /// if let Entry::Vacant(v) = map.try_entry("x-hello").unwrap() { + /// let mut e = v.try_insert_entry("world".parse().unwrap()).unwrap(); /// e.insert("world2".parse().unwrap()); /// } /// /// assert_eq!(map["x-hello"], "world2"); /// ``` pub fn insert_entry(self, value: T) -> OccupiedEntry<'a, T> { + self.try_insert_entry(value) + .expect("size overflows MAX_SIZE") + } + + /// Insert the value into the entry. + /// + /// The value will be associated with this entry's key. The new + /// `OccupiedEntry` is returned, allowing for further manipulation. + /// + /// # Examples + /// + /// ``` + /// # use http::header::*; + /// let mut map = HeaderMap::new(); + /// + /// if let Entry::Vacant(v) = map.try_entry("x-hello").unwrap() { + /// let mut e = v.try_insert_entry("world".parse().unwrap()).unwrap(); + /// e.insert("world2".parse().unwrap()); + /// } + /// + /// assert_eq!(map["x-hello"], "world2"); + /// ``` + pub fn try_insert_entry(self, value: T) -> Result, MaxSizeReached> { // Ensure that there is space in the map - let index = self - .map - .insert_phase_two(self.key, value, self.hash, self.probe, self.danger); + let index = + self.map + .try_insert_phase_two(self.key, value, self.hash, self.probe, self.danger)?; - OccupiedEntry { + Ok(OccupiedEntry { map: self.map, index, probe: self.probe, - } + }) } } @@ -3244,6 +3550,30 @@ impl Danger { } } +// ===== impl MaxSizeReached ===== + +impl MaxSizeReached { + fn new() -> Self { + MaxSizeReached { _priv: () } + } +} + +impl fmt::Debug for MaxSizeReached { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("MaxSizeReached") + // skip _priv noise + .finish() + } +} + +impl fmt::Display for MaxSizeReached { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("max size reached") + } +} + +impl std::error::Error for MaxSizeReached {} + // ===== impl Utils ===== #[inline] @@ -3306,7 +3636,7 @@ where */ mod into_header_name { - use super::{Entry, HdrName, HeaderMap, HeaderName}; + use super::{Entry, HdrName, HeaderMap, HeaderName, MaxSizeReached}; /// A marker trait used to identify values that can be used as insert keys /// to a `HeaderMap`. @@ -3322,31 +3652,36 @@ mod into_header_name { // without breaking any external crate. pub trait Sealed { #[doc(hidden)] - fn insert(self, map: &mut HeaderMap, val: T) -> Option; + fn try_insert(self, map: &mut HeaderMap, val: T) + -> Result, MaxSizeReached>; #[doc(hidden)] - fn append(self, map: &mut HeaderMap, val: T) -> bool; + fn try_append(self, map: &mut HeaderMap, val: T) -> Result; #[doc(hidden)] - fn entry(self, map: &mut HeaderMap) -> Entry<'_, T>; + fn try_entry(self, map: &mut HeaderMap) -> Result, MaxSizeReached>; } // ==== impls ==== impl Sealed for HeaderName { #[inline] - fn insert(self, map: &mut HeaderMap, val: T) -> Option { - map.insert2(self, val) + fn try_insert( + self, + map: &mut HeaderMap, + val: T, + ) -> Result, MaxSizeReached> { + map.try_insert2(self, val) } #[inline] - fn append(self, map: &mut HeaderMap, val: T) -> bool { - map.append2(self, val) + fn try_append(self, map: &mut HeaderMap, val: T) -> Result { + map.try_append2(self, val) } #[inline] - fn entry(self, map: &mut HeaderMap) -> Entry<'_, T> { - map.entry2(self) + fn try_entry(self, map: &mut HeaderMap) -> Result, MaxSizeReached> { + map.try_entry2(self) } } @@ -3354,17 +3689,21 @@ mod into_header_name { impl<'a> Sealed for &'a HeaderName { #[inline] - fn insert(self, map: &mut HeaderMap, val: T) -> Option { - map.insert2(self, val) + fn try_insert( + self, + map: &mut HeaderMap, + val: T, + ) -> Result, MaxSizeReached> { + map.try_insert2(self, val) } #[inline] - fn append(self, map: &mut HeaderMap, val: T) -> bool { - map.append2(self, val) + fn try_append(self, map: &mut HeaderMap, val: T) -> Result { + map.try_append2(self, val) } #[inline] - fn entry(self, map: &mut HeaderMap) -> Entry<'_, T> { - map.entry2(self) + fn try_entry(self, map: &mut HeaderMap) -> Result, MaxSizeReached> { + map.try_entry2(self) } } @@ -3372,17 +3711,21 @@ mod into_header_name { impl Sealed for &'static str { #[inline] - fn insert(self, map: &mut HeaderMap, val: T) -> Option { - HdrName::from_static(self, move |hdr| map.insert2(hdr, val)) + fn try_insert( + self, + map: &mut HeaderMap, + val: T, + ) -> Result, MaxSizeReached> { + HdrName::from_static(self, move |hdr| map.try_insert2(hdr, val)) } #[inline] - fn append(self, map: &mut HeaderMap, val: T) -> bool { - HdrName::from_static(self, move |hdr| map.append2(hdr, val)) + fn try_append(self, map: &mut HeaderMap, val: T) -> Result { + HdrName::from_static(self, move |hdr| map.try_append2(hdr, val)) } #[inline] - fn entry(self, map: &mut HeaderMap) -> Entry<'_, T> { - HdrName::from_static(self, move |hdr| map.entry2(hdr)) + fn try_entry(self, map: &mut HeaderMap) -> Result, MaxSizeReached> { + HdrName::from_static(self, move |hdr| map.try_entry2(hdr)) } } @@ -3390,12 +3733,31 @@ mod into_header_name { } mod as_header_name { - use super::{Entry, HdrName, HeaderMap, HeaderName, InvalidHeaderName}; + use super::{Entry, HdrName, HeaderMap, HeaderName, InvalidHeaderName, MaxSizeReached}; /// A marker trait used to identify values that can be used as search keys /// to a `HeaderMap`. pub trait AsHeaderName: Sealed {} + // Debug not currently needed, save on compiling it + #[allow(missing_debug_implementations)] + pub enum TryEntryError { + InvalidHeaderName(InvalidHeaderName), + MaxSizeReached(MaxSizeReached), + } + + impl From for TryEntryError { + fn from(e: InvalidHeaderName) -> TryEntryError { + TryEntryError::InvalidHeaderName(e) + } + } + + impl From for TryEntryError { + fn from(e: MaxSizeReached) -> TryEntryError { + TryEntryError::MaxSizeReached(e) + } + } + // All methods are on this pub(super) trait, instead of `AsHeaderName`, // so that they aren't publicly exposed to the world. // @@ -3406,7 +3768,7 @@ mod as_header_name { // without breaking any external crate. pub trait Sealed { #[doc(hidden)] - fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName>; + fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError>; #[doc(hidden)] fn find(&self, map: &HeaderMap) -> Option<(usize, usize)>; @@ -3419,8 +3781,8 @@ mod as_header_name { impl Sealed for HeaderName { #[inline] - fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { - Ok(map.entry2(self)) + fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { + Ok(map.try_entry2(self)?) } #[inline] @@ -3437,8 +3799,8 @@ mod as_header_name { impl<'a> Sealed for &'a HeaderName { #[inline] - fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { - Ok(map.entry2(self)) + fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { + Ok(map.try_entry2(self)?) } #[inline] @@ -3455,8 +3817,10 @@ mod as_header_name { impl<'a> Sealed for &'a str { #[inline] - fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { - HdrName::from_bytes(self.as_bytes(), move |hdr| map.entry2(hdr)) + fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { + Ok(HdrName::from_bytes(self.as_bytes(), move |hdr| { + map.try_entry2(hdr) + })??) } #[inline] @@ -3473,8 +3837,8 @@ mod as_header_name { impl Sealed for String { #[inline] - fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { - self.as_str().try_entry(map) + fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { + Ok(self.as_str().try_entry(map)?) } #[inline] @@ -3491,7 +3855,7 @@ mod as_header_name { impl<'a> Sealed for &'a String { #[inline] - fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { + fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { self.as_str().try_entry(map) } @@ -3531,7 +3895,7 @@ fn test_bounds() { #[test] fn skip_duplicates_during_key_iteration() { let mut map = HeaderMap::new(); - map.append("a", HeaderValue::from_static("a")); - map.append("a", HeaderValue::from_static("b")); + map.try_append("a", HeaderValue::from_static("a")).unwrap(); + map.try_append("a", HeaderValue::from_static("b")).unwrap(); assert_eq!(map.keys().count(), map.keys_len()); } diff --git a/src/header/mod.rs b/src/header/mod.rs index 0c55bffe..5d405767 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -76,7 +76,8 @@ mod value; pub use self::map::{ AsHeaderName, Drain, Entry, GetAll, HeaderMap, IntoHeaderName, IntoIter, Iter, IterMut, Keys, - OccupiedEntry, VacantEntry, ValueDrain, ValueIter, ValueIterMut, Values, ValuesMut, + MaxSizeReached, OccupiedEntry, VacantEntry, ValueDrain, ValueIter, ValueIterMut, Values, + ValuesMut, }; pub use self::name::{HeaderName, InvalidHeaderName}; pub use self::value::{HeaderValue, InvalidHeaderValue, ToStrError}; diff --git a/src/header/name.rs b/src/header/name.rs index e00e229d..6b7e0298 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1338,7 +1338,7 @@ impl fmt::Display for HeaderName { } impl InvalidHeaderName { - fn new() -> InvalidHeaderName { + pub(super) fn new() -> InvalidHeaderName { InvalidHeaderName { _priv: () } } } diff --git a/src/request.rs b/src/request.rs index 293c598c..d4c5bf54 100644 --- a/src/request.rs +++ b/src/request.rs @@ -908,7 +908,7 @@ impl Builder { self.and_then(move |mut head| { let name = >::try_from(key).map_err(Into::into)?; let value = >::try_from(value).map_err(Into::into)?; - head.headers.append(name, value); + head.headers.try_append(name, value)?; Ok(head) }) } diff --git a/src/response.rs b/src/response.rs index 0cc925ce..312cc5f8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -618,7 +618,7 @@ impl Builder { self.and_then(move |mut head| { let name = >::try_from(key).map_err(Into::into)?; let value = >::try_from(value).map_err(Into::into)?; - head.headers.append(name, value); + head.headers.try_append(name, value)?; Ok(head) }) } From 96dc52f513336285c572fe1509ab637261cfec9b Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Mon, 4 Mar 2024 11:06:28 -0500 Subject: [PATCH 02/20] fix: HeaderName::from_lowercase allowing NUL bytes in some cases If a byte slice larger than 64 bytes is passed to `HeaderName::from_lowercase`, it could allow NUL bytes. This fixes the bug. Reported-by: squirrel8@email.cz --- src/header/name.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/header/name.rs b/src/header/name.rs index 6b7e0298..64a276ae 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1176,9 +1176,9 @@ impl HeaderName { } Repr::Custom(MaybeLower { buf, lower: false }) => { for &b in buf.iter() { - // HEADER_CHARS maps all bytes that are not valid single-byte + // HEADER_CHARS_H2 maps all bytes that are not valid single-byte // UTF-8 to 0 so this check returns an error for invalid UTF-8. - if b != HEADER_CHARS[b as usize] { + if HEADER_CHARS_H2[b as usize] == 0 { return Err(InvalidHeaderName::new()); } } @@ -1904,4 +1904,16 @@ mod tests { fn test_all_tokens() { HeaderName::from_static("!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyz"); } + + #[test] + fn test_from_lowercase() { + HeaderName::from_lowercase(&[0; 10]).unwrap_err(); + HeaderName::from_lowercase(&[b'A'; 10]).unwrap_err(); + HeaderName::from_lowercase(&[0x1; 10]).unwrap_err(); + HeaderName::from_lowercase(&[0xFF; 10]).unwrap_err(); + //HeaderName::from_lowercase(&[0; 100]).unwrap_err(); + HeaderName::from_lowercase(&[b'A'; 100]).unwrap_err(); + HeaderName::from_lowercase(&[0x1; 100]).unwrap_err(); + HeaderName::from_lowercase(&[0xFF; 100]).unwrap_err(); + } } From 3fe7267b8c301dbe034749f0f31f48957ba61b3c Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Mon, 4 Mar 2024 11:40:53 -0500 Subject: [PATCH 03/20] v1.1.0 --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94aa956f..e189d618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.1.0 (March 4, 2024) + +* Add methods to allow trying to allocate in the `HeaderMap`, returning an error if oversize instead of panicking. +* Add `Extensions::get_or_insert()` method. +* Implement `From` for `uri::Builder`. +* Fix `HeaderName::from_lowercase` that could allow NUL bytes in some cases. + # 1.0.0 (November 15, 2023) - Implement `Clone` for `Request`, `Response`, and `Extensions`. This breaking change requires diff --git a/Cargo.toml b/Cargo.toml index 65f148ce..487507f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "http" # - Update html_root_url in lib.rs. # - Update CHANGELOG.md. # - Create git tag -version = "1.0.0" +version = "1.1.0" readme = "README.md" documentation = "https://docs.rs/http" repository = "https://github.com/hyperium/http" From ec9551152fc198208ce2fa9557418a0895ee2685 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Date: Wed, 15 Jun 2022 01:00:41 -0300 Subject: [PATCH 04/20] lib.rs intra-doc links --- src/lib.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3e8fd876..65d98721 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,12 @@ //! A general purpose library of common HTTP types //! //! This crate is a general purpose library for common types found when working -//! with the HTTP protocol. You'll find `Request` and `Response` types for +//! with the HTTP protocol. You'll find [`Request`] and [`Response`] types for //! working as either a client or a server as well as all of their components. -//! Notably you'll find `Uri` for what a `Request` is requesting, a `Method` -//! for how it's being requested, a `StatusCode` for what sort of response came -//! back, a `Version` for how this was communicated, and -//! `HeaderName`/`HeaderValue` definitions to get grouped in a `HeaderMap` to +//! Notably you'll find `Uri` for what a [`Request`] is requesting, a [`Method`] +//! for how it's being requested, a [`StatusCode`] for what sort of response came +//! back, a [`Version`] for how this was communicated, and +//! [`HeaderName`][header::HeaderName]/[`HeaderValue`][header::HeaderName] definitions to get grouped in a [`HeaderMap`] to //! work with request/response headers. //! //! You will notably *not* find an implementation of sending requests or @@ -19,11 +19,11 @@ //! //! ## Requests and Responses //! -//! Perhaps the main two types in this crate are the `Request` and `Response` -//! types. A `Request` could either be constructed to get sent off as a client -//! or it can also be received to generate a `Response` for a server. Similarly -//! as a client a `Response` is what you get after sending a `Request`, whereas -//! on a server you'll be manufacturing a `Response` to send back to the client. +//! Perhaps the main two types in this crate are the [`Request`] and [`Response`] +//! types. A [`Request`] could either be constructed to get sent off as a client +//! or it can also be received to generate a [`Response`] for a server. Similarly +//! as a client a [`Response`] is what you get after sending a [`Request`], whereas +//! on a server you'll be manufacturing a [`Response`] to send back to the client. //! //! Each type has a number of accessors for the component fields. For as a //! server you might want to inspect a requests URI to dispatch it: @@ -45,8 +45,8 @@ //! # fn not_found(_req: Request<()>) -> http::Result> { panic!() } //! ``` //! -//! On a `Request` you'll also find accessors like `method` to return a -//! `Method` and `headers` to inspect the various headers. A `Response` +//! On a [`Request`] you'll also find accessors like [`method`][Request::method] to return a +//! [`Method`] and [`headers`][Request::method] to inspect the various headers. A [`Response`] //! has similar methods for headers, the status code, etc. //! //! In addition to getters, request/response types also have mutable accessors @@ -64,7 +64,7 @@ //! ``` //! //! And finally, one of the most important aspects of requests/responses, the -//! body! The `Request` and `Response` types in this crate are *generic* in +//! body! The [`Request`] and [`Response`] types in this crate are *generic* in //! what their body is. This allows downstream libraries to use different //! representations such as `Request>`, `Response`, //! `Request, Error = _>>`, or even @@ -87,14 +87,14 @@ //! Accept: text/html //! ``` //! -//! Then `"Accept"` is a `HeaderName` while `"text/html"` is a `HeaderValue`. +//! Then `"Accept"` is a [`HeaderName`][header::HeaderName] while `"text/html"` is a [`HeaderValue`][header::HeaderValue]. //! Each of these is a dedicated type to allow for a number of interesting //! optimizations and to also encode the static guarantees of each type. For -//! example a `HeaderName` is always a valid `&str`, but a `HeaderValue` may +//! example a [`HeaderName`][header::HeaderName] is always a valid `&str`, but a [`HeaderValue`] may //! not be valid UTF-8. //! //! The most common header names are already defined for you as constant values -//! in the `header` module of this crate. For example: +//! in the [`header`] module of this crate. For example: //! //! ``` //! use http::header::{self, HeaderName}; @@ -112,7 +112,7 @@ //! assert_eq!(name, header::ACCEPT); //! ``` //! -//! Header values can be created from string literals through the `from_static` +//! Header values can be created from string literals through the [`from_static`][header::HeaderValue::from_static] //! function: //! //! ``` @@ -133,15 +133,15 @@ //! //! Most HTTP requests and responses tend to come with more than one header, so //! it's not too useful to just work with names and values only! This crate also -//! provides a `HeaderMap` type which is a specialized hash map for keys as -//! `HeaderName` and generic values. This type, like header names, is optimized +//! provides a [`HeaderMap`] type which is a specialized hash map for keys as +//! [`HeaderName`][header::HeaderName] and generic values. This type, like header names, is optimized //! for common usage but should continue to scale with your needs over time. //! //! # URIs //! -//! Each HTTP `Request` has an associated URI with it. This may just be a path +//! Each HTTP [`Request`] has an associated URI with it. This may just be a path //! like `/index.html` but it could also be an absolute URL such as -//! `https://www.rust-lang.org/index.html`. A `URI` has a number of accessors to +//! `https://www.rust-lang.org/index.html`. A [`URI`][uri::Uri] has a number of accessors to //! interpret it: //! //! ``` From c88f839212a25ed762b34cb52129e6ba5cd72816 Mon Sep 17 00:00:00 2001 From: Pi-Cla Date: Wed, 20 Mar 2024 15:38:32 +0000 Subject: [PATCH 05/20] Update StatusCode documentation to RFC 9110 (#690) --- src/status.rs | 122 +++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/src/status.rs b/src/status.rs index 1f619ee1..88c556e2 100644 --- a/src/status.rs +++ b/src/status.rs @@ -20,7 +20,7 @@ use std::fmt; use std::num::NonZeroU16; use std::str::FromStr; -/// An HTTP status code (`status-code` in RFC 7230 et al.). +/// An HTTP status code (`status-code` in RFC 9110 et al.). /// /// Constants are provided for known status codes, including those in the IANA /// [HTTP Status Code Registry]( @@ -326,195 +326,195 @@ macro_rules! status_codes { status_codes! { /// 100 Continue - /// [[RFC7231, Section 6.2.1](https://tools.ietf.org/html/rfc7231#section-6.2.1)] + /// [[RFC9110, Section 15.2.1](https://datatracker.ietf.org/doc/html/rfc9110#section-15.2.1)] (100, CONTINUE, "Continue"); /// 101 Switching Protocols - /// [[RFC7231, Section 6.2.2](https://tools.ietf.org/html/rfc7231#section-6.2.2)] + /// [[RFC9110, Section 15.2.2](https://datatracker.ietf.org/doc/html/rfc9110#section-15.2.2)] (101, SWITCHING_PROTOCOLS, "Switching Protocols"); /// 102 Processing - /// [[RFC2518](https://tools.ietf.org/html/rfc2518)] + /// [[RFC2518, Section 10.1](https://datatracker.ietf.org/doc/html/rfc2518#section-10.1)] (102, PROCESSING, "Processing"); /// 200 OK - /// [[RFC7231, Section 6.3.1](https://tools.ietf.org/html/rfc7231#section-6.3.1)] + /// [[RFC9110, Section 15.3.1](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.1)] (200, OK, "OK"); /// 201 Created - /// [[RFC7231, Section 6.3.2](https://tools.ietf.org/html/rfc7231#section-6.3.2)] + /// [[RFC9110, Section 15.3.2](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.2)] (201, CREATED, "Created"); /// 202 Accepted - /// [[RFC7231, Section 6.3.3](https://tools.ietf.org/html/rfc7231#section-6.3.3)] + /// [[RFC9110, Section 15.3.3](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.3)] (202, ACCEPTED, "Accepted"); /// 203 Non-Authoritative Information - /// [[RFC7231, Section 6.3.4](https://tools.ietf.org/html/rfc7231#section-6.3.4)] + /// [[RFC9110, Section 15.3.4](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.4)] (203, NON_AUTHORITATIVE_INFORMATION, "Non Authoritative Information"); /// 204 No Content - /// [[RFC7231, Section 6.3.5](https://tools.ietf.org/html/rfc7231#section-6.3.5)] + /// [[RFC9110, Section 15.3.5](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.5)] (204, NO_CONTENT, "No Content"); /// 205 Reset Content - /// [[RFC7231, Section 6.3.6](https://tools.ietf.org/html/rfc7231#section-6.3.6)] + /// [[RFC9110, Section 15.3.6](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.6)] (205, RESET_CONTENT, "Reset Content"); /// 206 Partial Content - /// [[RFC7233, Section 4.1](https://tools.ietf.org/html/rfc7233#section-4.1)] + /// [[RFC9110, Section 15.3.7](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.7)] (206, PARTIAL_CONTENT, "Partial Content"); /// 207 Multi-Status - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + /// [[RFC4918, Section 11.1](https://datatracker.ietf.org/doc/html/rfc4918#section-11.1)] (207, MULTI_STATUS, "Multi-Status"); /// 208 Already Reported - /// [[RFC5842](https://tools.ietf.org/html/rfc5842)] + /// [[RFC5842, Section 7.1](https://datatracker.ietf.org/doc/html/rfc5842#section-7.1)] (208, ALREADY_REPORTED, "Already Reported"); /// 226 IM Used - /// [[RFC3229](https://tools.ietf.org/html/rfc3229)] + /// [[RFC3229, Section 10.4.1](https://datatracker.ietf.org/doc/html/rfc3229#section-10.4.1)] (226, IM_USED, "IM Used"); /// 300 Multiple Choices - /// [[RFC7231, Section 6.4.1](https://tools.ietf.org/html/rfc7231#section-6.4.1)] + /// [[RFC9110, Section 15.4.1](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.1)] (300, MULTIPLE_CHOICES, "Multiple Choices"); /// 301 Moved Permanently - /// [[RFC7231, Section 6.4.2](https://tools.ietf.org/html/rfc7231#section-6.4.2)] + /// [[RFC9110, Section 15.4.2](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.2)] (301, MOVED_PERMANENTLY, "Moved Permanently"); /// 302 Found - /// [[RFC7231, Section 6.4.3](https://tools.ietf.org/html/rfc7231#section-6.4.3)] + /// [[RFC9110, Section 15.4.3](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.3)] (302, FOUND, "Found"); /// 303 See Other - /// [[RFC7231, Section 6.4.4](https://tools.ietf.org/html/rfc7231#section-6.4.4)] + /// [[RFC9110, Section 15.4.4](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.4)] (303, SEE_OTHER, "See Other"); /// 304 Not Modified - /// [[RFC7232, Section 4.1](https://tools.ietf.org/html/rfc7232#section-4.1)] + /// [[RFC9110, Section 15.4.5](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.5)] (304, NOT_MODIFIED, "Not Modified"); /// 305 Use Proxy - /// [[RFC7231, Section 6.4.5](https://tools.ietf.org/html/rfc7231#section-6.4.5)] + /// [[RFC9110, Section 15.4.6](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.6)] (305, USE_PROXY, "Use Proxy"); /// 307 Temporary Redirect - /// [[RFC7231, Section 6.4.7](https://tools.ietf.org/html/rfc7231#section-6.4.7)] + /// [[RFC9110, Section 15.4.7](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.7)] (307, TEMPORARY_REDIRECT, "Temporary Redirect"); /// 308 Permanent Redirect - /// [[RFC7238](https://tools.ietf.org/html/rfc7238)] + /// [[RFC9110, Section 15.4.8](https://datatracker.ietf.org/doc/html/rfc9110#section-15.4.8)] (308, PERMANENT_REDIRECT, "Permanent Redirect"); /// 400 Bad Request - /// [[RFC7231, Section 6.5.1](https://tools.ietf.org/html/rfc7231#section-6.5.1)] + /// [[RFC9110, Section 15.5.1](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.1)] (400, BAD_REQUEST, "Bad Request"); /// 401 Unauthorized - /// [[RFC7235, Section 3.1](https://tools.ietf.org/html/rfc7235#section-3.1)] + /// [[RFC9110, Section 15.5.2](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.2)] (401, UNAUTHORIZED, "Unauthorized"); /// 402 Payment Required - /// [[RFC7231, Section 6.5.2](https://tools.ietf.org/html/rfc7231#section-6.5.2)] + /// [[RFC9110, Section 15.5.3](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.3)] (402, PAYMENT_REQUIRED, "Payment Required"); /// 403 Forbidden - /// [[RFC7231, Section 6.5.3](https://tools.ietf.org/html/rfc7231#section-6.5.3)] + /// [[RFC9110, Section 15.5.4](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.4)] (403, FORBIDDEN, "Forbidden"); /// 404 Not Found - /// [[RFC7231, Section 6.5.4](https://tools.ietf.org/html/rfc7231#section-6.5.4)] + /// [[RFC9110, Section 15.5.5](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.5)] (404, NOT_FOUND, "Not Found"); /// 405 Method Not Allowed - /// [[RFC7231, Section 6.5.5](https://tools.ietf.org/html/rfc7231#section-6.5.5)] + /// [[RFC9110, Section 15.5.6](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.6)] (405, METHOD_NOT_ALLOWED, "Method Not Allowed"); /// 406 Not Acceptable - /// [[RFC7231, Section 6.5.6](https://tools.ietf.org/html/rfc7231#section-6.5.6)] + /// [[RFC9110, Section 15.5.7](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.7)] (406, NOT_ACCEPTABLE, "Not Acceptable"); /// 407 Proxy Authentication Required - /// [[RFC7235, Section 3.2](https://tools.ietf.org/html/rfc7235#section-3.2)] + /// [[RFC9110, Section 15.5.8](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.8)] (407, PROXY_AUTHENTICATION_REQUIRED, "Proxy Authentication Required"); /// 408 Request Timeout - /// [[RFC7231, Section 6.5.7](https://tools.ietf.org/html/rfc7231#section-6.5.7)] + /// [[RFC9110, Section 15.5.9](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.9)] (408, REQUEST_TIMEOUT, "Request Timeout"); /// 409 Conflict - /// [[RFC7231, Section 6.5.8](https://tools.ietf.org/html/rfc7231#section-6.5.8)] + /// [[RFC9110, Section 15.5.10](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.10)] (409, CONFLICT, "Conflict"); /// 410 Gone - /// [[RFC7231, Section 6.5.9](https://tools.ietf.org/html/rfc7231#section-6.5.9)] + /// [[RFC9110, Section 15.5.11](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.11)] (410, GONE, "Gone"); /// 411 Length Required - /// [[RFC7231, Section 6.5.10](https://tools.ietf.org/html/rfc7231#section-6.5.10)] + /// [[RFC9110, Section 15.5.12](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.12)] (411, LENGTH_REQUIRED, "Length Required"); /// 412 Precondition Failed - /// [[RFC7232, Section 4.2](https://tools.ietf.org/html/rfc7232#section-4.2)] + /// [[RFC9110, Section 15.5.13](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.13)] (412, PRECONDITION_FAILED, "Precondition Failed"); /// 413 Payload Too Large - /// [[RFC7231, Section 6.5.11](https://tools.ietf.org/html/rfc7231#section-6.5.11)] + /// [[RFC9110, Section 15.5.14](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.14)] (413, PAYLOAD_TOO_LARGE, "Payload Too Large"); /// 414 URI Too Long - /// [[RFC7231, Section 6.5.12](https://tools.ietf.org/html/rfc7231#section-6.5.12)] + /// [[RFC9110, Section 15.5.15](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.15)] (414, URI_TOO_LONG, "URI Too Long"); /// 415 Unsupported Media Type - /// [[RFC7231, Section 6.5.13](https://tools.ietf.org/html/rfc7231#section-6.5.13)] + /// [[RFC9110, Section 15.5.16](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.16)] (415, UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"); /// 416 Range Not Satisfiable - /// [[RFC7233, Section 4.4](https://tools.ietf.org/html/rfc7233#section-4.4)] + /// [[RFC9110, Section 15.5.17](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.17)] (416, RANGE_NOT_SATISFIABLE, "Range Not Satisfiable"); /// 417 Expectation Failed - /// [[RFC7231, Section 6.5.14](https://tools.ietf.org/html/rfc7231#section-6.5.14)] + /// [[RFC9110, Section 15.5.18](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.18)] (417, EXPECTATION_FAILED, "Expectation Failed"); /// 418 I'm a teapot - /// [curiously not registered by IANA but [RFC2324](https://tools.ietf.org/html/rfc2324)] + /// [curiously not registered by IANA but [RFC2324, Section 2.3.2](https://datatracker.ietf.org/doc/html/rfc2324#section-2.3.2)] (418, IM_A_TEAPOT, "I'm a teapot"); /// 421 Misdirected Request - /// [RFC7540, Section 9.1.2](https://tools.ietf.org/html/rfc7540#section-9.1.2) + /// [[RFC9110, Section 15.5.20](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.20)] (421, MISDIRECTED_REQUEST, "Misdirected Request"); /// 422 Unprocessable Entity - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + /// [[RFC9110, Section 15.5.21](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.21)] (422, UNPROCESSABLE_ENTITY, "Unprocessable Entity"); /// 423 Locked - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + /// [[RFC4918, Section 11.3](https://datatracker.ietf.org/doc/html/rfc4918#section-11.3)] (423, LOCKED, "Locked"); /// 424 Failed Dependency - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + /// [[RFC4918, Section 11.4](https://tools.ietf.org/html/rfc4918#section-11.4)] (424, FAILED_DEPENDENCY, "Failed Dependency"); /// 426 Upgrade Required - /// [[RFC7231, Section 6.5.15](https://tools.ietf.org/html/rfc7231#section-6.5.15)] + /// [[RFC9110, Section 15.5.22](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.22)] (426, UPGRADE_REQUIRED, "Upgrade Required"); /// 428 Precondition Required - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + /// [[RFC6585, Section 3](https://datatracker.ietf.org/doc/html/rfc6585#section-3)] (428, PRECONDITION_REQUIRED, "Precondition Required"); /// 429 Too Many Requests - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + /// [[RFC6585, Section 4](https://datatracker.ietf.org/doc/html/rfc6585#section-4)] (429, TOO_MANY_REQUESTS, "Too Many Requests"); /// 431 Request Header Fields Too Large - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + /// [[RFC6585, Section 5](https://datatracker.ietf.org/doc/html/rfc6585#section-5)] (431, REQUEST_HEADER_FIELDS_TOO_LARGE, "Request Header Fields Too Large"); /// 451 Unavailable For Legal Reasons - /// [[RFC7725](https://tools.ietf.org/html/rfc7725)] + /// [[RFC7725, Section 3](https://tools.ietf.org/html/rfc7725#section-3)] (451, UNAVAILABLE_FOR_LEGAL_REASONS, "Unavailable For Legal Reasons"); /// 500 Internal Server Error - /// [[RFC7231, Section 6.6.1](https://tools.ietf.org/html/rfc7231#section-6.6.1)] + /// [[RFC9110, Section 15.6.1](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.1)] (500, INTERNAL_SERVER_ERROR, "Internal Server Error"); /// 501 Not Implemented - /// [[RFC7231, Section 6.6.2](https://tools.ietf.org/html/rfc7231#section-6.6.2)] + /// [[RFC9110, Section 15.6.2](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.2)] (501, NOT_IMPLEMENTED, "Not Implemented"); /// 502 Bad Gateway - /// [[RFC7231, Section 6.6.3](https://tools.ietf.org/html/rfc7231#section-6.6.3)] + /// [[RFC9110, Section 15.6.3](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.3)] (502, BAD_GATEWAY, "Bad Gateway"); /// 503 Service Unavailable - /// [[RFC7231, Section 6.6.4](https://tools.ietf.org/html/rfc7231#section-6.6.4)] + /// [[RFC9110, Section 15.6.4](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.4)] (503, SERVICE_UNAVAILABLE, "Service Unavailable"); /// 504 Gateway Timeout - /// [[RFC7231, Section 6.6.5](https://tools.ietf.org/html/rfc7231#section-6.6.5)] + /// [[RFC9110, Section 15.6.5](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.5)] (504, GATEWAY_TIMEOUT, "Gateway Timeout"); /// 505 HTTP Version Not Supported - /// [[RFC7231, Section 6.6.6](https://tools.ietf.org/html/rfc7231#section-6.6.6)] + /// [[RFC9110, Section 15.6.6](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.6)] (505, HTTP_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported"); /// 506 Variant Also Negotiates - /// [[RFC2295](https://tools.ietf.org/html/rfc2295)] + /// [[RFC2295, Section 8.1](https://datatracker.ietf.org/doc/html/rfc2295#section-8.1)] (506, VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates"); /// 507 Insufficient Storage - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + /// [[RFC4918, Section 11.5](https://datatracker.ietf.org/doc/html/rfc4918#section-11.5)] (507, INSUFFICIENT_STORAGE, "Insufficient Storage"); /// 508 Loop Detected - /// [[RFC5842](https://tools.ietf.org/html/rfc5842)] + /// [[RFC5842, Section 7.2](https://datatracker.ietf.org/doc/html/rfc5842#section-7.2)] (508, LOOP_DETECTED, "Loop Detected"); /// 510 Not Extended - /// [[RFC2774](https://tools.ietf.org/html/rfc2774)] + /// [[RFC2774, Section 7](https://datatracker.ietf.org/doc/html/rfc2774#section-7)] (510, NOT_EXTENDED, "Not Extended"); /// 511 Network Authentication Required - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + /// [[RFC6585, Section 6](https://datatracker.ietf.org/doc/html/rfc6585#section-6)] (511, NETWORK_AUTHENTICATION_REQUIRED, "Network Authentication Required"); } From 3f0b7de0d0788d28169edf59e2fcd31bb3713019 Mon Sep 17 00:00:00 2001 From: tottoto Date: Sat, 4 May 2024 11:26:18 +0900 Subject: [PATCH 06/20] refactor(lib): replace deprecated numeric api with primitive one --- src/header/map.rs | 2 +- src/uri/mod.rs | 2 +- src/uri/path.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/header/map.rs b/src/header/map.rs index e1960a0c..86255777 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -1066,7 +1066,7 @@ impl HeaderMap { } else { ValueIter { map: self, - index: ::std::usize::MAX, + index: usize::MAX, front: None, back: None, } diff --git a/src/uri/mod.rs b/src/uri/mod.rs index 6ef2dc8e..767f0743 100644 --- a/src/uri/mod.rs +++ b/src/uri/mod.rs @@ -28,9 +28,9 @@ use std::convert::TryFrom; use bytes::Bytes; use std::error::Error; +use std::fmt; use std::hash::{Hash, Hasher}; use std::str::{self, FromStr}; -use std::{fmt, u16, u8}; use self::scheme::Scheme2; diff --git a/src/uri/path.rs b/src/uri/path.rs index 341ba2e6..6a2f1e1e 100644 --- a/src/uri/path.rs +++ b/src/uri/path.rs @@ -14,7 +14,7 @@ pub struct PathAndQuery { pub(super) query: u16, } -const NONE: u16 = ::std::u16::MAX; +const NONE: u16 = u16::MAX; impl PathAndQuery { // Not public while `bytes` is unstable. From 7fca4d9216973ebba65cc638514868216475673c Mon Sep 17 00:00:00 2001 From: tottoto Date: Sat, 4 May 2024 11:27:59 +0900 Subject: [PATCH 07/20] refactor(header): refactor redundant closure style --- src/header/map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/header/map.rs b/src/header/map.rs index 86255777..ee989ffe 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -705,12 +705,12 @@ impl HeaderMap { .entries .len() .checked_add(additional) - .ok_or_else(|| MaxSizeReached::new())?; + .ok_or_else(MaxSizeReached::new)?; if cap > self.indices.len() { let cap = cap .checked_next_power_of_two() - .ok_or_else(|| MaxSizeReached::new())?; + .ok_or_else(MaxSizeReached::new)?; if cap > MAX_SIZE { return Err(MaxSizeReached::new()); } From c97f7ddc898bb749560eaeffd7dde9130f1d0d70 Mon Sep 17 00:00:00 2001 From: tottoto Date: Sat, 4 May 2024 11:29:15 +0900 Subject: [PATCH 08/20] refactor(header): remove redundant result handling --- src/header/map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/header/map.rs b/src/header/map.rs index ee989ffe..0b5beb3d 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -3838,7 +3838,7 @@ mod as_header_name { impl Sealed for String { #[inline] fn try_entry(self, map: &mut HeaderMap) -> Result, TryEntryError> { - Ok(self.as_str().try_entry(map)?) + self.as_str().try_entry(map) } #[inline] From 0636a4290f37d086f1a1c9618222736f4fb04c41 Mon Sep 17 00:00:00 2001 From: tottoto Date: Sat, 4 May 2024 11:31:26 +0900 Subject: [PATCH 09/20] refactor(header): remove multiple bound location --- src/header/map.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/header/map.rs b/src/header/map.rs index 0b5beb3d..a668b311 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -1449,9 +1449,9 @@ impl HeaderMap { } #[inline] - fn find(&self, key: &K) -> Option<(usize, usize)> + fn find(&self, key: &K) -> Option<(usize, usize)> where - K: Hash + Into, + K: Hash + Into + ?Sized, HeaderName: PartialEq, { if self.entries.is_empty() { @@ -3603,9 +3603,9 @@ fn probe_distance(mask: Size, hash: HashValue, current: usize) -> usize { current.wrapping_sub(desired_pos(mask, hash)) & mask as usize } -fn hash_elem_using(danger: &Danger, k: &K) -> HashValue +fn hash_elem_using(danger: &Danger, k: &K) -> HashValue where - K: Hash, + K: Hash + ?Sized, { use fnv::FnvHasher; From f4e8c0c3159b18b8b7e4a6a3762c55528483d25a Mon Sep 17 00:00:00 2001 From: tottoto Date: Sat, 4 May 2024 11:34:01 +0900 Subject: [PATCH 10/20] refactor(header): allow clippy::out_of_bounds_indexing to panic in const context workaround --- src/header/name.rs | 2 +- src/header/value.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/header/name.rs b/src/header/name.rs index 64a276ae..41320262 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1274,7 +1274,7 @@ impl HeaderName { // https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html#panic-in-const-contexts // // See the panics section of this method's document for details. - #[allow(clippy::no_effect)] + #[allow(clippy::no_effect, clippy::out_of_bounds_indexing)] ([] as [u8; 0])[0]; // Invalid header name } diff --git a/src/header/value.rs b/src/header/value.rs index b7978cac..4813f6fd 100644 --- a/src/header/value.rs +++ b/src/header/value.rs @@ -91,7 +91,7 @@ impl HeaderValue { // https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html#panic-in-const-contexts // // See the panics section of this method's document for details. - #[allow(clippy::no_effect)] + #[allow(clippy::no_effect, clippy::out_of_bounds_indexing)] ([] as [u8; 0])[0]; // Invalid header value } i += 1; From af5df31324ae701234b9db19f1bc378cdeb61662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20S=C3=A1nchez-Aedo?= <145811200+franfastly@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:07:42 +0100 Subject: [PATCH 11/20] fix: Allow characters `#`, `$`, `%`, `&`, `'` in ` Method` (#713) * Allow characters `#`, `$`, `%,` `&,` `'` in HTTP method * Update method character docomentation to RFC 9110 --- src/method.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/method.rs b/src/method.rs index 94e4d4ec..3d45ef9f 100644 --- a/src/method.rs +++ b/src/method.rs @@ -356,7 +356,7 @@ mod extension { } } - // From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can + // From the RFC 9110 HTTP Semantics, section 9.1, the HTTP method is case-sensitive and can // contain the following characters: // // ``` @@ -366,7 +366,7 @@ mod extension { // "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA // ``` // - // https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method + // https://datatracker.ietf.org/doc/html/rfc9110#section-9.1 // // Note that this definition means that any &[u8] that consists solely of valid // characters is also valid UTF-8 because the valid method characters are a @@ -377,7 +377,7 @@ mod extension { b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // x b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 1x b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 2x - b'\0', b'\0', b'\0', b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 3x + b'\0', b'\0', b'\0', b'!', b'\0', b'#', b'$', b'%', b'&', b'\'', // 3x b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', // 4x b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', // 5x b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', // 6x @@ -466,4 +466,20 @@ mod test { let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely."; assert_eq!(Method::from_str(long_method).unwrap(), long_method); } + + #[test] + fn test_extension_method_chars() { + const VALID_METHOD_CHARS: &str = + "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + for c in VALID_METHOD_CHARS.chars() { + let c = c.to_string(); + + assert_eq!( + Method::from_str(&c).unwrap(), + c.as_str(), + "testing {c} is a valid method character" + ); + } + } } From de89c5a2d964293b17d7c1b36570a8fd04e9d046 Mon Sep 17 00:00:00 2001 From: Wyatt Herkamp Date: Thu, 8 Aug 2024 13:19:37 -0400 Subject: [PATCH 12/20] docs: Correct documentation for the last-modified http header. (#714) --- src/header/name.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/header/name.rs b/src/header/name.rs index 41320262..ac46a207 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -676,7 +676,10 @@ standard_headers! { /// document. (IfUnmodifiedSince, IF_UNMODIFIED_SINCE, b"if-unmodified-since"); - /// Content-Types that are acceptable for the response. + /// The Last-Modified header contains the date and time when the origin believes + /// the resource was last modified. + /// + /// The value is a valid Date/Time string defined in [RFC9910](https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.7) (LastModified, LAST_MODIFIED, b"last-modified"); /// Allows the server to point an interested client to another resource From 308104b9f68f513dc68e5900dae24118e10c650b Mon Sep 17 00:00:00 2001 From: Chen Hongzhi Date: Wed, 18 Sep 2024 03:23:36 +0800 Subject: [PATCH 13/20] fix: Remove double quote from header name validation as per spec (#716) --- src/header/name.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/header/name.rs b/src/header/name.rs index ac46a207..09af1da1 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1011,7 +1011,7 @@ const HEADER_CHARS: [u8; 256] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x - 0, 0, 0, b'!', b'"', b'#', b'$', b'%', b'&', b'\'', // 3x + 0, 0, 0, b'!', 0, b'#', b'$', b'%', b'&', b'\'', // 3x 0, 0, b'*', b'+', 0, b'-', b'.', 0, b'0', b'1', // 4x b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, 0, // 5x 0, 0, 0, 0, 0, b'a', b'b', b'c', b'd', b'e', // 6x From 761d36acb069ed335d2f9dfd7a568b8735ec7fec Mon Sep 17 00:00:00 2001 From: 39zde <168634234+39zde@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:22:51 +0000 Subject: [PATCH 14/20] docs: fix typos (#718) --- src/byte_str.rs | 2 +- src/extensions.rs | 2 +- src/header/name.rs | 6 +++--- src/uri/authority.rs | 2 +- src/uri/scheme.rs | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/byte_str.rs b/src/byte_str.rs index 181ced9e..90872ecb 100644 --- a/src/byte_str.rs +++ b/src/byte_str.rs @@ -31,7 +31,7 @@ impl ByteStr { /// /// ## Safety /// `bytes` must contain valid UTF-8. In a release build it is undefined - /// behaviour to call this with `bytes` that is not valid UTF-8. + /// behavior to call this with `bytes` that is not valid UTF-8. pub unsafe fn from_utf8_unchecked(bytes: Bytes) -> ByteStr { if cfg!(debug_assertions) { match str::from_utf8(&bytes) { diff --git a/src/extensions.rs b/src/extensions.rs index 3764a558..c41bbfc3 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -215,7 +215,7 @@ impl Extensions { self.map.as_ref().map_or(true, |map| map.is_empty()) } - /// Get the numer of extensions available. + /// Get the number of extensions available. /// /// # Example /// diff --git a/src/header/name.rs b/src/header/name.rs index 09af1da1..3d563f4e 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1659,13 +1659,13 @@ const SCRATCH_BUF_OVERFLOW: usize = SCRATCH_BUF_SIZE + 1; fn uninit_u8_array() -> [MaybeUninit; SCRATCH_BUF_SIZE] { let arr = MaybeUninit::<[MaybeUninit; SCRATCH_BUF_SIZE]>::uninit(); // Safety: assume_init() is claiming that an array of MaybeUninit<> - // has been initilized, but MaybeUninit<>'s do not require initilizaton. + // has been initialized, but MaybeUninit<>'s do not require initialization. unsafe { arr.assume_init() } } -// Assuming all the elements are initilized, get a slice of them. +// Assuming all the elements are initialized, get a slice of them. // -// Safety: All elements of `slice` must be initilized to prevent +// Safety: All elements of `slice` must be initialized to prevent // undefined behavior. unsafe fn slice_assume_init(slice: &[MaybeUninit]) -> &[T] { &*(slice as *const [MaybeUninit] as *const [T]) diff --git a/src/uri/authority.rs b/src/uri/authority.rs index dab6dcd0..07aa6795 100644 --- a/src/uri/authority.rs +++ b/src/uri/authority.rs @@ -24,7 +24,7 @@ impl Authority { // Not public while `bytes` is unstable. pub(super) fn from_shared(s: Bytes) -> Result { // Precondition on create_authority: trivially satisfied by the - // identity clousre + // identity closure create_authority(s, |s| s) } diff --git a/src/uri/scheme.rs b/src/uri/scheme.rs index c33ec41a..dbcc8c3f 100644 --- a/src/uri/scheme.rs +++ b/src/uri/scheme.rs @@ -302,7 +302,7 @@ impl Scheme2 { // Return scheme return Ok(Scheme2::Other(i)); } - // Invald scheme character, abort + // Invalid scheme character, abort 0 => break, _ => {} } @@ -349,10 +349,10 @@ mod test { #[test] fn invalid_scheme_is_error() { - Scheme::try_from("my_funky_scheme").expect_err("Unexpectly valid Scheme"); + Scheme::try_from("my_funky_scheme").expect_err("Unexpectedly valid Scheme"); // Invalid UTF-8 - Scheme::try_from([0xC0].as_ref()).expect_err("Unexpectly valid Scheme"); + Scheme::try_from([0xC0].as_ref()).expect_err("Unexpectedly valid Scheme"); } fn scheme(s: &str) -> Scheme { From 0136ae7f029ec01496c058e05ef2b509beb9316b Mon Sep 17 00:00:00 2001 From: Phoenix Kahlo Date: Mon, 28 Oct 2024 05:55:45 -0700 Subject: [PATCH 15/20] docs: fix docs failing to compile (#722) Closes #721 --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 65d98721..f23cc996 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! Notably you'll find `Uri` for what a [`Request`] is requesting, a [`Method`] //! for how it's being requested, a [`StatusCode`] for what sort of response came //! back, a [`Version`] for how this was communicated, and -//! [`HeaderName`][header::HeaderName]/[`HeaderValue`][header::HeaderName] definitions to get grouped in a [`HeaderMap`] to +//! [`HeaderName`]/[`HeaderValue`] definitions to get grouped in a [`HeaderMap`] to //! work with request/response headers. //! //! You will notably *not* find an implementation of sending requests or @@ -87,10 +87,10 @@ //! Accept: text/html //! ``` //! -//! Then `"Accept"` is a [`HeaderName`][header::HeaderName] while `"text/html"` is a [`HeaderValue`][header::HeaderValue]. +//! Then `"Accept"` is a [`HeaderName`] while `"text/html"` is a [`HeaderValue`]. //! Each of these is a dedicated type to allow for a number of interesting //! optimizations and to also encode the static guarantees of each type. For -//! example a [`HeaderName`][header::HeaderName] is always a valid `&str`, but a [`HeaderValue`] may +//! example a [`HeaderName`] is always a valid `&str`, but a [`HeaderValue`] may //! not be valid UTF-8. //! //! The most common header names are already defined for you as constant values @@ -134,7 +134,7 @@ //! Most HTTP requests and responses tend to come with more than one header, so //! it's not too useful to just work with names and values only! This crate also //! provides a [`HeaderMap`] type which is a specialized hash map for keys as -//! [`HeaderName`][header::HeaderName] and generic values. This type, like header names, is optimized +//! [`HeaderName`] and generic values. This type, like header names, is optimized //! for common usage but should continue to scale with your needs over time. //! //! # URIs From 7faad85c9458f4e6bdc4628485efea20aa2b581f Mon Sep 17 00:00:00 2001 From: "A. Carscadden" <7132621+alistaircarscadden@users.noreply.github.com> Date: Wed, 20 Nov 2024 06:55:13 -0500 Subject: [PATCH 16/20] docs: remove text stating http is early in lifecycle (#724) --- src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f23cc996..0ab5bdfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,7 @@ //! You will notably *not* find an implementation of sending requests or //! spinning up a server in this crate. It's intended that this crate is the //! "standard library" for HTTP clients and servers without dictating any -//! particular implementation. Note that this crate is still early on in its -//! lifecycle so the support libraries that integrate with the `http` crate are -//! a work in progress! Stay tuned and we'll be sure to highlight crates here -//! in the future. +//! particular implementation. //! //! ## Requests and Responses //! From 75298bd6ee42beef0e82eb4848c766c6f455ae12 Mon Sep 17 00:00:00 2001 From: Bastian <15634263+cemoktra@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:46:06 +0100 Subject: [PATCH 17/20] feat: add StatusCode::TOO_EARLY 425 (#725) Co-authored-by: Bastian Schubert --- src/status.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/status.rs b/src/status.rs index 88c556e2..16896e4a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -463,6 +463,10 @@ status_codes! { /// [[RFC4918, Section 11.4](https://tools.ietf.org/html/rfc4918#section-11.4)] (424, FAILED_DEPENDENCY, "Failed Dependency"); + /// 425 Too early + /// [[RFC8470, Section 5.2](https://httpwg.org/specs/rfc8470.html#status)] + (425, TOO_EARLY, "Too Early"); + /// 426 Upgrade Required /// [[RFC9110, Section 15.5.22](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.22)] (426, UPGRADE_REQUIRED, "Upgrade Required"); From 300f91da35009a8db8071258bb21794741b7b6a0 Mon Sep 17 00:00:00 2001 From: Qqwy / Marten Date: Tue, 26 Nov 2024 14:14:00 +0100 Subject: [PATCH 18/20] Generalize HeaderMap's TryFrom impl from HashMap, to allow other hashing functions (#729) Fixes #726 --- src/header/map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/header/map.rs b/src/header/map.rs index a668b311..7610175b 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -2020,7 +2020,7 @@ impl FromIterator<(HeaderName, T)> for HeaderMap { /// let headers: HeaderMap = (&map).try_into().expect("valid headers"); /// assert_eq!(headers["X-Custom-Header"], "my value"); /// ``` -impl<'a, K, V, T> TryFrom<&'a HashMap> for HeaderMap +impl<'a, K, V, S, T> TryFrom<&'a HashMap> for HeaderMap where K: Eq + Hash, HeaderName: TryFrom<&'a K>, @@ -2030,7 +2030,7 @@ where { type Error = Error; - fn try_from(c: &'a HashMap) -> Result { + fn try_from(c: &'a HashMap) -> Result { c.iter() .map(|(k, v)| -> crate::Result<(HeaderName, T)> { let name = TryFrom::try_from(k).map_err(Into::into)?; From 5a1a5e8ee54fc2e2f274faf318d9a8b1129c386a Mon Sep 17 00:00:00 2001 From: Robert <102612257+SmolPatches@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:30:29 -0500 Subject: [PATCH 19/20] feat: change to TryInto bounds for Builder Methods (#730) This makes requests::Builder trait bound easily readable and consistent w/ stdlib recommendations. It also technically _increases_ the amount of types that could meet the bounds, because of how TryFrom/TryInto interact. Closes #727 Co-authored-by: rob --- src/request.rs | 62 +++++++++++++++++++++++----------------------- src/response.rs | 20 +++++++-------- src/uri/builder.rs | 14 +++++------ 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/request.rs b/src/request.rs index d4c5bf54..324b676c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -53,7 +53,7 @@ //! ``` use std::any::Any; -use std::convert::TryFrom; +use std::convert::TryInto; use std::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; @@ -231,8 +231,8 @@ impl Request<()> { /// ``` pub fn get(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::GET).uri(uri) } @@ -253,8 +253,8 @@ impl Request<()> { /// ``` pub fn put(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::PUT).uri(uri) } @@ -275,8 +275,8 @@ impl Request<()> { /// ``` pub fn post(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::POST).uri(uri) } @@ -297,8 +297,8 @@ impl Request<()> { /// ``` pub fn delete(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::DELETE).uri(uri) } @@ -320,8 +320,8 @@ impl Request<()> { /// ``` pub fn options(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::OPTIONS).uri(uri) } @@ -342,8 +342,8 @@ impl Request<()> { /// ``` pub fn head(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::HEAD).uri(uri) } @@ -364,8 +364,8 @@ impl Request<()> { /// ``` pub fn connect(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::CONNECT).uri(uri) } @@ -386,8 +386,8 @@ impl Request<()> { /// ``` pub fn patch(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::PATCH).uri(uri) } @@ -408,8 +408,8 @@ impl Request<()> { /// ``` pub fn trace(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::TRACE).uri(uri) } @@ -767,11 +767,11 @@ impl Builder { /// ``` pub fn method(self, method: T) -> Builder where - Method: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - let method = TryFrom::try_from(method).map_err(Into::into)?; + let method = method.try_into().map_err(Into::into)?; head.method = method; Ok(head) }) @@ -812,11 +812,11 @@ impl Builder { /// ``` pub fn uri(self, uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - head.uri = TryFrom::try_from(uri).map_err(Into::into)?; + head.uri = uri.try_into().map_err(Into::into)?; Ok(head) }) } @@ -900,14 +900,14 @@ impl Builder { /// ``` pub fn header(self, key: K, value: V) -> Builder where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, + K: TryInto, + >::Error: Into, + V: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - let name = >::try_from(key).map_err(Into::into)?; - let value = >::try_from(value).map_err(Into::into)?; + let name = key.try_into().map_err(Into::into)?; + let value = value.try_into().map_err(Into::into)?; head.headers.try_append(name, value)?; Ok(head) }) diff --git a/src/response.rs b/src/response.rs index 312cc5f8..ab9e49bc 100644 --- a/src/response.rs +++ b/src/response.rs @@ -62,7 +62,7 @@ //! ``` use std::any::Any; -use std::convert::TryFrom; +use std::convert::TryInto; use std::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; @@ -559,11 +559,11 @@ impl Builder { /// ``` pub fn status(self, status: T) -> Builder where - StatusCode: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - head.status = TryFrom::try_from(status).map_err(Into::into)?; + head.status = status.try_into().map_err(Into::into)?; Ok(head) }) } @@ -610,14 +610,14 @@ impl Builder { /// ``` pub fn header(self, key: K, value: V) -> Builder where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, + K: TryInto, + >::Error: Into, + V: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - let name = >::try_from(key).map_err(Into::into)?; - let value = >::try_from(value).map_err(Into::into)?; + let name = key.try_into().map_err(Into::into)?; + let value = value.try_into().map_err(Into::into)?; head.headers.try_append(name, value)?; Ok(head) }) diff --git a/src/uri/builder.rs b/src/uri/builder.rs index 9964d389..d5f7f49b 100644 --- a/src/uri/builder.rs +++ b/src/uri/builder.rs @@ -1,4 +1,4 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; use super::{Authority, Parts, PathAndQuery, Scheme}; use crate::Uri; @@ -44,8 +44,8 @@ impl Builder { /// ``` pub fn scheme(self, scheme: T) -> Self where - Scheme: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.map(move |mut parts| { let scheme = scheme.try_into().map_err(Into::into)?; @@ -68,8 +68,8 @@ impl Builder { /// ``` pub fn authority(self, auth: T) -> Self where - Authority: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.map(move |mut parts| { let auth = auth.try_into().map_err(Into::into)?; @@ -92,8 +92,8 @@ impl Builder { /// ``` pub fn path_and_query(self, p_and_q: T) -> Self where - PathAndQuery: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.map(move |mut parts| { let p_and_q = p_and_q.try_into().map_err(Into::into)?; From 92d4148a6490823dc285c8cc240d750e08f88d6a Mon Sep 17 00:00:00 2001 From: dlzht <78463157+dlzht@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:43:42 +0800 Subject: [PATCH 20/20] fix: off by 1 error in Method::from_bytes causing an allocation (#708) --- src/method.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/method.rs b/src/method.rs index 3d45ef9f..7b4584ab 100644 --- a/src/method.rs +++ b/src/method.rs @@ -123,7 +123,7 @@ impl Method { _ => Method::extension_inline(src), }, _ => { - if src.len() < InlineExtension::MAX { + if src.len() <= InlineExtension::MAX { Method::extension_inline(src) } else { let allocated = AllocatedExtension::new(src)?; @@ -465,6 +465,21 @@ mod test { let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely."; assert_eq!(Method::from_str(long_method).unwrap(), long_method); + + let longest_inline_method = [b'A'; InlineExtension::MAX]; + assert_eq!( + Method::from_bytes(&longest_inline_method).unwrap(), + Method(ExtensionInline( + InlineExtension::new(&longest_inline_method).unwrap() + )) + ); + let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1]; + assert_eq!( + Method::from_bytes(&shortest_allocated_method).unwrap(), + Method(ExtensionAllocated( + AllocatedExtension::new(&shortest_allocated_method).unwrap() + )) + ); } #[test]