debugbundle 1.0.0-alpha.1

Safe, bounded runtime error reporting for Rust, Axum and Actix Web
Documentation
use super::{RelayOptions, RelayRequest};
use std::{
    collections::HashMap,
    net::IpAddr,
    time::{Duration, Instant},
};
#[cfg(test)]
mod tests;
fn origin_url(value: &str, referer: bool) -> Option<String> {
    if value.len() > 8192 {
        return None;
    }
    let url = url::Url::parse(value).ok()?;
    if !matches!(url.scheme(), "https" | "http")
        || url.host_str().is_none()
        || !url.username().is_empty()
        || url.password().is_some()
        || (!referer && (url.path() != "/" || url.query().is_some() || url.fragment().is_some()))
    {
        return None;
    }
    Some(url.origin().ascii_serialization())
}
fn trusted(request: &RelayRequest, options: &RelayOptions) -> bool {
    request
        .peer
        .is_some_and(|peer| options.trusted_proxies.contains(&peer))
}
pub(super) fn origin(request: &RelayRequest, options: &RelayOptions) -> Option<String> {
    let origin = match &request.origin {
        Some(origin) => origin_url(origin, false)?,
        None => origin_url(request.referer.as_deref()?, true)?,
    };
    if !options.allowed_origins.is_empty() {
        return options.allowed_origins.contains(&origin).then_some(origin);
    }
    let (host, scheme) = if trusted(request, options) {
        (
            request.forwarded_host.as_deref().unwrap_or(&request.host),
            request
                .forwarded_proto
                .as_deref()
                .unwrap_or(&request.scheme),
        )
    } else {
        (request.host.as_str(), request.scheme.as_str())
    };
    if host.len() > 2048
        || host.contains(['/', '?', '#', '@', ',', '\\'])
        || !matches!(scheme, "http" | "https")
    {
        return None;
    }
    (origin_url(&format!("{scheme}://{host}"), false)? == origin).then_some(origin)
}
pub(super) fn peer(request: &RelayRequest, options: &RelayOptions) -> Option<IpAddr> {
    if trusted(request, options) {
        if let Some(peer) = request
            .forwarded_for
            .as_deref()
            .filter(|peer| peer.len() <= 45)
            .and_then(|peer| peer.parse().ok())
        {
            return Some(peer);
        }
    }
    request.peer
}
#[derive(Default)]
pub(super) struct Limiter {
    entries: HashMap<Option<IpAddr>, (Instant, u32)>,
}
impl Limiter {
    pub(super) fn allow(&mut self, peer: Option<IpAddr>, limit: u32, now: Instant) -> bool {
        self.entries.retain(|_, (start, _)| {
            now.saturating_duration_since(*start) < Duration::from_secs(60)
        });
        if !self.entries.contains_key(&peer) && self.entries.len() >= 4096 {
            return false;
        }
        let entry = self.entries.entry(peer).or_insert((now, 0));
        if entry.1 >= limit {
            return false;
        }
        entry.1 += 1;
        true
    }
}