fn parse_key_hex(value: &str) -> Option { if value.len() != 64 || !value.bytes().all(|b| b.is_ascii_hexdigit()) { return None; } let mut key = [0_u8; 32]; for (index, chunk) in value.as_bytes().chunks_exact(2).enumerate() { let text = std::str::from_utf8(chunk).ok()?; key[index] = u8::from_str_radix(text, 16).ok()?; } Some(key) } fn default_cache_scheme() -> String { "http".to_owned() } fn cache_tags(headers: &HeaderMap) -> Vec { let mut tags = Vec::new(); for value in headers.get_all("cache-tag") { let Ok(value) = value.to_str() else { continue; }; for raw in value.split(|c: char| c == ',' || c.is_ascii_whitespace()) { let tag = raw.trim(); if tag.is_empty() || tag.len() > 128 || !tag.bytes().all(|b| { b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.' | b':' | b'/') }) { continue; } if !tags.iter().any(|existing| existing == tag) { tags.push(tag.to_owned()); if tags.len() >= 64 { return tags; } } } } tags } fn now_ms() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_millis() .min(u64::MAX as u128) as u64 } fn system_time_ms(value: u64) -> SystemTime { UNIX_EPOCH .checked_add(Duration::from_millis(value)) .unwrap_or(UNIX_EPOCH) } fn cold_age(entry: &ColdEntry) -> u64 { entry .age_at_store_seconds .saturating_add(entry.stored_at.elapsed().unwrap_or_default().as_secs()) } fn cold_resident_age(entry: &ColdEntry) -> u64 { entry .resident_age_at_store_seconds .saturating_add(entry.stored_at.elapsed().unwrap_or_default().as_secs()) } fn retention_deadline( _ttl: u64, _stale_while_revalidate: u64, _stale_if_error: u64, _has_validator: bool, ) -> u64 { u64::MAX } fn has_validator(headers: &HeaderMap) -> bool { headers.contains_key("etag") || headers.contains_key("last-modified") } fn stale_forbidden(must_revalidate: bool, proxy_revalidate: bool) -> bool { must_revalidate || proxy_revalidate } fn client_max_stale(headers: &HeaderMap) -> Option> { let control = directives(headers)?; let value = control.get("max-stale")?; match value { None => Some(None), Some(value) => value.parse::().ok().map(Some), } } fn request_freshness_allows(age: u64, ttl: u64, headers: &HeaderMap) -> bool { let Some(control) = directives(headers) else { return false; }; if let Some(Some(value)) = control.get("max-age") { let Ok(limit) = value.parse::() else { return false; }; if age > limit { return false; } } if let Some(Some(value)) = control.get("min-fresh") { let Ok(required) = value.parse::() else { return false; }; if ttl.saturating_sub(age) < required { return false; } } true } fn stale_mode( age: u64, ttl: u64, stale_while_revalidate: u64, must_revalidate: bool, proxy_revalidate: bool, requires_revalidation: bool, request_headers: &HeaderMap, ) -> Option { if age < ttl || requires_revalidation || stale_forbidden(must_revalidate, proxy_revalidate) { return None; } if !request_freshness_allows(age, ttl, request_headers) { return None; } let stale_for = age.saturating_sub(ttl); let max_stale = client_max_stale(request_headers); if matches!(max_stale, Some(Some(limit)) if stale_for > limit) { return None; } if stale_while_revalidate > 0 && stale_for <= stale_while_revalidate { return Some(true); } match max_stale { Some(None) | Some(Some(_)) => Some(false), None => None, } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum CacheState { Fresh, StaleButServable { background_revalidate: bool }, Expired, } impl CacheState { fn servable(self) -> bool { !matches!(self, Self::Expired) } fn stale(self) -> bool { matches!(self, Self::StaleButServable { .. }) } fn background_revalidate(self) -> bool { matches!( self, Self::StaleButServable { background_revalidate: true } ) } } fn cache_state( age: u64, ttl: u64, stale_while_revalidate: u64, must_revalidate: bool, proxy_revalidate: bool, requires_revalidation: bool, request_headers: &HeaderMap, ) -> CacheState { if age < ttl && !requires_revalidation && request_freshness_allows(age, ttl, request_headers) { return CacheState::Fresh; } match stale_mode( age, ttl, stale_while_revalidate, must_revalidate, proxy_revalidate, requires_revalidation, request_headers, ) { Some(background_revalidate) => CacheState::StaleButServable { background_revalidate, }, None => CacheState::Expired, } } fn cold_paths(root: &Path, key: &Key) -> (PathBuf, PathBuf, PathBuf, PathBuf) { let key = key_hex(key); let shard = root.join(&key[..2]); ( shard.join(format!("{key}.body")), shard.join(format!("{key}.json")), shard.join(format!(".{key}.body.tmp")), shard.join(format!(".{key}.json.tmp")), ) } fn cold_header_pairs(headers: &HeaderMap) -> Option> { let mut result = Vec::new(); for (name, value) in headers { result.push((name.as_str().to_owned(), value.to_str().ok()?.to_owned())); } Some(result) } fn pairs_to_headers(values: &[(String, String)]) -> Option { let mut headers = HeaderMap::new(); for (name, value) in values { let name = name.parse::().ok()?; let value = HeaderValue::from_str(value).ok()?; headers.append(name, value); } Some(headers) }