From 2011a8591becff6934fbdae83fb9bf8a090950a7 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Thu, 30 Jan 2025 17:52:20 -0500 Subject: [PATCH] rework for no_std support --- Cargo.toml | 26 +++++--- src/byte_str.rs | 6 +- src/convert.rs | 5 +- src/error.rs | 27 ++++++-- src/extensions.rs | 16 +++-- src/header/map.rs | 116 ++++++++++++++++++++++++++++------- src/header/mod.rs | 4 ++ src/header/name.rs | 142 +++++++++++++++++++++++++++++++++---------- src/header/value.rs | 55 ++++++++++------- src/lib.rs | 37 +++++++++-- src/method.rs | 62 +++++++++++++------ src/request.rs | 23 ++++--- src/response.rs | 27 +++++--- src/status.rs | 12 ++-- src/uri/authority.rs | 36 ++++++----- src/uri/builder.rs | 3 +- src/uri/mod.rs | 29 +++++---- src/uri/path.rs | 42 ++++++++----- src/uri/port.rs | 4 +- src/uri/scheme.rs | 41 +++++++++---- src/uri/tests.rs | 22 ++++--- src/version.rs | 2 +- 22 files changed, 517 insertions(+), 220 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a6e9c9d..b45fbd10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ documentation = "https://docs.rs/http" repository = "https://github.com/hyperium/http" license = "MIT OR Apache-2.0" authors = [ - "Alex Crichton ", - "Carl Lerche ", - "Sean McArthur ", + "Alex Crichton ", + "Carl Lerche ", + "Sean McArthur ", ] description = """ A set of types for representing HTTP requests and responses. @@ -25,19 +25,21 @@ rust-version = "1.49.0" [workspace] members = [ - ".", + ".", ] exclude = [ - "fuzz", - "benches" + "fuzz", + "benches" ] [features] default = ["std"] -std = [] +std = ["alloc"] +alloc = ["dep:bytes", "dep:hashbrown"] [dependencies] -bytes = "1" +bytes = { version = "1", optional = true } +hashbrown = { version = "0.15", optional = true } fnv = "1.0.5" itoa = "1" @@ -47,3 +49,11 @@ rand = "0.8.0" serde = "1.0" serde_json = "1.0" doc-comment = "0.3" + +[[test]] +name = "header_map_fuzz" +required-features = ["alloc"] + +[[test]] +name = "header_map" +required-features = ["alloc"] diff --git a/src/byte_str.rs b/src/byte_str.rs index 90872ecb..f54d4c59 100644 --- a/src/byte_str.rs +++ b/src/byte_str.rs @@ -1,6 +1,6 @@ use bytes::Bytes; -use std::{ops, str}; +use core::{ops, str}; #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub(crate) struct ByteStr { @@ -58,9 +58,9 @@ impl ops::Deref for ByteStr { } } -impl From for ByteStr { +impl From for ByteStr { #[inline] - fn from(src: String) -> ByteStr { + fn from(src: alloc::string::String) -> ByteStr { ByteStr { // Invariant: src is a String so contains valid UTF-8. bytes: Bytes::from(src), diff --git a/src/convert.rs b/src/convert.rs index 682e0ed5..3454f46b 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,11 +1,12 @@ +#[cfg(feature = "alloc")] macro_rules! if_downcast_into { ($in_ty:ty, $out_ty:ty, $val:ident, $body:expr) => {{ - if std::any::TypeId::of::<$in_ty>() == std::any::TypeId::of::<$out_ty>() { + if ::core::any::TypeId::of::<$in_ty>() == ::core::any::TypeId::of::<$out_ty>() { // Store the value in an `Option` so we can `take` // it after casting to `&mut dyn Any`. let mut slot = Some($val); // Re-write the `$val` ident with the downcasted value. - let $val = (&mut slot as &mut dyn std::any::Any) + let $val = (&mut slot as &mut dyn ::core::any::Any) .downcast_mut::>() .unwrap() .take() diff --git a/src/error.rs b/src/error.rs index 762ee1c2..a1a49c04 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,12 @@ -use std::error; -use std::fmt; -use std::result; +use core::error; +use core::fmt; use crate::header; +#[cfg(feature = "alloc")] use crate::header::MaxSizeReached; use crate::method; use crate::status; +#[cfg(feature = "alloc")] use crate::uri; /// A generic "error" for HTTP connections @@ -19,15 +20,19 @@ pub struct Error { } /// A `Result` typedef to use with the `http::Error` type -pub type Result = result::Result; +pub type Result = core::result::Result; enum ErrorKind { StatusCode(status::InvalidStatusCode), Method(method::InvalidMethod), + #[cfg(feature = "alloc")] Uri(uri::InvalidUri), + #[cfg(feature = "alloc")] UriParts(uri::InvalidUriParts), HeaderName(header::InvalidHeaderName), + #[cfg(feature = "alloc")] HeaderValue(header::InvalidHeaderValue), + #[cfg(feature = "alloc")] MaxSizeReached(MaxSizeReached), } @@ -59,10 +64,14 @@ impl Error { match self.inner { StatusCode(ref e) => e, Method(ref e) => e, + #[cfg(feature = "alloc")] Uri(ref e) => e, + #[cfg(feature = "alloc")] UriParts(ref e) => e, HeaderName(ref e) => e, + #[cfg(feature = "alloc")] HeaderValue(ref e) => e, + #[cfg(feature = "alloc")] MaxSizeReached(ref e) => e, } } @@ -76,6 +85,7 @@ impl error::Error for Error { } } +#[cfg(feature = "alloc")] impl From for Error { fn from(err: MaxSizeReached) -> Error { Error { @@ -100,6 +110,7 @@ impl From for Error { } } +#[cfg(feature = "alloc")] impl From for Error { fn from(err: uri::InvalidUri) -> Error { Error { @@ -108,6 +119,7 @@ impl From for Error { } } +#[cfg(feature = "alloc")] impl From for Error { fn from(err: uri::InvalidUriParts) -> Error { Error { @@ -124,6 +136,7 @@ impl From for Error { } } +#[cfg(feature = "alloc")] impl From for Error { fn from(err: header::InvalidHeaderValue) -> Error { Error { @@ -132,8 +145,8 @@ impl From for Error { } } -impl From for Error { - fn from(err: std::convert::Infallible) -> Error { +impl From for Error { + fn from(err: core::convert::Infallible) -> Error { match err {} } } @@ -147,10 +160,12 @@ mod tests { if let Err(e) = status::StatusCode::from_u16(6666) { let err: Error = e.into(); let ie = err.get_ref(); + #[cfg(feature = "alloc")] assert!(!ie.is::()); assert!(ie.is::()); ie.downcast_ref::().unwrap(); + #[cfg(feature = "alloc")] assert!(!err.is::()); assert!(err.is::()); } else { diff --git a/src/extensions.rs b/src/extensions.rs index f16d762e..e7e0aae2 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,9 +1,13 @@ -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::fmt; -use std::hash::{BuildHasherDefault, Hasher}; - -type AnyMap = HashMap, BuildHasherDefault>; +use alloc::boxed::Box; +use core::any::{Any, TypeId}; +use core::fmt; +use std::hash::Hasher; + +type AnyMap = hashbrown::HashMap< + TypeId, + Box, + core::hash::BuildHasherDefault, +>; // With TypeIds as keys, there's no need to hash them. They are already hashes // themselves, coming from the compiler. The IdHasher just holds the u64 of diff --git a/src/header/map.rs b/src/header/map.rs index ebbc5937..9f3bb32b 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -1,10 +1,14 @@ -use std::collections::hash_map::RandomState; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::hash::{BuildHasher, Hash, Hasher}; -use std::iter::{FromIterator, FusedIterator}; -use std::marker::PhantomData; -use std::{fmt, mem, ops, ptr, vec}; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +use core::convert::TryFrom; +use core::hash::{BuildHasher, Hash, Hasher}; +use core::iter::{FromIterator, FusedIterator}; +use core::marker::PhantomData; +use core::{fmt, mem, ops, ptr}; + +use hashbrown::HashMap; use crate::Error; @@ -116,7 +120,7 @@ pub struct IntoIter { /// associated value. #[derive(Debug)] pub struct Keys<'a, T> { - inner: ::std::slice::Iter<'a, Bucket>, + inner: ::core::slice::Iter<'a, Bucket>, } /// `HeaderMap` value iterator. @@ -209,7 +213,7 @@ pub struct ValueIterMut<'a, T> { #[derive(Debug)] pub struct ValueDrain<'a, T> { first: Option, - next: Option<::std::vec::IntoIter>, + next: Option<::alloc::vec::IntoIter>, lt: PhantomData<&'a mut HeaderMap>, } @@ -316,7 +320,7 @@ enum Link { enum Danger { Green, Yellow, - Red(RandomState), + Red(hashbrown::DefaultHashBuilder), } // Constants related to detecting DOS attacks. @@ -2007,12 +2011,56 @@ impl FromIterator<(HeaderName, T)> for HeaderMap { } } -/// Try to convert a `HashMap` into a `HeaderMap`. +macro_rules! try_map { + ($map_like:ident) => { + ($map_like) + .into_iter() + .map(|(k, v)| { + let name = TryFrom::try_from(k).map_err(Into::into)?; + let value = TryFrom::try_from(v).map_err(Into::into)?; + + Ok((name, value)) + }) + .collect() + }; +} + +/// Try to convert a `BTreeMap` into a `HeaderMap`. /// /// # Examples /// /// ``` -/// use std::collections::HashMap; +/// use std::collections::BTreeMap; +/// use std::convert::TryInto; +/// use http::HeaderMap; +/// +/// let mut map = BTreeMap::new(); +/// map.insert("X-Custom-Header".to_string(), "my value".to_string()); +/// +/// let headers: HeaderMap = (&map).try_into().expect("valid headers"); +/// assert_eq!(headers["X-Custom-Header"], "my value"); +/// ``` +impl<'a, K, V, T> TryFrom<&'a alloc::collections::BTreeMap> for HeaderMap +where + K: Eq + Hash + Ord, + HeaderName: TryFrom<&'a K>, + >::Error: Into, + T: TryFrom<&'a V>, + T::Error: Into, +{ + type Error = Error; + + fn try_from(c: &'a alloc::collections::BTreeMap) -> Result { + try_map!(c) + } +} + +/// Try to convert a `hashbrown::HashMap` into a `HeaderMap`. +/// +/// # Examples +/// +/// ``` +/// use hashbrown::HashMap; /// use std::convert::TryInto; /// use http::HeaderMap; /// @@ -2033,13 +2081,38 @@ where type Error = Error; 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)?; - let value = TryFrom::try_from(v).map_err(Into::into)?; - Ok((name, value)) - }) - .collect() + try_map!(c) + } +} + +/// Try to convert a `HashMap` into a `HeaderMap`. +/// +/// # Examples +/// +/// ``` +/// use std::collections::HashMap; +/// use std::convert::TryInto; +/// use http::HeaderMap; +/// +/// let mut map = HashMap::new(); +/// map.insert("X-Custom-Header".to_string(), "my value".to_string()); +/// +/// let headers: HeaderMap = (&map).try_into().expect("valid headers"); +/// assert_eq!(headers["X-Custom-Header"], "my value"); +/// ``` +#[cfg(feature = "std")] +impl<'a, K, V, S, T> TryFrom<&'a std::collections::HashMap> for HeaderMap +where + K: Eq + Hash, + HeaderName: TryFrom<&'a K>, + >::Error: Into, + T: TryFrom<&'a V>, + T::Error: Into, +{ + type Error = Error; + + fn try_from(c: &'a std::collections::HashMap) -> Result { + try_map!(c) } } @@ -3533,7 +3606,7 @@ impl Danger { fn set_red(&mut self) { debug_assert!(self.is_yellow()); - *self = Danger::Red(RandomState::new()); + *self = Danger::Red(hashbrown::DefaultHashBuilder::default()); } fn is_yellow(&self) -> bool { @@ -3574,7 +3647,7 @@ impl fmt::Display for MaxSizeReached { } } -impl std::error::Error for MaxSizeReached {} +impl core::error::Error for MaxSizeReached {} // ===== impl Utils ===== @@ -3736,6 +3809,7 @@ mod into_header_name { mod as_header_name { use super::{Entry, HdrName, HeaderMap, HeaderName, InvalidHeaderName, MaxSizeReached}; + use alloc::string::String; /// A marker trait used to identify values that can be used as search keys /// to a `HeaderMap`. diff --git a/src/header/mod.rs b/src/header/mod.rs index 5d405767..45f89495 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -70,16 +70,20 @@ //! [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html //! [Robin Hood hashing]: https://en.wikipedia.org/wiki/Hash_table#Robin_Hood_hashing +#[cfg(feature = "alloc")] mod map; mod name; +#[cfg(feature = "alloc")] mod value; +#[cfg(feature = "alloc")] pub use self::map::{ AsHeaderName, Drain, Entry, GetAll, HeaderMap, IntoHeaderName, IntoIter, Iter, IterMut, Keys, MaxSizeReached, OccupiedEntry, VacantEntry, ValueDrain, ValueIter, ValueIterMut, Values, ValuesMut, }; pub use self::name::{HeaderName, InvalidHeaderName}; +#[cfg(feature = "alloc")] pub use self::value::{HeaderValue, InvalidHeaderValue, ToStrError}; // Use header name constants diff --git a/src/header/name.rs b/src/header/name.rs index 3d563f4e..eef94690 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1,13 +1,15 @@ +use core::borrow::Borrow; +use core::convert::TryFrom; +use core::error::Error; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::mem::MaybeUninit; +use core::str::FromStr; + +#[cfg(feature = "alloc")] use crate::byte_str::ByteStr; -use bytes::{Bytes, BytesMut}; - -use std::borrow::Borrow; -use std::convert::TryFrom; -use std::error::Error; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::mem::MaybeUninit; -use std::str::FromStr; +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; /// Represents an HTTP header field name /// @@ -31,7 +33,10 @@ use std::str::FromStr; /// [`header`]: index.html #[derive(Clone, Eq, PartialEq, Hash)] pub struct HeaderName { + #[cfg(feature = "alloc")] inner: Repr, + #[cfg(not(feature = "alloc"))] + inner: Repr, } // Almost a full `HeaderName` @@ -46,6 +51,7 @@ enum Repr { Custom(T), } +#[cfg(feature = "alloc")] // Used to hijack the Hash impl #[derive(Debug, Clone, Eq, PartialEq)] struct Custom(ByteStr); @@ -89,7 +95,7 @@ macro_rules! standard_headers { match *self { // Safety: test_parse_standard_headers ensures these &[u8]s are &str-safe. $( - StandardHeader::$konst => unsafe { std::str::from_utf8_unchecked( $name_bytes ) }, + StandardHeader::$konst => unsafe { core::str::from_utf8_unchecked( $name_bytes ) }, )+ } } @@ -118,25 +124,26 @@ macro_rules! standard_headers { assert_eq!(HeaderName::from_bytes(name_bytes).unwrap(), HeaderName::from(std)); // Test upper case - let upper = std::str::from_utf8(name_bytes).expect("byte string constants are all utf-8").to_uppercase(); + let upper = core::str::from_utf8(name_bytes).expect("byte string constants are all utf-8").to_uppercase(); assert_eq!(HeaderName::from_bytes(upper.as_bytes()).unwrap(), HeaderName::from(std)); } } + #[cfg(feature = "alloc")] #[test] fn test_standard_headers_into_bytes() { for &(std, name_bytes) in TEST_HEADERS { - let name = std::str::from_utf8(name_bytes).unwrap(); + let name = core::str::from_utf8(name_bytes).unwrap(); let std = HeaderName::from(std); // Test lower case - let bytes: Bytes = + let bytes: bytes::Bytes = HeaderName::from_bytes(name_bytes).unwrap().inner.into(); assert_eq!(bytes, name); assert_eq!(HeaderName::from_bytes(name_bytes).unwrap(), std); // Test upper case let upper = name.to_uppercase(); - let bytes: Bytes = + let bytes: bytes::Bytes = HeaderName::from_bytes(upper.as_bytes()).unwrap().inner.into(); assert_eq!(bytes, name_bytes); assert_eq!(HeaderName::from_bytes(upper.as_bytes()).unwrap(), @@ -1117,15 +1124,21 @@ impl HeaderName { // Precondition: HEADER_CHARS is a valid table for parse_hdr(). match parse_hdr(src, &mut buf, &HEADER_CHARS)?.inner { Repr::Standard(std) => Ok(std.into()), + + #[cfg(not(feature = "alloc"))] + _ => Err(InvalidHeaderName::new()), + + #[cfg(feature = "alloc")] Repr::Custom(MaybeLower { buf, lower: true }) => { - let buf = Bytes::copy_from_slice(buf); + let buf = bytes::Bytes::copy_from_slice(buf); // Safety: the invariant on MaybeLower ensures buf is valid UTF-8. let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; Ok(Custom(val).into()) } + #[cfg(feature = "alloc")] Repr::Custom(MaybeLower { buf, lower: false }) => { use bytes::BufMut; - let mut dst = BytesMut::with_capacity(buf.len()); + let mut dst = bytes::BytesMut::with_capacity(buf.len()); for b in buf.iter() { // HEADER_CHARS maps all bytes to valid single-byte UTF-8 @@ -1171,12 +1184,19 @@ impl HeaderName { // Precondition: HEADER_CHARS_H2 is a valid table for parse_hdr() match parse_hdr(src, &mut buf, &HEADER_CHARS_H2)?.inner { Repr::Standard(std) => Ok(std.into()), + + #[cfg(not(feature = "alloc"))] + _ => Err(InvalidHeaderName::new()), + + #[cfg(feature = "alloc")] Repr::Custom(MaybeLower { buf, lower: true }) => { - let buf = Bytes::copy_from_slice(buf); + let buf = bytes::Bytes::copy_from_slice(buf); // Safety: the invariant on MaybeLower ensures buf is valid UTF-8. let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; Ok(Custom(val).into()) } + + #[cfg(feature = "alloc")] Repr::Custom(MaybeLower { buf, lower: false }) => { for &b in buf.iter() { // HEADER_CHARS_H2 maps all bytes that are not valid single-byte @@ -1186,7 +1206,7 @@ impl HeaderName { } } - let buf = Bytes::copy_from_slice(buf); + let buf = bytes::Bytes::copy_from_slice(buf); // Safety: the loop above checks that each byte of buf (either // version) is valid UTF-8. let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; @@ -1226,6 +1246,9 @@ impl HeaderName { /// | ------------------------------------------------------------------------ /// ``` /// + /// With the "alloc" feature disabled, any header name that is not well-known will also + /// raise a panic. It is recommended in this case to use [`StandardHeader::from_bytes`]. + /// /// # Examples /// /// ``` @@ -1234,12 +1257,14 @@ impl HeaderName { /// let hdr = HeaderName::from_static("content-length"); /// assert_eq!(CONTENT_LENGTH, hdr); /// + /// # #[cfg(feature = "alloc")] { /// // Parsing a custom header /// let CUSTOM_HEADER: &'static str = "custom-header"; /// /// let a = HeaderName::from_lowercase(b"custom-header").unwrap(); /// let b = HeaderName::from_static(CUSTOM_HEADER); /// assert_eq!(a, b); + /// # } /// ``` /// /// ```should_panic @@ -1281,6 +1306,19 @@ impl HeaderName { ([] as [u8; 0])[0]; // Invalid header name } + #[cfg(not(feature = "alloc"))] + { + // TODO: see above + #[allow(clippy::no_effect, clippy::out_of_bounds_indexing)] + ([] as [u8; 0])[0]; // Invalid header name + + // dummy + HeaderName { + inner: Repr::Standard(StandardHeader::Accept), + } + } + + #[cfg(feature = "alloc")] HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static(src))), } @@ -1293,11 +1331,15 @@ impl HeaderName { pub fn as_str(&self) -> &str { match self.inner { Repr::Standard(v) => v.as_str(), + #[cfg(feature = "alloc")] Repr::Custom(ref v) => &v.0, + #[cfg(not(feature = "alloc"))] + _ => unreachable!(), } } - pub(super) fn into_bytes(self) -> Bytes { + #[cfg(feature = "alloc")] + pub(super) fn into_bytes(self) -> bytes::Bytes { self.inner.into() } } @@ -1352,23 +1394,25 @@ impl<'a> From<&'a HeaderName> for HeaderName { } } +#[cfg(feature = "alloc")] #[doc(hidden)] -impl From> for Bytes +impl From> for bytes::Bytes where - T: Into, + T: Into, { - fn from(repr: Repr) -> Bytes { + fn from(repr: Repr) -> bytes::Bytes { match repr { - Repr::Standard(header) => Bytes::from_static(header.as_str().as_bytes()), + Repr::Standard(header) => bytes::Bytes::from_static(header.as_str().as_bytes()), Repr::Custom(header) => header.into(), } } } -impl From for Bytes { +#[cfg(feature = "alloc")] +impl From for bytes::Bytes { #[inline] - fn from(Custom(inner): Custom) -> Bytes { - Bytes::from(inner) + fn from(Custom(inner): Custom) -> bytes::Bytes { + bytes::Bytes::from(inner) } } @@ -1380,6 +1424,7 @@ impl<'a> TryFrom<&'a str> for HeaderName { } } +#[cfg(feature = "alloc")] impl<'a> TryFrom<&'a String> for HeaderName { type Error = InvalidHeaderName; #[inline] @@ -1396,6 +1441,7 @@ impl<'a> TryFrom<&'a [u8]> for HeaderName { } } +#[cfg(feature = "alloc")] impl TryFrom for HeaderName { type Error = InvalidHeaderName; @@ -1405,6 +1451,7 @@ impl TryFrom for HeaderName { } } +#[cfg(feature = "alloc")] impl TryFrom> for HeaderName { type Error = InvalidHeaderName; @@ -1423,6 +1470,7 @@ impl From for HeaderName { } } +#[cfg(feature = "alloc")] #[doc(hidden)] impl From for HeaderName { fn from(src: Custom) -> HeaderName { @@ -1558,9 +1606,10 @@ impl<'a> From> for HeaderName { Repr::Standard(s) => HeaderName { inner: Repr::Standard(s), }, + #[cfg(feature = "alloc")] Repr::Custom(maybe_lower) => { if maybe_lower.lower { - let buf = Bytes::copy_from_slice(maybe_lower.buf); + let buf = bytes::Bytes::copy_from_slice(maybe_lower.buf); // Safety: the invariant on MaybeLower ensures buf is valid UTF-8. let byte_str = unsafe { ByteStr::from_utf8_unchecked(buf) }; @@ -1569,7 +1618,7 @@ impl<'a> From> for HeaderName { } } else { use bytes::BufMut; - let mut dst = BytesMut::with_capacity(maybe_lower.buf.len()); + let mut dst = bytes::BytesMut::with_capacity(maybe_lower.buf.len()); for b in maybe_lower.buf.iter() { // HEADER_CHARS maps each byte to a valid single-byte UTF-8 @@ -1587,6 +1636,10 @@ impl<'a> From> for HeaderName { } } } + #[cfg(not(feature = "alloc"))] + _ => { + unreachable!() + } } } } @@ -1600,6 +1653,7 @@ impl<'a> PartialEq> for HeaderName { Repr::Standard(b) => a == b, _ => false, }, + #[cfg(feature = "alloc")] Repr::Custom(Custom(ref a)) => match other.inner { Repr::Custom(ref b) => { if b.lower { @@ -1610,12 +1664,18 @@ impl<'a> PartialEq> for HeaderName { } _ => false, }, + + #[cfg(not(feature = "alloc"))] + _ => { + unreachable!() + } } } } // ===== Custom ===== +#[cfg(feature = "alloc")] impl Hash for Custom { #[inline] fn hash(&self, hasher: &mut H) { @@ -1675,6 +1735,13 @@ unsafe fn slice_assume_init(slice: &[MaybeUninit]) -> &[T] { mod tests { use self::StandardHeader::Vary; use super::*; + use alloc::vec; + + #[cfg(feature = "alloc")] + use crate::byte_str::ByteStr; + + #[cfg(feature = "alloc")] + use crate::header::name::Custom; #[test] fn test_bounds() { @@ -1696,6 +1763,7 @@ mod tests { const ONE_TOO_LONG: &[u8] = &[b'a'; super::super::MAX_HEADER_NAME_LEN + 1]; + #[cfg(feature = "alloc")] #[test] fn test_invalid_name_lengths() { assert!( @@ -1705,7 +1773,7 @@ mod tests { let long = &ONE_TOO_LONG[0..super::super::MAX_HEADER_NAME_LEN]; - let long_str = std::str::from_utf8(long).unwrap(); + let long_str = core::str::from_utf8(long).unwrap(); assert_eq!(HeaderName::from_static(long_str), long_str); // shouldn't panic! assert!( @@ -1722,10 +1790,11 @@ mod tests { #[should_panic] fn test_static_invalid_name_lengths() { // Safety: ONE_TOO_LONG contains only the UTF-8 safe, single-byte codepoint b'a'. - let _ = HeaderName::from_static(unsafe { std::str::from_utf8_unchecked(ONE_TOO_LONG) }); + let _ = HeaderName::from_static(unsafe { core::str::from_utf8_unchecked(ONE_TOO_LONG) }); } #[test] + #[cfg(feature = "alloc")] fn test_from_hdr_name() { use self::StandardHeader::Vary; @@ -1761,6 +1830,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn test_eq_hdr_name() { use self::StandardHeader::Vary; @@ -1820,8 +1890,11 @@ mod tests { let b = HeaderName::from_static("vary"); assert_eq!(a, b); - let b = HeaderName::from_static("vaary"); - assert_ne!(a, b); + #[cfg(feature = "alloc")] + { + let b = HeaderName::from_static("vaary"); + assert_ne!(a, b); + } } #[test] @@ -1838,6 +1911,7 @@ mod tests { // MaybeLower { lower: true } #[test] + #[cfg(feature = "alloc")] fn test_from_static_custom_short() { let a = HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static("customheader"))), @@ -1860,6 +1934,7 @@ mod tests { // MaybeLower { lower: false } #[test] + #[cfg(feature = "alloc")] fn test_from_static_custom_long() { let a = HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static( @@ -1889,6 +1964,7 @@ mod tests { } #[test] + #[cfg(feature = "alloc")] fn test_from_static_custom_single_char() { let a = HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static("a"))), @@ -1903,6 +1979,8 @@ mod tests { HeaderName::from_static(""); } + // Header name parsing panics in a non-alloc context + #[cfg(feature = "alloc")] #[test] fn test_all_tokens() { HeaderName::from_static("!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyz"); diff --git a/src/header/value.rs b/src/header/value.rs index 99d1e155..3124edd0 100644 --- a/src/header/value.rs +++ b/src/header/value.rs @@ -1,11 +1,11 @@ use bytes::{Bytes, BytesMut}; -use std::convert::TryFrom; -use std::error::Error; -use std::fmt::Write; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; -use std::{cmp, fmt, str}; +use core::convert::TryFrom; +use core::error::Error; +use core::fmt::Write; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; +use core::{cmp, fmt, str}; use crate::header::name::HeaderName; @@ -234,7 +234,7 @@ impl HeaderValue { } fn from_shared(src: Bytes) -> Result { - HeaderValue::try_from_generic(src, std::convert::identity) + HeaderValue::try_from_generic(src, core::convert::identity) } fn try_from_generic, F: FnOnce(T) -> Bytes>( @@ -435,11 +435,13 @@ macro_rules! from_integers { #[test] fn $name() { + use alloc::string::ToString; + let n: $t = 55; let val = HeaderValue::from(n); assert_eq!(val, &n.to_string()); - let n = ::std::$t::MAX; + let n = ::core::$t::MAX; let val = HeaderValue::from(n); assert_eq!(val, &n.to_string()); } @@ -528,10 +530,11 @@ impl<'a> TryFrom<&'a str> for HeaderValue { } } -impl<'a> TryFrom<&'a String> for HeaderValue { +#[cfg(feature = "alloc")] +impl<'a> TryFrom<&'a alloc::string::String> for HeaderValue { type Error = InvalidHeaderValue; #[inline] - fn try_from(s: &'a String) -> Result { + fn try_from(s: &'a alloc::string::String) -> Result { Self::from_bytes(s.as_bytes()) } } @@ -545,20 +548,22 @@ impl<'a> TryFrom<&'a [u8]> for HeaderValue { } } -impl TryFrom for HeaderValue { +#[cfg(feature = "alloc")] +impl TryFrom for HeaderValue { type Error = InvalidHeaderValue; #[inline] - fn try_from(t: String) -> Result { + fn try_from(t: alloc::string::String) -> Result { HeaderValue::from_shared(t.into()) } } -impl TryFrom> for HeaderValue { +#[cfg(feature = "alloc")] +impl TryFrom> for HeaderValue { type Error = InvalidHeaderValue; #[inline] - fn try_from(vec: Vec) -> Result { + fn try_from(vec: alloc::vec::Vec) -> Result { HeaderValue::from_shared(vec.into()) } } @@ -697,28 +702,32 @@ impl PartialOrd for [u8] { } } -impl PartialEq for HeaderValue { +#[cfg(feature = "alloc")] +impl PartialEq for HeaderValue { #[inline] - fn eq(&self, other: &String) -> bool { + fn eq(&self, other: &alloc::string::String) -> bool { *self == other[..] } } -impl PartialOrd for HeaderValue { +#[cfg(feature = "alloc")] +impl PartialOrd for HeaderValue { #[inline] - fn partial_cmp(&self, other: &String) -> Option { + fn partial_cmp(&self, other: &alloc::string::String) -> Option { self.inner.partial_cmp(other.as_bytes()) } } -impl PartialEq for String { +#[cfg(feature = "alloc")] +impl PartialEq for alloc::string::String { #[inline] fn eq(&self, other: &HeaderValue) -> bool { *other == *self } } -impl PartialOrd for String { +#[cfg(feature = "alloc")] +impl PartialOrd for alloc::string::String { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { self.as_bytes().partial_cmp(other.as_bytes()) @@ -775,7 +784,7 @@ impl<'a> PartialOrd for &'a str { #[test] fn test_try_from() { - HeaderValue::try_from(vec![127]).unwrap_err(); + HeaderValue::try_from(alloc::vec![127]).unwrap_err(); } #[test] @@ -788,11 +797,11 @@ fn test_debug() { for &(value, expected) in cases { let val = HeaderValue::from_bytes(value.as_bytes()).unwrap(); - let actual = format!("{:?}", val); + let actual = alloc::format!("{:?}", val); assert_eq!(expected, actual); } let mut sensitive = HeaderValue::from_static("password"); sensitive.set_sensitive(true); - assert_eq!("Sensitive", format!("{:?}", sensitive)); + assert_eq!("Sensitive", alloc::format!("{:?}", sensitive)); } diff --git a/src/lib.rs b/src/lib.rs index 0ab5bdfd..3610cf7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ //! server you might want to inspect a requests URI to dispatch it: //! //! ``` +//! # #[cfg(feature = "alloc")] { //! use http::{Request, Response}; //! //! fn response(req: Request<()>) -> http::Result> { @@ -40,6 +41,7 @@ //! # fn foo(_req: Request<()>) -> http::Result> { panic!() } //! # fn bar(_req: Request<()>) -> http::Result> { panic!() } //! # fn not_found(_req: Request<()>) -> http::Result> { panic!() } +//! # } //! ``` //! //! On a [`Request`] you'll also find accessors like [`method`][Request::method] to return a @@ -50,6 +52,7 @@ //! to edit the request/response: //! //! ``` +//! # #[cfg(feature = "alloc")] { //! use http::{HeaderValue, Response, StatusCode}; //! use http::header::CONTENT_TYPE; //! @@ -58,6 +61,7 @@ //! .insert(CONTENT_TYPE, HeaderValue::from_static("text/html")); //! *response.status_mut() = StatusCode::OK; //! } +//! # } //! ``` //! //! And finally, one of the most important aspects of requests/responses, the @@ -113,19 +117,23 @@ //! function: //! //! ``` +//! # #[cfg(feature = "alloc")] { //! use http::HeaderValue; //! //! let value = HeaderValue::from_static("text/html"); //! assert_eq!(value.as_bytes(), b"text/html"); +//! # } //! ``` //! //! And header values can also be parsed like names: //! //! ``` +//! # #[cfg(feature = "alloc")] { //! use http::HeaderValue; //! //! let value = "text/html"; //! let value = value.parse::().unwrap(); +//! # } //! ``` //! //! Most HTTP requests and responses tend to come with more than one header, so @@ -142,6 +150,7 @@ //! interpret it: //! //! ``` +//! # #[cfg(feature = "alloc")] { //! use http::Uri; //! use http::uri::Scheme; //! @@ -151,18 +160,22 @@ //! assert_eq!(uri.host(), Some("www.rust-lang.org")); //! assert_eq!(uri.path(), "/index.html"); //! assert_eq!(uri.query(), None); +//! # } //! ``` #![deny(warnings, missing_docs, missing_debug_implementations)] - -//#![cfg_attr(not(feature = "std"), no_std)] -#[cfg(not(feature = "std"))] -compile_error!("`std` feature currently required, support for `no_std` may be added later"); +#![no_std] #[cfg(test)] #[macro_use] extern crate doc_comment; +#[cfg(any(feature = "alloc", test))] +extern crate alloc; + +#[cfg(any(feature = "std", test))] +extern crate std; + #[cfg(test)] doctest!("../README.md"); @@ -171,27 +184,41 @@ mod convert; pub mod header; pub mod method; +#[cfg(feature = "alloc")] pub mod request; +#[cfg(feature = "alloc")] pub mod response; pub mod status; + +#[cfg(feature = "alloc")] pub mod uri; pub mod version; +#[cfg(feature = "alloc")] mod byte_str; mod error; + +#[cfg(feature = "alloc")] mod extensions; pub use crate::error::{Error, Result}; +#[cfg(feature = "alloc")] pub use crate::extensions::Extensions; +pub use crate::header::HeaderName; #[doc(no_inline)] -pub use crate::header::{HeaderMap, HeaderName, HeaderValue}; +#[cfg(feature = "alloc")] +pub use crate::header::{HeaderMap, HeaderValue}; pub use crate::method::Method; +#[cfg(feature = "alloc")] pub use crate::request::Request; +#[cfg(feature = "alloc")] pub use crate::response::Response; pub use crate::status::StatusCode; +#[cfg(feature = "alloc")] pub use crate::uri::Uri; pub use crate::version::Version; +#[cfg(feature = "alloc")] #[cfg(test)] mod tests { use super::*; diff --git a/src/method.rs b/src/method.rs index 7b4584ab..7450036d 100644 --- a/src/method.rs +++ b/src/method.rs @@ -15,13 +15,15 @@ //! assert_eq!(Method::POST.as_str(), "POST"); //! ``` -use self::extension::{AllocatedExtension, InlineExtension}; +#[cfg(feature = "alloc")] +use self::extension::AllocatedExtension; +use self::extension::InlineExtension; use self::Inner::*; -use std::convert::TryFrom; -use std::error::Error; -use std::str::FromStr; -use std::{fmt, str}; +use core::convert::TryFrom; +use core::error::Error; +use core::str::FromStr; +use core::{fmt, str}; /// The Request Method (VERB) /// @@ -63,6 +65,7 @@ enum Inner { // If the extension is short enough, store it inline ExtensionInline(InlineExtension), // Otherwise, allocate it + #[cfg(feature = "alloc")] ExtensionAllocated(AllocatedExtension), } @@ -126,9 +129,16 @@ impl Method { if src.len() <= InlineExtension::MAX { Method::extension_inline(src) } else { - let allocated = AllocatedExtension::new(src)?; - - Ok(Method(ExtensionAllocated(allocated))) + #[cfg(not(feature = "alloc"))] + { + Err(InvalidMethod::new()) + } + + #[cfg(feature = "alloc")] + { + let allocated = AllocatedExtension::new(src)?; + Ok(Method(ExtensionAllocated(allocated))) + } } } } @@ -175,6 +185,7 @@ impl Method { Connect => "CONNECT", Patch => "PATCH", ExtensionInline(ref inline) => inline.as_str(), + #[cfg(feature = "alloc")] ExtensionAllocated(ref allocated) => allocated.as_str(), } } @@ -306,15 +317,16 @@ impl Error for InvalidMethod {} mod extension { use super::InvalidMethod; - use std::str; + use core::str; #[derive(Clone, PartialEq, Eq, Hash)] // Invariant: the first self.1 bytes of self.0 are valid UTF-8. pub struct InlineExtension([u8; InlineExtension::MAX], u8); + #[cfg(feature = "alloc")] #[derive(Clone, PartialEq, Eq, Hash)] // Invariant: self.0 contains valid UTF-8. - pub struct AllocatedExtension(Box<[u8]>); + pub struct AllocatedExtension(alloc::boxed::Box<[u8]>); impl InlineExtension { // Method::from_bytes() assumes this is at least 7 @@ -338,9 +350,10 @@ mod extension { } } + #[cfg(feature = "alloc")] impl AllocatedExtension { pub fn new(src: &[u8]) -> Result { - let mut data: Vec = vec![0; src.len()]; + let mut data: alloc::vec::Vec = alloc::vec![0; src.len()]; write_checked(src, &mut data)?; @@ -422,6 +435,7 @@ mod extension { #[cfg(test)] mod test { use super::*; + use alloc::string::ToString; #[test] fn test_method_eq() { @@ -463,8 +477,11 @@ mod test { assert_eq!(Method::from_str("WOW").unwrap(), "WOW"); assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!"); - let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely."; - assert_eq!(Method::from_str(long_method).unwrap(), long_method); + #[cfg(feature = "alloc")] + { + 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!( @@ -473,13 +490,18 @@ mod test { 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() - )) - ); + + #[cfg(feature = "alloc")] + { + 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] diff --git a/src/request.rs b/src/request.rs index 324b676c..129322f7 100644 --- a/src/request.rs +++ b/src/request.rs @@ -52,14 +52,14 @@ //! } //! ``` -use std::any::Any; -use std::convert::TryInto; -use std::fmt; +use core::any::Any; +use core::convert::TryInto; +use core::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; use crate::method::Method; use crate::version::Version; -use crate::{Extensions, Result, Uri}; +use crate::{Result, Uri}; /// Represents an HTTP request. /// @@ -179,7 +179,8 @@ pub struct Parts { pub headers: HeaderMap, /// The request's extensions - pub extensions: Extensions, + #[cfg(feature = "alloc")] + pub extensions: crate::Extensions, _priv: (), } @@ -581,8 +582,9 @@ impl Request { /// let request: Request<()> = Request::default(); /// assert!(request.extensions().get::().is_none()); /// ``` + #[cfg(feature = "alloc")] #[inline] - pub fn extensions(&self) -> &Extensions { + pub fn extensions(&self) -> &crate::Extensions { &self.head.extensions } @@ -597,8 +599,9 @@ impl Request { /// request.extensions_mut().insert("hello"); /// assert_eq!(request.extensions().get(), Some(&"hello")); /// ``` + #[cfg(feature = "alloc")] #[inline] - pub fn extensions_mut(&mut self) -> &mut Extensions { + pub fn extensions_mut(&mut self) -> &mut crate::Extensions { &mut self.head.extensions } @@ -714,7 +717,7 @@ impl Parts { uri: Uri::default(), version: Version::default(), headers: HeaderMap::default(), - extensions: Extensions::default(), + extensions: crate::Extensions::default(), _priv: (), } } @@ -991,7 +994,7 @@ impl Builder { /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` - pub fn extensions_ref(&self) -> Option<&Extensions> { + pub fn extensions_ref(&self) -> Option<&crate::Extensions> { self.inner.as_ref().ok().map(|h| &h.extensions) } @@ -1009,7 +1012,7 @@ impl Builder { /// extensions.insert(5u32); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` - pub fn extensions_mut(&mut self) -> Option<&mut Extensions> { + pub fn extensions_mut(&mut self) -> Option<&mut crate::Extensions> { self.inner.as_mut().ok().map(|h| &mut h.extensions) } diff --git a/src/response.rs b/src/response.rs index ab9e49bc..0f381550 100644 --- a/src/response.rs +++ b/src/response.rs @@ -61,14 +61,14 @@ //! // ... //! ``` -use std::any::Any; -use std::convert::TryInto; -use std::fmt; +use core::any::Any; +use core::convert::TryInto; +use core::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; use crate::status::StatusCode; use crate::version::Version; -use crate::{Extensions, Result}; +use crate::Result; /// Represents an HTTP response /// @@ -197,8 +197,9 @@ pub struct Parts { /// The response's headers pub headers: HeaderMap, + #[cfg(feature = "alloc")] /// The response's extensions - pub extensions: Extensions, + pub extensions: crate::Extensions, _priv: (), } @@ -374,8 +375,9 @@ impl Response { /// let response: Response<()> = Response::default(); /// assert!(response.extensions().get::().is_none()); /// ``` + #[cfg(feature = "alloc")] #[inline] - pub fn extensions(&self) -> &Extensions { + pub fn extensions(&self) -> &crate::Extensions { &self.head.extensions } @@ -390,8 +392,9 @@ impl Response { /// response.extensions_mut().insert("hello"); /// assert_eq!(response.extensions().get(), Some(&"hello")); /// ``` + #[cfg(feature = "alloc")] #[inline] - pub fn extensions_mut(&mut self) -> &mut Extensions { + pub fn extensions_mut(&mut self) -> &mut crate::Extensions { &mut self.head.extensions } @@ -506,7 +509,8 @@ impl Parts { status: StatusCode::default(), version: Version::default(), headers: HeaderMap::default(), - extensions: Extensions::default(), + #[cfg(feature = "alloc")] + extensions: crate::Extensions::default(), _priv: (), } } @@ -681,6 +685,7 @@ impl Builder { /// assert_eq!(response.extensions().get::<&'static str>(), /// Some(&"My Extension")); /// ``` + #[cfg(feature = "alloc")] pub fn extension(self, extension: T) -> Builder where T: Clone + Any + Send + Sync + 'static, @@ -704,7 +709,8 @@ impl Builder { /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` - pub fn extensions_ref(&self) -> Option<&Extensions> { + #[cfg(feature = "alloc")] + pub fn extensions_ref(&self) -> Option<&crate::Extensions> { self.inner.as_ref().ok().map(|h| &h.extensions) } @@ -722,7 +728,8 @@ impl Builder { /// extensions.insert(5u32); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` - pub fn extensions_mut(&mut self) -> Option<&mut Extensions> { + #[cfg(feature = "alloc")] + pub fn extensions_mut(&mut self) -> Option<&mut crate::Extensions> { self.inner.as_mut().ok().map(|h| &mut h.extensions) } diff --git a/src/status.rs b/src/status.rs index 9ad04d20..f9df39a4 100644 --- a/src/status.rs +++ b/src/status.rs @@ -14,11 +14,11 @@ //! assert!(StatusCode::OK.is_success()); //! ``` -use std::convert::TryFrom; -use std::error::Error; -use std::fmt; -use std::num::NonZeroU16; -use std::str::FromStr; +use core::convert::TryFrom; +use core::error::Error; +use core::fmt; +use core::num::NonZeroU16; +use core::str::FromStr; /// An HTTP status code (`status-code` in RFC 9110 et al.). /// @@ -267,7 +267,7 @@ impl FromStr for StatusCode { impl<'a> From<&'a StatusCode> for StatusCode { #[inline] fn from(t: &'a StatusCode) -> Self { - t.to_owned() + *t } } diff --git a/src/uri/authority.rs b/src/uri/authority.rs index 07aa6795..82640d52 100644 --- a/src/uri/authority.rs +++ b/src/uri/authority.rs @@ -1,7 +1,7 @@ -use std::convert::TryFrom; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; -use std::{cmp, fmt, str}; +use core::convert::TryFrom; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; +use core::{cmp, fmt, str}; use bytes::Bytes; @@ -21,6 +21,7 @@ impl Authority { } } + #[cfg(feature = "alloc")] // Not public while `bytes` is unstable. pub(super) fn from_shared(s: Bytes) -> Result { // Precondition on create_authority: trivially satisfied by the @@ -314,13 +315,15 @@ impl<'a> PartialEq<&'a str> for Authority { } } -impl PartialEq for Authority { - fn eq(&self, other: &String) -> bool { +#[cfg(feature = "alloc")] +impl PartialEq for Authority { + fn eq(&self, other: &alloc::string::String) -> bool { self.data.eq_ignore_ascii_case(other.as_str()) } } -impl PartialEq for String { +#[cfg(feature = "alloc")] +impl PartialEq for alloc::string::String { fn eq(&self, other: &Authority) -> bool { self.as_str().eq_ignore_ascii_case(other.as_str()) } @@ -376,15 +379,17 @@ impl<'a> PartialOrd<&'a str> for Authority { } } -impl PartialOrd for Authority { - fn partial_cmp(&self, other: &String) -> Option { +#[cfg(feature = "alloc")] +impl PartialOrd for Authority { + fn partial_cmp(&self, other: &alloc::string::String) -> Option { let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } -impl PartialOrd for String { +#[cfg(feature = "alloc")] +impl PartialOrd for alloc::string::String { fn partial_cmp(&self, other: &Authority) -> Option { let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); @@ -446,20 +451,22 @@ impl<'a> TryFrom<&'a str> for Authority { } } -impl TryFrom> for Authority { +#[cfg(feature = "alloc")] +impl TryFrom> for Authority { type Error = InvalidUri; #[inline] - fn try_from(vec: Vec) -> Result { + fn try_from(vec: alloc::vec::Vec) -> Result { Authority::from_shared(vec.into()) } } -impl TryFrom for Authority { +#[cfg(feature = "alloc")] +impl TryFrom for Authority { type Error = InvalidUri; #[inline] - fn try_from(t: String) -> Result { + fn try_from(t: alloc::string::String) -> Result { Authority::from_shared(t.into()) } } @@ -531,6 +538,7 @@ where #[cfg(test)] mod tests { use super::*; + use alloc::string::ToString; #[test] fn parse_empty_string_is_error() { diff --git a/src/uri/builder.rs b/src/uri/builder.rs index d5f7f49b..0ce39e29 100644 --- a/src/uri/builder.rs +++ b/src/uri/builder.rs @@ -1,4 +1,4 @@ -use std::convert::TryInto; +use core::convert::TryInto; use super::{Authority, Parts, PathAndQuery, Scheme}; use crate::Uri; @@ -163,6 +163,7 @@ impl From for Builder { #[cfg(test)] mod tests { use super::*; + use alloc::format; #[test] fn build_from_str() { diff --git a/src/uri/mod.rs b/src/uri/mod.rs index 767f0743..50b1ce1c 100644 --- a/src/uri/mod.rs +++ b/src/uri/mod.rs @@ -22,15 +22,17 @@ //! assert_eq!(uri.path(), "/install.html"); //! ``` -use crate::byte_str::ByteStr; -use std::convert::TryFrom; +use alloc::boxed::Box; +use core::convert::TryFrom; +use core::error::Error; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; +use core::str::{self}; use bytes::Bytes; -use std::error::Error; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::str::{self, FromStr}; +use crate::byte_str::ByteStr; use self::scheme::Scheme2; @@ -723,29 +725,32 @@ impl<'a> TryFrom<&'a str> for Uri { } } -impl<'a> TryFrom<&'a String> for Uri { +#[cfg(feature = "alloc")] +impl<'a> TryFrom<&'a alloc::string::String> for Uri { type Error = InvalidUri; #[inline] - fn try_from(t: &'a String) -> Result { + fn try_from(t: &'a alloc::string::String) -> Result { t.parse() } } -impl TryFrom for Uri { +#[cfg(feature = "alloc")] +impl TryFrom for Uri { type Error = InvalidUri; #[inline] - fn try_from(t: String) -> Result { + fn try_from(t: alloc::string::String) -> Result { Uri::from_shared(Bytes::from(t)) } } -impl TryFrom> for Uri { +#[cfg(feature = "alloc")] +impl TryFrom> for Uri { type Error = InvalidUri; #[inline] - fn try_from(vec: Vec) -> Result { + fn try_from(vec: alloc::vec::Vec) -> Result { Uri::from_shared(Bytes::from(vec)) } } diff --git a/src/uri/path.rs b/src/uri/path.rs index df00c415..d53bb796 100644 --- a/src/uri/path.rs +++ b/src/uri/path.rs @@ -1,6 +1,7 @@ -use std::convert::TryFrom; -use std::str::FromStr; -use std::{cmp, fmt, hash, str}; +use core::convert::TryFrom; +use core::hash::Hash; +use core::str::FromStr; +use core::{cmp, fmt, hash, str}; use bytes::Bytes; @@ -294,26 +295,29 @@ impl<'a> TryFrom<&'a str> for PathAndQuery { } } -impl TryFrom> for PathAndQuery { +#[cfg(feature = "alloc")] +impl TryFrom> for PathAndQuery { type Error = InvalidUri; #[inline] - fn try_from(vec: Vec) -> Result { + fn try_from(vec: alloc::vec::Vec) -> Result { PathAndQuery::from_shared(vec.into()) } } -impl TryFrom for PathAndQuery { +#[cfg(feature = "alloc")] +impl TryFrom for PathAndQuery { type Error = InvalidUri; #[inline] - fn try_from(s: String) -> Result { + fn try_from(s: alloc::string::String) -> Result { PathAndQuery::from_shared(s.into()) } } -impl TryFrom<&String> for PathAndQuery { +#[cfg(feature = "alloc")] +impl TryFrom<&alloc::string::String> for PathAndQuery { type Error = InvalidUri; #[inline] - fn try_from(s: &String) -> Result { + fn try_from(s: &alloc::string::String) -> Result { TryFrom::try_from(s.as_bytes()) } } @@ -345,7 +349,7 @@ impl fmt::Display for PathAndQuery { } } -impl hash::Hash for PathAndQuery { +impl Hash for PathAndQuery { fn hash(&self, state: &mut H) { self.data.hash(state); } @@ -390,14 +394,16 @@ impl PartialEq for str { } } -impl PartialEq for PathAndQuery { +#[cfg(feature = "alloc")] +impl PartialEq for PathAndQuery { #[inline] - fn eq(&self, other: &String) -> bool { + fn eq(&self, other: &alloc::string::String) -> bool { self.as_str() == other.as_str() } } -impl PartialEq for String { +#[cfg(feature = "alloc")] +impl PartialEq for alloc::string::String { #[inline] fn eq(&self, other: &PathAndQuery) -> bool { self.as_str() == other.as_str() @@ -439,14 +445,16 @@ impl<'a> PartialOrd for &'a str { } } -impl PartialOrd for PathAndQuery { +#[cfg(feature = "alloc")] +impl PartialOrd for PathAndQuery { #[inline] - fn partial_cmp(&self, other: &String) -> Option { + fn partial_cmp(&self, other: &alloc::string::String) -> Option { self.as_str().partial_cmp(other.as_str()) } } -impl PartialOrd for String { +#[cfg(feature = "alloc")] +impl PartialOrd for alloc::string::String { #[inline] fn partial_cmp(&self, other: &PathAndQuery) -> Option { self.as_str().partial_cmp(other.as_str()) @@ -456,6 +464,8 @@ impl PartialOrd for String { #[cfg(test)] mod tests { use super::*; + use alloc::format; + use alloc::string::ToString; #[test] fn equal_to_self_of_same_path() { diff --git a/src/uri/port.rs b/src/uri/port.rs index 2a7028e2..fac08e68 100644 --- a/src/uri/port.rs +++ b/src/uri/port.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; use super::{ErrorKind, InvalidUri}; @@ -128,7 +128,7 @@ mod tests { port: 8081, }; let port_b = Port { - repr: String::from("8081"), + repr: alloc::string::String::from("8081"), port: 8081, }; assert_eq!(port_a, port_b); diff --git a/src/uri/scheme.rs b/src/uri/scheme.rs index dbcc8c3f..d730e872 100644 --- a/src/uri/scheme.rs +++ b/src/uri/scheme.rs @@ -1,12 +1,9 @@ -use std::convert::TryFrom; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; - -use bytes::Bytes; +use core::convert::TryFrom; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; use super::{ErrorKind, InvalidUri}; -use crate::byte_str::ByteStr; /// Represents the scheme component of a URI #[derive(Clone)] @@ -14,8 +11,13 @@ pub struct Scheme { pub(super) inner: Scheme2, } +#[cfg(feature = "alloc")] +type DefaultScheme = alloc::boxed::Box; +#[cfg(not(feature = "alloc"))] +type DefaultScheme = core::convert::Infallible; + #[derive(Clone, Debug)] -pub(super) enum Scheme2> { +pub(super) enum Scheme2 { None, Standard(Protocol), Other(T), @@ -61,8 +63,11 @@ impl Scheme { match self.inner { Standard(Http) => "http", Standard(Https) => "https", + + #[cfg(feature = "alloc")] Other(ref v) => &v[..], - None => unreachable!(), + + _ => unreachable!(), } } } @@ -76,15 +81,20 @@ impl<'a> TryFrom<&'a [u8]> for Scheme { match Scheme2::parse_exact(s)? { None => Err(ErrorKind::InvalidScheme.into()), Standard(p) => Ok(Standard(p).into()), + + #[cfg(feature = "alloc")] Other(_) => { - let bytes = Bytes::copy_from_slice(s); + let bytes = bytes::Bytes::copy_from_slice(s); // Safety: postcondition on parse_exact() means that s and // hence bytes are valid UTF-8. - let string = unsafe { ByteStr::from_utf8_unchecked(bytes) }; + let string = unsafe { crate::byte_str::ByteStr::from_utf8_unchecked(bytes) }; - Ok(Other(Box::new(string)).into()) + Ok(Other(alloc::boxed::Box::new(string)).into()) } + + #[cfg(not(feature = "alloc"))] + _ => unreachable!(), } } } @@ -132,7 +142,10 @@ impl PartialEq for Scheme { match (&self.inner, &other.inner) { (&Standard(Http), &Standard(Http)) => true, (&Standard(Https), &Standard(Https)) => true, + #[cfg(feature = "alloc")] (Other(a), Other(b)) => a.eq_ignore_ascii_case(b), + #[cfg(not(feature = "alloc"))] + (Other(a), Other(b)) => a == b, (&None, _) | (_, &None) => unreachable!(), _ => false, } @@ -173,12 +186,15 @@ impl Hash for Scheme { Scheme2::None => (), Scheme2::Standard(Protocol::Http) => state.write_u8(1), Scheme2::Standard(Protocol::Https) => state.write_u8(2), + #[cfg(feature = "alloc")] Scheme2::Other(ref other) => { other.len().hash(state); for &b in other.as_bytes() { state.write_u8(b.to_ascii_lowercase()); } } + #[cfg(not(feature = "alloc"))] + _ => (), } } } @@ -338,6 +354,7 @@ impl From for Scheme { #[cfg(test)] mod test { use super::*; + use alloc::format; #[test] fn scheme_eq_to_str() { diff --git a/src/uri/tests.rs b/src/uri/tests.rs index 719cb94e..939f78b9 100644 --- a/src/uri/tests.rs +++ b/src/uri/tests.rs @@ -1,4 +1,6 @@ -use std::str::FromStr; +use alloc::string::ToString; +use alloc::vec; +use core::str::FromStr; use super::{ErrorKind, InvalidUri, Port, Uri, URI_CHARS}; @@ -442,11 +444,11 @@ fn test_uri_parse_error() { #[test] fn test_max_uri_len() { - let mut uri = vec![]; + let mut uri = alloc::vec![]; uri.extend(b"http://localhost/"); - uri.extend(vec![b'a'; 70 * 1024]); + uri.extend([b'a'; 70 * 1024]); - let uri = String::from_utf8(uri).unwrap(); + let uri = core::str::from_utf8(&uri).unwrap(); let res: Result = uri.parse(); assert_eq!(res.unwrap_err().0, ErrorKind::TooLong); @@ -454,11 +456,11 @@ fn test_max_uri_len() { #[test] fn test_overflowing_scheme() { - let mut uri = vec![]; - uri.extend(vec![b'a'; 256]); + let mut uri = alloc::vec![]; + uri.extend([b'a'; 256]); uri.extend(b"://localhost/"); - let uri = String::from_utf8(uri).unwrap(); + let uri = core::str::from_utf8(&uri).unwrap(); let res: Result = uri.parse(); assert_eq!(res.unwrap_err().0, ErrorKind::SchemeTooLong); @@ -466,11 +468,11 @@ fn test_overflowing_scheme() { #[test] fn test_max_length_scheme() { - let mut uri = vec![]; - uri.extend(vec![b'a'; 64]); + let mut uri = alloc::vec![]; + uri.extend([b'a'; 64]); uri.extend(b"://localhost/"); - let uri = String::from_utf8(uri).unwrap(); + let uri = core::str::from_utf8(&uri).unwrap(); let uri: Uri = uri.parse().unwrap(); assert_eq!(uri.scheme_str().unwrap().len(), 64); diff --git a/src/version.rs b/src/version.rs index d8b71306..436ca7df 100644 --- a/src/version.rs +++ b/src/version.rs @@ -19,7 +19,7 @@ //! println!("{:?}", http2); //! ``` -use std::fmt; +use core::fmt; /// Represents a version of the HTTP spec. #[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash)]