pub(crate) fn parse_retry_after(header: &str, now: u64) -> Option<u32> {
let h = header.trim();
if let Ok(secs) = h.parse::<u32>() {
return Some(secs);
}
let target = parse_imf_fixdate(h)?;
Some(target.saturating_sub(now).min(u32::MAX as u64) as u32)
}
fn parse_imf_fixdate(s: &str) -> Option<u64> {
let s = s.strip_suffix(" GMT")?;
let (_day_name, rest) = s.split_once(", ")?; let mut parts = rest.split(' '); let day: u32 = parts.next()?.parse().ok()?;
let month = month_num(parts.next()?)?;
let year: i64 = parts.next()?.parse().ok()?;
let mut hms = parts.next()?.split(':');
let hour: u64 = hms.next()?.parse().ok()?;
let min: u64 = hms.next()?.parse().ok()?;
let sec: u64 = hms.next()?.parse().ok()?;
if parts.next().is_some() || hms.next().is_some() || hour > 23 || min > 59 || sec > 60 {
return None;
}
let days = days_from_civil(year, month, day)?;
let secs = days
.checked_mul(86_400)?
.checked_add((hour * 3600 + min * 60 + sec) as i64)?;
u64::try_from(secs).ok()
}
fn month_num(mon: &str) -> Option<u32> {
Some(match mon {
"Jan" => 1,
"Feb" => 2,
"Mar" => 3,
"Apr" => 4,
"May" => 5,
"Jun" => 6,
"Jul" => 7,
"Aug" => 8,
"Sep" => 9,
"Oct" => 10,
"Nov" => 11,
"Dec" => 12,
_ => return None,
})
}
fn days_from_civil(year: i64, month: u32, day: u32) -> Option<i64> {
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return None;
}
let y = if month <= 2 { year - 1 } else { year };
let m = month as i64;
let d = day as i64;
let era = y / 400;
let yoe = y - era * 400; let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; Some(era * 146097 + doe - 719468)
}
#[cfg(test)]
mod tests {
use super::parse_retry_after;
#[test]
fn integer_seconds_pass_through_ignoring_now() {
assert_eq!(parse_retry_after("120", 0), Some(120));
assert_eq!(parse_retry_after(" 30 ", 999), Some(30));
assert_eq!(parse_retry_after("0", 5), Some(0));
}
#[test]
fn http_date_is_deadline_minus_now() {
assert_eq!(
parse_retry_after("Thu, 01 Jan 1970 01:00:00 GMT", 0),
Some(3600)
);
assert_eq!(
parse_retry_after("Fri, 01 May 1970 00:00:00 GMT", 0),
Some(10_368_000)
);
assert_eq!(
parse_retry_after("Thu, 01 Jan 1970 00:00:00 GMT", 100),
Some(0)
);
assert_eq!(
parse_retry_after("Fri, 31 Dec 9999 23:59:59 GMT", 0),
Some(u32::MAX)
);
}
#[test]
fn malformed_headers_are_none() {
for h in [
"", "soon", "-5", "Thu, 01 Jan 1970 00:00:00", "Thu 01 Jan 1970 00:00:00 GMT", "Thu, 01 Foo 1970 00:00:00 GMT", "Thu, 01 Jan 19XX 00:00:00 GMT", "Thu, XX Jan 1970 00:00:00 GMT", "Thu, 00 Jan 1970 00:00:00 GMT", "Thu, 01 Jan 1970 24:00:00 GMT", "Thu, 01 Jan 1970 00:60:00 GMT", "Thu, 01 Jan 1970 00:00 GMT", "Thu, 01 Jan 1970 00:00:00 extra GMT", "Thu, 01 Jan 1970 00:00:00:00 GMT", ] {
assert_eq!(parse_retry_after(h, 0), None, "expected None for {h:?}");
}
}
}