diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 58d2a0d648b47de5fd191f62119a8c9519054ea3..3005eb301f08bfb42a5392bea92b81093aec6ee6 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,4 +1,6 @@ -use std::time::Duration; +use std::{error::Error, str::FromStr, time::Duration}; + +use crate::handling::response::CookieBuilder; /// Structure representing a Cookie /// # Creating a Cookie: @@ -21,16 +23,16 @@ pub struct Cookie { /// The cookie's path domain, if any. pub(crate) path: Option<String>, /// Whether this cookie was marked Secure. - pub(crate) secure: Option<bool>, + pub(crate) secure: bool, /// Whether this cookie was marked HttpOnly. - pub(crate) http_only: Option<bool>, + pub(crate) http_only: bool, /// The draft `SameSite` attribute. pub(crate) same_site: Option<SameSite>, pub(crate) expires: Option<String>, - pub(crate) partitioned: Option<bool>, + pub(crate) partitioned: bool, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// SameSite Paremeters pub enum SameSite { /// Requires Secure @@ -42,7 +44,7 @@ pub enum SameSite { impl std::fmt::Display for SameSite { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::None => write!(f, "SameSite=None; Secure"), + Self::None => write!(f, "SameSite=None"), Self::Lax => write!(f, "SameSite=Lax"), Self::Strict => write!(f, "SameSite=Strict"), } @@ -51,6 +53,95 @@ impl std::fmt::Display for SameSite { impl std::fmt::Display for Cookie { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut appendix = String::from(""); + if self.secure { + appendix += "; Secure"; + } + if self.http_only { + appendix += "; HttpOnly"; + } + if self.partitioned { + appendix += "; Partitioned"; + } + if let Some(max_age) = &self.max_age { + appendix += &format!("; Max-Age={}", max_age.as_secs()); + } + if let Some(domain) = &self.domain { + appendix += &format!("; Domain={}", domain); + } + if let Some(path) = &self.path { + appendix += &format!("; Path={}", path); + } + if let Some(same_site) = &self.same_site { + appendix += &format!("; {}", same_site); + if !self.secure && *same_site == SameSite::None { + appendix += &format!("; Secure"); + } + } + if let Some(expires) = &self.expires { + appendix += &format!("; Expires={}", expires) + } + write!(f, "Set-Cookie: {}={}{}", self.name, self.value, appendix) + } +} + +impl Error for ParseCookieError {} + +#[derive(Debug)] +pub struct ParseCookieError { + inner: CookieError, +} + +#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum CookieError { + MissingEqual, +} + +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> { todo!() } } + +#[cfg(test)] +mod test { + use std::time::Duration; + + use crate::handling::response::CookieBuilder; + + use super::SameSite; + + #[test] + fn test_cookie_to_string() { + let test_cookie1 = CookieBuilder::build("a", "cookie").finish().to_string(); + let test_cookie1_res = "Set-Cookie: a=cookie"; + let test_cookie2 = CookieBuilder::build("a", "secure_cookie") + .secure(true) + .finish() + .to_string(); + let test_cookie2_res = "Set-Cookie: a=secure_cookie; Secure"; + let test_cookie3 = CookieBuilder::build("ab", "ss") + .max_age(Duration::from_secs(24)) + .domain("codecraft.com") + .path("/") + .same_site(SameSite::None) + .http_only(true) + .partitioned(true) + .expires("Monday") + .finish() + .to_string(); + let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ + Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; + + assert_eq!(test_cookie1_res, test_cookie1); + assert_eq!(test_cookie2_res, test_cookie2); + assert_eq!(test_cookie3_res, test_cookie3); + } +} 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 a38f92cdae59d9ac46c679b9742d8304725bfe41..41bdc977c1f91f64859db4c2c93a88dadac6b44c 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -1,5 +1,7 @@ use std::time::Duration; +use crate::utils::urlencoded::EnCodable; + use super::{Cookie, SameSite}; /// Builder wrapper for a Cookie @@ -22,16 +24,16 @@ impl CookieBuilder { CookieBuilder { inner: Cookie { cookie_string: None, - name: name.to_owned(), - value: value.to_owned(), + name: name.encode(), + value: value.encode(), max_age: None, domain: None, path: None, - secure: None, - http_only: None, + secure: false, + http_only: false, same_site: None, expires: None, - partitioned: None, + partitioned: false, }, } } @@ -43,19 +45,19 @@ impl CookieBuilder { self } pub fn domain(mut self, domain: &str) -> Self { - self.inner.domain = Some(domain.to_owned()); + self.inner.domain = Some(domain.encode()); self } pub fn path(mut self, path: &str) -> Self { - self.inner.path = Some(path.to_owned()); + self.inner.path = Some(path.encode()); self } pub fn secure(mut self, secure: bool) -> Self { - self.inner.secure = Some(secure); + self.inner.secure = secure; self } pub fn http_only(mut self, http_only: bool) -> Self { - self.inner.http_only = Some(http_only); + self.inner.http_only = http_only; self } pub fn same_site(mut self, same_site: SameSite) -> Self { @@ -63,19 +65,19 @@ impl CookieBuilder { self } pub fn expires(mut self, expire: &str) -> Self { - self.inner.expires = Some(expire.to_owned()); + self.inner.expires = Some(expire.encode()); self } pub fn partitioned(mut self, partitioned: bool) -> Self { - self.inner.partitioned = Some(partitioned); + self.inner.partitioned = partitioned; self } pub fn name(mut self, name: &str) -> Self { - self.inner.name = name.to_owned(); + self.inner.name = name.encode(); self } pub fn value(mut self, value: &str) -> Self { - self.inner.value = value.to_owned(); + self.inner.value = value.encode(); self } } diff --git a/core/http/src/utils/urlencoded/endecode.rs b/core/http/src/utils/urlencoded/endecode.rs index 9fba4cb4763bdafea6115959a35316218c7101cc..8dd45d666a285cea508fe91e698e073e3148825c 100644 --- a/core/http/src/utils/urlencoded/endecode.rs +++ b/core/http/src/utils/urlencoded/endecode.rs @@ -18,6 +18,12 @@ pub trait DeCodable { fn decode(&self) -> Result<Vec<u8>, ()>; } +impl<T: AsRef<[u8]>> EnCodable for T { + fn encode(&self) -> String { + self.as_ref().encode() + } +} + impl EnCodable for [u8] { fn encode(self: &[u8]) -> String { let mut result = String::with_capacity(self.len());