use crate::clock::Clock;
use crate::GatewardenError;
use chrono::{DateTime, Utc};
pub const MAX_RESPONSE_AGE_SECONDS: i64 = 5 * 60;
pub const MAX_FUTURE_TOLERANCE_SECONDS: i64 = 60;
pub fn parse_rfc2822_date(date_str: &str) -> Result<DateTime<Utc>, GatewardenError> {
DateTime::parse_from_rfc2822(date_str)
.map(|dt| dt.with_timezone(&Utc))
.map_err(|e| {
GatewardenError::ProtocolError(format!("Invalid date header: {} ({})", date_str, e))
})
}
pub fn check_freshness<C: Clock + ?Sized>(
response_date: DateTime<Utc>,
clock: &C,
) -> Result<(), GatewardenError> {
let now = clock.now_utc();
let age_seconds = (now - response_date).num_seconds();
if age_seconds > MAX_RESPONSE_AGE_SECONDS {
return Err(GatewardenError::ResponseTooOld { age_seconds });
}
if age_seconds < -MAX_FUTURE_TOLERANCE_SECONDS {
return Err(GatewardenError::ResponseFromFuture);
}
Ok(())
}
pub fn check_date_freshness<C: Clock + ?Sized>(
date_header: &str,
clock: &C,
) -> Result<DateTime<Utc>, GatewardenError> {
let response_date = parse_rfc2822_date(date_header)?;
check_freshness(response_date, clock)?;
Ok(response_date)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clock::MockClock;
#[test]
fn test_parse_rfc2822_valid() {
let date = parse_rfc2822_date("Wed, 09 Jun 2021 16:08:15 GMT").unwrap();
assert_eq!(date.to_rfc3339(), "2021-06-09T16:08:15+00:00");
}
#[test]
fn test_parse_rfc2822_invalid() {
let result = parse_rfc2822_date("not a date");
assert!(matches!(result, Err(GatewardenError::ProtocolError(_))));
}
#[test]
fn test_freshness_valid() {
let clock = MockClock::from_rfc3339("2021-06-09T16:10:00Z").unwrap();
let response_date = parse_rfc2822_date("Wed, 09 Jun 2021 16:08:15 GMT").unwrap();
let result = check_freshness(response_date, &clock);
assert!(result.is_ok());
}
#[test]
fn test_freshness_stale() {
let clock = MockClock::from_rfc3339("2021-06-09T16:20:00Z").unwrap();
let response_date = parse_rfc2822_date("Wed, 09 Jun 2021 16:08:15 GMT").unwrap();
let result = check_freshness(response_date, &clock);
assert!(matches!(
result,
Err(GatewardenError::ResponseTooOld { .. })
));
}
#[test]
fn test_freshness_exactly_5_minutes() {
let clock = MockClock::from_rfc3339("2021-06-09T16:13:15Z").unwrap();
let response_date = parse_rfc2822_date("Wed, 09 Jun 2021 16:08:15 GMT").unwrap();
let result = check_freshness(response_date, &clock);
assert!(result.is_ok());
}
#[test]
fn test_freshness_just_over_5_minutes() {
let clock = MockClock::from_rfc3339("2021-06-09T16:13:16Z").unwrap();
let response_date = parse_rfc2822_date("Wed, 09 Jun 2021 16:08:15 GMT").unwrap();
let result = check_freshness(response_date, &clock);
assert!(matches!(
result,
Err(GatewardenError::ResponseTooOld { .. })
));
}
#[test]
fn test_freshness_future_within_tolerance() {
let clock = MockClock::from_rfc3339("2021-06-09T16:07:30Z").unwrap();
let response_date = parse_rfc2822_date("Wed, 09 Jun 2021 16:08:15 GMT").unwrap();
let result = check_freshness(response_date, &clock);
assert!(result.is_ok());
}
#[test]
fn test_freshness_future_exceeds_tolerance() {
let clock = MockClock::from_rfc3339("2021-06-09T16:06:00Z").unwrap();
let response_date = parse_rfc2822_date("Wed, 09 Jun 2021 16:08:15 GMT").unwrap();
let result = check_freshness(response_date, &clock);
assert!(matches!(result, Err(GatewardenError::ResponseFromFuture)));
}
#[test]
fn test_check_date_freshness_combined() {
let clock = MockClock::from_rfc3339("2021-06-09T16:10:00Z").unwrap();
let result = check_date_freshness("Wed, 09 Jun 2021 16:08:15 GMT", &clock);
assert!(result.is_ok());
}
}