Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
SergioBenitez committed Oct 3, 2023
1 parent c105b67 commit d0771e2
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 21 deletions.
13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ signed = ["hmac", "sha2", "base64", "rand", "subtle"]
key-expansion = ["sha2", "hkdf"]

[dependencies]
time = { version = "0.3", default-features = false, features = ["std", "parsing", "formatting", "macros"] }
percent-encoding = { version = "2.0", optional = true }

# dependencies for secure (private/signed) functionality
Expand All @@ -33,6 +32,18 @@ rand = { version = "0.8", optional = true }
hkdf = { version = "0.12.0", optional = true }
subtle = { version = "2.3", optional = true }

[dependencies.time]
version = "0.3"
default-features = false
features = ["std", "parsing", "formatting", "macros"]
optional = true

[dependencies.chrono]
version = "0.4.30"
default-features = false
features = ["std", "clock"]
optional = true

[build-dependencies]
version_check = "0.9.4"

Expand Down
3 changes: 2 additions & 1 deletion src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::borrow::{Cow, Borrow, BorrowMut};

use crate::{Cookie, SameSite, Expiration};
use crate::time::Duration;

/// Structure that follows the builder pattern for building `Cookie` structs.
///
Expand Down Expand Up @@ -102,7 +103,7 @@ impl<'c> CookieBuilder<'c> {
/// assert_eq!(c.inner().max_age(), Some(Duration::seconds(30 * 60)));
/// ```
#[inline]
pub fn max_age(mut self, value: time::Duration) -> Self {
pub fn max_age(mut self, value: Duration) -> Self {
self.cookie.set_max_age(value);
self
}
Expand Down
10 changes: 5 additions & 5 deletions src/expiration.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use time::OffsetDateTime;
use crate::time::DateTime;

/// A cookie's expiration: either a date-time or session.
///
Expand All @@ -25,7 +25,7 @@ use time::OffsetDateTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Expiration {
/// Expiration for a "permanent" cookie at a specific date-time.
DateTime(OffsetDateTime),
DateTime(DateTime),
/// Expiration for a "session" cookie. Browsers define the notion of a
/// "session" and will automatically expire session cookies when they deem
/// the "session" to be over. This is typically, but need not be, when the
Expand Down Expand Up @@ -91,7 +91,7 @@ impl Expiration {
/// let expires = Expiration::from(now);
/// assert_eq!(expires.datetime(), Some(now));
/// ```
pub fn datetime(self) -> Option<OffsetDateTime> {
pub fn datetime(self) -> Option<DateTime> {
match self {
Expiration::Session => None,
Expiration::DateTime(v) => Some(v)
Expand All @@ -117,7 +117,7 @@ impl Expiration {
/// assert_eq!(expires.map(|t| t + one_week).datetime(), None);
/// ```
pub fn map<F>(self, f: F) -> Self
where F: FnOnce(OffsetDateTime) -> OffsetDateTime
where F: FnOnce(DateTime) -> DateTime
{
match self {
Expiration::Session => Expiration::Session,
Expand All @@ -126,7 +126,7 @@ impl Expiration {
}
}

impl<T: Into<Option<OffsetDateTime>>> From<T> for Expiration {
impl<T: Into<Option<DateTime>>> From<T> for Expiration {
fn from(option: T) -> Self {
match option.into() {
Some(value) => Expiration::DateTime(value),
Expand Down
27 changes: 13 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

#![deny(missing_docs)]

pub use time;
pub mod time;

mod builder;
mod parse;
Expand All @@ -91,7 +91,7 @@ use std::str::FromStr;
#[allow(unused_imports, deprecated)]
use std::ascii::AsciiExt;

use time::{Duration, OffsetDateTime, UtcOffset, macros::datetime};
use time::{Duration, DateTime, InternalDateTime};

use crate::parse::parse_cookie;
pub use crate::parse::ParseError;
Expand Down Expand Up @@ -781,8 +781,9 @@ impl<'c> Cookie<'c> {
/// assert_eq!(c.expires_datetime().map(|t| t.year()), Some(2017));
/// ```
#[inline]
pub fn expires_datetime(&self) -> Option<OffsetDateTime> {
self.expires.and_then(|e| e.datetime())
pub fn expires_datetime(&self) -> Option<DateTime> {
todo!()
// self.expires.and_then(|e| e.datetime())
}

/// Sets the name of `self` to `name`.
Expand Down Expand Up @@ -931,7 +932,8 @@ impl<'c> Cookie<'c> {
/// ```
#[inline]
pub fn set_max_age<D: Into<Option<Duration>>>(&mut self, value: D) {
self.max_age = value.into();
todo!()
// self.max_age = value.into();
}

/// Sets the `path` of `self` to `path`.
Expand Down Expand Up @@ -1031,11 +1033,9 @@ impl<'c> Cookie<'c> {
/// assert_eq!(c.expires(), Some(Expiration::Session));
/// ```
pub fn set_expires<T: Into<Expiration>>(&mut self, time: T) {
static MAX_DATETIME: OffsetDateTime = datetime!(9999-12-31 23:59:59.999_999 UTC);

// RFC 6265 requires dates not to exceed 9999 years.
self.expires = Some(time.into()
.map(|time| std::cmp::min(time, MAX_DATETIME)));
.map(|time| std::cmp::min(time, DateTime::MAX)));
}

/// Unsets the `expires` of `self`.
Expand Down Expand Up @@ -1081,7 +1081,7 @@ impl<'c> Cookie<'c> {
pub fn make_permanent(&mut self) {
let twenty_years = Duration::days(365 * 20);
self.set_max_age(twenty_years);
self.set_expires(OffsetDateTime::now_utc() + twenty_years);
self.set_expires(DateTime::now() + twenty_years);
}

/// Make `self` a "removal" cookie by clearing its value, setting a max-age
Expand All @@ -1107,8 +1107,8 @@ impl<'c> Cookie<'c> {
/// ```
pub fn make_removal(&mut self) {
self.set_value("");
self.set_max_age(Duration::seconds(0));
self.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
self.set_max_age(Duration::ZERO);
self.set_expires(DateTime::now_utc() - Duration::days(365));
}

fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result {
Expand Down Expand Up @@ -1137,12 +1137,11 @@ impl<'c> Cookie<'c> {
}

if let Some(max_age) = self.max_age() {
write!(f, "; Max-Age={}", max_age.whole_seconds())?;
write!(f, "; Max-Age={}", max_age.seconds())?;
}

if let Some(time) = self.expires_datetime() {
let time = time.to_offset(UtcOffset::UTC);
write!(f, "; Expires={}", time.format(&crate::parse::FMT1).map_err(|_| fmt::Error)?)?;
write!(f, "; Expires={}", time.expiration_format().ok_or(fmt::Error)?)?;
}

Ok(())
Expand Down
81 changes: 81 additions & 0 deletions src/time/datetime/internal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use super::{DateTime, InternalDateTime};

impl InternalDateTime for DateTime {
const MAX: Self = {
#[cfg(feature = "time")] {
DateTime::Time(time::OffsetDateTime::MAX)
}

#[cfg(not(feature = "time"))] {
DateTime::Chrono(chrono::DateTime::MAX)
}
};

fn now() -> Self {
#[cfg(feature = "time")] {
DateTime::Time(time::OffsetDateTime::now())
}

#[cfg(not(feature = "time"))] {
DateTime::Chrono(chrono::DateTime::now())
}
}

fn destruct(&self) -> (i32, u32, u32, i32, u32, u32, u32) {
match self {
#[cfg(feature = "time")]
DateTime::Time(inner) => inner.destruct(),
#[cfg(feature = "chrono")]
DateTime::Chrono(inner) => inner.destruct(),
}
}

fn expiration_format(&self) -> Option<String> {
match self {
#[cfg(feature = "time")]
DateTime::Time(inner) => inner.expiration_format(),
#[cfg(feature = "chrono")]
DateTime::Chrono(inner) => inner.expiration_format(),
}
}
}

#[cfg(feature = "time")]
impl InternalDateTime for time::OffsetDateTime {
const MAX: Self = time::macros::datetime!(9999-12-31 23:59:59.999_999 UTC);

fn now() -> Self {
time::OffsetDateTime::now_utc()
}

fn destruct(&self) -> (i32, u32, u32, i32, u32, u32, u32) {
let (year, month, day) = self.to_calendar_date();
let (hour, minute, second, nanos) = self.to_hms_nano();
(year, month as u32, day.into(), hour.into(), minute.into(), second.into(), nanos)
}

fn expiration_format(&self) -> Option<String> {
self.format(&crate::parse::FMT1).ok()
}
}

#[cfg(feature = "chrono")]
impl InternalDateTime for chrono::DateTime<chrono::Utc> {
const MAX: Self = chrono::DateTime::from_naive_utc_and_offset(
chrono::NaiveDateTime::new(
chrono::NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
chrono::NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
), chrono::Utc);

fn now() -> Self {
chrono::Utc::now()
}

fn destruct(&self) -> (i32, u32, u32, i32, u32, u32, u32) {
todo!()
}

fn expiration_format(&self) -> Option<String> {
todo!()
}
}
98 changes: 98 additions & 0 deletions src/time/datetime/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
mod internal;

#[derive(Debug, Clone, Copy, Eq)]
pub enum DateTime {
#[cfg(feature = "time")]
Time(time::OffsetDateTime),
#[cfg(feature = "chrono")]
Chrono(chrono::DateTime<chrono::Utc>)
}

/// API implemented for internal use. This is private. Everything else: public.
pub(crate) trait InternalDateTime: From<DateTime> + Into<DateTime> {
/// The max cookie date-time.
const MAX: Self;

/// The datetime right now.
fn now() -> Self;

/// UTC based (year, month, day, hour, minute, second, nanosecond).
/// * date is ISO 8601 calendar date
/// * month is 1-indexed
fn destruct(&self) -> (i32, u32, u32, i32, u32, u32, u32);

/// The datetime as a string suitable for use as a cookie expiration.
fn expiration_format(&self) -> Option<String>;
}

impl PartialEq for DateTime {
fn eq(&self, other: &Self) -> bool {
self.destruct() == other.destruct()
}
}

impl std::hash::Hash for DateTime {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.destruct().hash(state)
}
}

impl PartialOrd for DateTime {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.destruct().partial_cmp(&other.destruct())
}
}

impl Ord for DateTime {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.destruct().cmp(&other.destruct())
}
}

#[cfg(feature = "time")]
mod time_impl {
use super::*;

impl From<DateTime> for time::OffsetDateTime {
fn from(value: DateTime) -> Self {
let (yr, mon, day, hr, min, sec, nano) = value.destruct();
todo!()
}
}

impl From<time::OffsetDateTime> for DateTime {
fn from(value: time::OffsetDateTime) -> Self {
DateTime::Time(value)
}
}

impl PartialEq<time::OffsetDateTime> for DateTime {
fn eq(&self, other: &time::OffsetDateTime) -> bool {
self.destruct().eq(&other.destruct())
}
}
}

#[cfg(feature = "chrono")]
mod chrono_impl {
use super::*;

impl From<DateTime> for chrono::DateTime<chrono::Utc> {
fn from(value: DateTime) -> Self {
let (yr, mon, day, hr, min, sec, nano) = value.destruct();
todo!()
}
}

impl From<chrono::DateTime<chrono::Utc>> for DateTime {
fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
DateTime::Chrono(value)
}
}

impl PartialEq<chrono::DateTime<chrono::Utc>> for DateTime {
fn eq(&self, other: &chrono::DateTime<chrono::Utc>) -> bool {
self.destruct().eq(&other.destruct())
}
}
}
Loading

0 comments on commit d0771e2

Please sign in to comment.