diff --git a/ohkami/src/request/_test_parse.rs b/ohkami/src/request/_test_parse.rs index 7a089ae3e..d2b183df8 100644 --- a/ohkami/src/request/_test_parse.rs +++ b/ohkami/src/request/_test_parse.rs @@ -11,15 +11,15 @@ use ohkami_lib::{Slice, CowSlice}; fn parse_path() { let mut path = Path::uninit(); path.init_with_request_bytes(b"/abc").unwrap(); - assert_eq!(&*path, "/abc"); + assert_eq!(path.str(), "/abc"); let mut path = Path::uninit(); path.init_with_request_bytes(b"/abc/").unwrap(); - assert_eq!(&*path, "/abc"); + assert_eq!(path.str(), "/abc"); let mut path = Path::uninit(); path.init_with_request_bytes(b"/").unwrap(); - assert_eq!(&*path, "/"); + assert_eq!(path.str(), "/"); } macro_rules! assert_parse { diff --git a/ohkami/src/request/headers.rs b/ohkami/src/request/headers.rs index 0e991fbdf..505efdc26 100644 --- a/ohkami/src/request/headers.rs +++ b/ohkami/src/request/headers.rs @@ -202,7 +202,7 @@ macro_rules! Header { let standard = Header::from_bytes(name.as_bytes())?; unsafe {self.standard.get(standard as usize)} })?; - Some(std::str::from_utf8(unsafe {value.as_bytes()}).expect("Header value is not UTF-8")) + std::str::from_utf8(unsafe {value.as_bytes()}).ok() } } @@ -279,16 +279,15 @@ impl Headers { pub(crate) fn iter(&self) -> impl Iterator { self.standard.iter() - .map(|(i, v)| ( + .filter_map(|(i, v)| Some(( unsafe {std::mem::transmute::<_, Header>(i as u8).as_str()}, - std::str::from_utf8(v).expect("Non UTF-8 header value") - )) - .chain(self.custom.as_ref() - .into_iter() - .flat_map(|hm| hm.iter().map(|(k, v)| ( - std::str::from_utf8(unsafe {k.as_bytes()}).expect("Header value is not UTF-8"), - std::str::from_utf8(unsafe {v.as_bytes()}).expect("Header value is not UTF-8"), - ))) + std::str::from_utf8(v).ok()? + ))) + .chain(self.custom.as_ref().into_iter() + .flat_map(|hm| hm.iter().filter_map(|(k, v)| Some(( + std::str::from_utf8(unsafe {k.as_bytes()}).ok()?, + std::str::from_utf8(unsafe {v.as_bytes()}).ok()?, + )))) ) .chain(self.cookie().map(|c| ("Cookie", c))) } @@ -309,7 +308,7 @@ impl Headers { #[inline] pub(crate) fn get_standard(&self, name: Header) -> Option<&str> { unsafe {match self.standard.get(name as usize) { - Some(cs) => Some(std::str::from_utf8(&cs).expect("non UTF-8 header value")), + Some(cs) => std::str::from_utf8(&cs).ok(), None => None }} } diff --git a/ohkami/src/request/path.rs b/ohkami/src/request/path.rs index 85adf3716..3c449387b 100644 --- a/ohkami/src/request/path.rs +++ b/ohkami/src/request/path.rs @@ -1,7 +1,6 @@ use std::{borrow::Cow, mem::MaybeUninit}; use ohkami_lib::{percent_decode_utf8, Slice}; - pub struct Path( MaybeUninit ); @@ -30,25 +29,37 @@ const _: () = { impl Path { pub fn params(&self) -> impl Iterator> { - unsafe {self.0.assume_init_ref()} - .params.iter() + (unsafe {self.0.assume_init_ref()}) + .params + .iter() .map(|slice| percent_decode_utf8(unsafe {slice.as_bytes()}) - .expect("Non UTF-8 path params")) + .unwrap_or_else(|_| String::from_utf8_lossy(unsafe {slice.as_bytes()})) + ) } + #[inline] + pub fn as_bytes(&self) -> &[u8] { + let bytes = unsafe {self.0.assume_init_ref().raw.as_bytes()}; + if bytes.is_empty() {b"/"} else {bytes} + } /// Get request path as `Cow::Borrowed(&str)` if it's not percent-encoded, or, /// decode it into `Cow::Owned(String)` if encoded in the original request. + /// + /// Or if the path is not valid UTF-8, it returns `Cow::Owned(String)` with + /// lossy conversion. #[inline] pub fn str(&self) -> Cow<'_, str> { let bytes = unsafe {self.0.assume_init_ref().raw.as_bytes()}; if bytes.is_empty() {return Cow::Borrowed("/")} - percent_decode_utf8(bytes).expect("Non UTF-8 path params") + percent_decode_utf8(bytes).unwrap_or_else(|_| String::from_utf8_lossy(bytes)) } - #[inline] pub(crate) unsafe fn assume_one_param<'p>(&self) -> &'p [u8] { + #[inline] + pub(crate) unsafe fn assume_one_param<'p>(&self) -> &'p [u8] { unsafe {self.0.assume_init_ref().params.list.get_unchecked(0).assume_init_ref().as_bytes()} } - #[inline] pub(crate) unsafe fn assume_two_params<'p>(&self) -> (&'p [u8], &'p [u8]) { + #[inline] + pub(crate) unsafe fn assume_two_params<'p>(&self) -> (&'p [u8], &'p [u8]) { unsafe {( self.0.assume_init_ref().params.list.get_unchecked(0).assume_init_ref().as_bytes(), self.0.assume_init_ref().params.list.get_unchecked(1).assume_init_ref().as_bytes() @@ -56,22 +67,6 @@ const _: () = { } } - impl std::ops::Deref for Path { - type Target = str; - #[inline] - fn deref(&self) -> &Self::Target { - self.as_ref() - } - } - impl AsRef for Path { - #[inline] - fn as_ref(&self) -> &str { - let bytes = &unsafe {self.0.assume_init_ref().raw.as_bytes()}; - if bytes.is_empty() {return "/"} - std::str::from_utf8(bytes).expect("Non UTF-8 path params") - } - } - impl std::fmt::Debug for Path { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { as std::fmt::Debug>::fmt(&self.str(), f) diff --git a/ohkami/src/request/query.rs b/ohkami/src/request/query.rs index 21d96cdfa..e083515bf 100644 --- a/ohkami/src/request/query.rs +++ b/ohkami/src/request/query.rs @@ -1,7 +1,5 @@ use std::borrow::Cow; -use ohkami_lib::percent_decode; -use super::Slice; - +use ohkami_lib::{percent_decode_utf8, Slice}; #[derive(PartialEq)] pub struct QueryParams( @@ -23,17 +21,7 @@ impl QueryParams { ohkami_lib::serde_urlencoded::from_bytes(unsafe {self.0.as_bytes()}) } - pub fn iter(&self) -> impl Iterator< - Item = (Cow<'_, str>, Cow<'_, str>) - > { - #[inline(always)] - fn decoded_utf8(maybe_encoded: &[u8]) -> Cow<'_, str> { - match percent_decode(maybe_encoded) { - Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes), - Cow::Owned(vec) => String::from_utf8_lossy(&vec).into_owned().into(), - } - } - + pub fn iter(&self) -> impl Iterator, Cow<'_, str>)> { let bytes = unsafe {self.0.as_bytes()}; (if bytes.is_empty() {None} else {Some( bytes @@ -57,8 +45,8 @@ impl QueryParams { None } Some(n) => Some(( - decoded_utf8(unsafe {kv.get_unchecked(..n)}), - decoded_utf8(unsafe {kv.get_unchecked(n+1..)}) + percent_decode_utf8(unsafe {kv.get_unchecked(..n)}).ok()?, + percent_decode_utf8(unsafe {kv.get_unchecked(n+1..)}).ok()? )) } })