diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 4bd4d2ee301217b33d1676edff2cc3b4858743a0..8234ab46ee914dd92e9666d390d7c44ac5b54384 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,6 +1,17 @@ -use std::{error::Error, str::FromStr, time::Duration}; +use std::{collections::HashMap, str::FromStr, time::Duration}; -use crate::{handling::response::CookieBuilder, utils::urlencoded::DeCodable}; +use crate::{ + handling::response::{cookie_management::error_types::CookieError, CookieBuilder}, + utils::{url_utils::Uri, urlencoded::DeCodable}, +}; + +macro_rules! update_map { + ($map:expr, $key:expr) => { + *$map.get_mut($key).unwrap() = true; + }; +} + +use super::error_types::{ParseCookieError, ParseSameSiteError, SameSiteError}; /// Structure representing a Cookie /// # Creating a Cookie: @@ -17,20 +28,20 @@ pub struct Cookie { pub(crate) cookie_string: Option<String>, pub(crate) name: String, pub(crate) value: String, - // expires: Option<Tm>, - pub(crate) max_age: Option<Duration>, - /// The cookie's domain, if any. - pub(crate) domain: Option<String>, - /// The cookie's path domain, if any. - pub(crate) path: Option<String>, /// Whether this cookie was marked Secure. pub(crate) secure: bool, /// Whether this cookie was marked HttpOnly. pub(crate) http_only: bool, + pub(crate) partitioned: bool, + // expires: Option<Tm>, + pub(crate) max_age: Option<Duration>, /// The draft `SameSite` attribute. pub(crate) same_site: Option<SameSite>, + /// The cookie's domain, if any. + pub(crate) domain: Option<String>, + /// The cookie's path domain, if any. + pub(crate) path: Option<Uri>, pub(crate) expires: Option<String>, - pub(crate) partitioned: bool, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -52,6 +63,21 @@ impl std::fmt::Display for SameSite { } } +impl FromStr for SameSite { + type Err = ParseSameSiteError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "None" => Ok(SameSite::None), + "Lax" => Ok(SameSite::Lax), + "Strict" => Ok(SameSite::Strict), + _ => Err(Self::Err { + inner: SameSiteError::NotAValidVariant, + }), + } + } +} + impl std::fmt::Display for Cookie { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut appendix = String::from(""); @@ -86,31 +112,24 @@ impl std::fmt::Display for Cookie { } } -impl Error for ParseCookieError {} - -#[derive(Debug)] -pub struct ParseCookieError { - inner: CookieError, -} - -#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum CookieError { - MissingEqual, - InvalidAttribute, -} - -impl std::fmt::Display for ParseCookieError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ParseCookieError {{ error: {:?} }}", self.inner) - } -} - impl FromStr for Cookie { type Err = ParseCookieError; fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut map = HashMap::with_capacity(8); + map.insert("Partitioned", false); + map.insert("HttpOnly", false); + map.insert("Secure", false); + map.insert("SameSite", false); + map.insert("Max-Age", false); + map.insert("Domain", false); + map.insert("Path", false); + map.insert("Expires", false); let mut final_result = CookieBuilder::build("", ""); let mut first = true; - for part in s.split(';') { + let stripped = s.strip_prefix("Set-Cookie: ").ok_or(Self::Err { + inner: CookieError::NoSetCookieHeader, + })?; + for part in stripped.split(';') { let trimmed_part = part.trim(); if first { let Some(name_val) = part.split_once('=') else { @@ -131,20 +150,74 @@ impl FromStr for Cookie { ); } first = false; - break; + continue; } - final_result = match trimmed_part { - "Secure" => final_result.secure(true), - "HttpOnly" => final_result.http_only(true), - "Partitioned" => final_result.partitioned(true), - _ => { - return Err(Self::Err { - inner: CookieError::InvalidAttribute, - }); - } + if !map.get("Max-Age").unwrap() && trimmed_part.starts_with("Max-Age=") { + final_result = final_result.max_age(Duration::from_secs( + trimmed_part + .strip_prefix("Max-Age=") + .unwrap() + .parse() + .map_err(|_| Self::Err { + inner: CookieError::InvalidMaxAge, + })?, + )); + update_map!(map, "Max-Age"); + continue; + } + if !map.get("Expires").unwrap() && trimmed_part.starts_with("Expires=") { + final_result = final_result.expires(trimmed_part.strip_prefix("Expires=").unwrap()); + update_map!(map, "Expires"); + continue; + } + if !map.get("HttpOnly").unwrap() && trimmed_part == "HttpOnly" { + final_result = final_result.http_only(true); + update_map!(map, "HttpOnly"); + continue; + } + if !map.get("SameSite").unwrap() && trimmed_part.starts_with("SameSite=") { + final_result = final_result.same_site( + trimmed_part + .strip_prefix("SameSite=") + .unwrap() + .parse::<SameSite>() + .map_err(|err| Self::Err { + inner: CookieError::InvilidSameSite(err.inner), + })?, + ); + update_map!(map, "SameSite"); + continue; + } + if !map.get("Path").unwrap() && trimmed_part.starts_with("Path=") { + final_result = final_result.path( + trimmed_part + .strip_prefix("Path=") + .unwrap() + .parse::<Uri>() + .map_err(|err| Self::Err { + inner: CookieError::PathError(err.error), + })?, + ); + update_map!(map, "Path"); + continue; + } + if !map.get("Domain").unwrap() && trimmed_part.starts_with("Domain=") { + final_result = final_result.domain(trimmed_part.strip_prefix("Domain=").unwrap()); + update_map!(map, "Domain"); + continue; + } + if !map.get("Secure").unwrap() && trimmed_part == "Secure" { + final_result = final_result.secure(true); + update_map!(map, "Secure"); + continue; + } + + if !map.get("Partitioned").unwrap() && trimmed_part == "Partitioned" { + final_result = final_result.partitioned(true); + update_map!(map, "Partitioned"); + continue; } } - println!("{:?}", final_result); Ok(final_result.finish()) } } @@ -153,7 +226,7 @@ impl FromStr for Cookie { mod test { use std::time::Duration; - use crate::handling::response::CookieBuilder; + use crate::handling::response::{Cookie, CookieBuilder}; use super::SameSite; @@ -169,19 +242,32 @@ mod test { let test_cookie3 = CookieBuilder::build("ab", "ss") .max_age(Duration::from_secs(24)) .domain("codecraft.com") - .path("/") + .path("/".parse().unwrap()) .same_site(SameSite::None) .http_only(true) .partitioned(true) .expires("Monday") .finish(); - let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ - Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; + let test_cookie3_res = "Set-Cookie: ab=ss; Secure; HttpOnly; Partitioned; \ + Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Expires=Monday"; assert_eq!(test_cookie1_res, test_cookie1); assert_eq!(test_cookie2_res, test_cookie2); assert_eq!(test_cookie3_res, test_cookie3.to_string()); } #[test] - fn cooki_from_string() {} + fn cookie_from_string() { + let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ + Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; + let test_cookie3 = CookieBuilder::build("ab", "ss") + .max_age(Duration::from_secs(24)) + .domain("codecraft.com") + .path("/".parse().unwrap()) + .same_site(SameSite::None) + .http_only(true) + .partitioned(true) + .expires("Monday") + .finish(); + assert_eq!(test_cookie3, test_cookie3_res.parse::<Cookie>().unwrap()); + } } diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index 5a3dac31730d2a2366fbd6453417f91e51b82619..269925787f17fcbef64e9760f0795e1f935464e9 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::utils::urlencoded::EnCodable; +use crate::utils::{url_utils::Uri, urlencoded::EnCodable}; use super::{Cookie, SameSite}; @@ -11,7 +11,7 @@ use super::{Cookie, SameSite}; /// use http::handling::response::Cookie; /// use http::handling::response::CookieBuilder; /// -/// let cookie = CookieBuilder::build("name", "value").path("/").finish(); +/// let cookie = CookieBuilder::build("name", "value").path("/".parse().unwrap()).finish(); /// ``` #[derive(Debug)] pub struct CookieBuilder { @@ -49,8 +49,8 @@ impl CookieBuilder { self.inner.domain = Some(domain.encode()); self } - pub fn path(mut self, path: &str) -> Self { - self.inner.path = Some(path.encode()); + pub fn path(mut self, path: Uri) -> Self { + self.inner.path = Some(path); self } pub fn secure(mut self, secure: bool) -> Self { @@ -62,6 +62,9 @@ impl CookieBuilder { self } pub fn same_site(mut self, same_site: SameSite) -> Self { + if same_site == SameSite::None { + self.inner.secure = true; + } self.inner.same_site = Some(same_site); self } diff --git a/core/http/src/handling/response/cookie_management/error_types.rs b/core/http/src/handling/response/cookie_management/error_types.rs new file mode 100644 index 0000000000000000000000000000000000000000..12277c05d92924bdb73f558b1df5ff7a498b8e34 --- /dev/null +++ b/core/http/src/handling/response/cookie_management/error_types.rs @@ -0,0 +1,43 @@ +use std::error::Error; + +use crate::utils::url_utils::UriError; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum SameSiteError { + NotAValidVariant, +} + +#[derive(Debug)] +pub struct ParseSameSiteError { + pub inner: SameSiteError, +} + +impl Error for ParseSameSiteError {} + +impl std::fmt::Display for ParseSameSiteError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} +impl Error for ParseCookieError {} + +#[derive(Debug)] +pub struct ParseCookieError { + pub inner: CookieError, +} + +#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum CookieError { + MissingEqual, + InvalidAttribute, + InvalidMaxAge, + NoSetCookieHeader, + InvilidSameSite(SameSiteError), + PathError(UriError), +} + +impl std::fmt::Display for ParseCookieError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ParseCookieError {{ error: {:?} }}", self.inner) + } +} diff --git a/core/http/src/handling/response/cookie_management/mod.rs b/core/http/src/handling/response/cookie_management/mod.rs index 50f34ea4c9ed80fdaf39a474f764b8de1f0790b6..5681658fbc6aa339cde0466a7f96bc56af7a22a2 100644 --- a/core/http/src/handling/response/cookie_management/mod.rs +++ b/core/http/src/handling/response/cookie_management/mod.rs @@ -1,6 +1,11 @@ mod cookie; mod cookie_builder; +mod error_types; pub use cookie::Cookie; pub use cookie::SameSite; pub use cookie_builder::CookieBuilder; +pub use error_types::CookieError; +pub use error_types::ParseCookieError; +pub use error_types::ParseSameSiteError; +pub use error_types::SameSiteError; diff --git a/core/http/src/utils/url_utils/datatypes.rs b/core/http/src/utils/url_utils/datatypes.rs index 3c4003770906612619b50904e1f2c2f15f0263b5..6e360807ec57cfb988d5e4f2664873f72791b807 100644 --- a/core/http/src/utils/url_utils/datatypes.rs +++ b/core/http/src/utils/url_utils/datatypes.rs @@ -20,14 +20,14 @@ pub enum RawUriElement { Name(UrlEncodeData), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum UriError { InvalidUriEncoding, } #[derive(Debug)] pub struct ParseUriError { - error: UriError, + pub error: UriError, } impl std::fmt::Display for ParseUriError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {