#[cfg(test)]
mod tests;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::application::Application;
use crate::error::{AppError, IntoResponse};
use crate::middleware::Middleware;
use crate::request::Request;
use crate::response::Response;
use crate::server::ConnectionInfo;
pub struct Blocklist {
denied: Mutex<Vec<String>>,
count: AtomicUsize,
}
impl Blocklist {
fn new() -> Self {
Blocklist { denied: Mutex::new(Vec::new()), count: AtomicUsize::new(0) }
}
pub fn block(&self, ip: &str) {
let mut guard = self.denied.lock().unwrap();
if !guard.iter().any(|e| e == ip) {
guard.push(ip.to_string());
}
self.count.store(guard.len(), Ordering::Relaxed);
}
pub fn unblock(&self, ip: &str) {
let mut guard = self.denied.lock().unwrap();
guard.retain(|e| e != ip);
self.count.store(guard.len(), Ordering::Relaxed);
}
pub fn is_blocked(&self, ip: &str) -> bool {
if self.count.load(Ordering::Relaxed) == 0 {
return false;
}
self.denied.lock().unwrap().iter().any(|e| e == ip)
}
pub fn list(&self) -> Vec<String> {
self.denied.lock().unwrap().clone()
}
pub fn clear(&self) {
let mut guard = self.denied.lock().unwrap();
guard.clear();
self.count.store(0, Ordering::Relaxed);
}
}
static INSTANCE: OnceLock<Blocklist> = OnceLock::new();
pub fn global() -> &'static Blocklist {
INSTANCE.get_or_init(Blocklist::new)
}
pub struct BlocklistLayer;
impl Middleware for BlocklistLayer {
fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
true
}
fn handle(
&self,
request: &Request,
connection: &ConnectionInfo,
next: &dyn Application,
) -> Result<Response, String> {
if global().is_blocked(&connection.client.ip) {
return Ok(AppError::Forbidden.into_response());
}
next.execute(request, connection)
}
fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
true
}
}