#[cfg(test)]
mod tests;
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>>,
}
impl Blocklist {
fn new() -> Self {
Blocklist { denied: Mutex::new(Vec::new()) }
}
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());
}
}
pub fn unblock(&self, ip: &str) {
self.denied.lock().unwrap().retain(|e| e != ip);
}
pub fn is_blocked(&self, ip: &str) -> bool {
self.denied.lock().unwrap().iter().any(|e| e == ip)
}
pub fn list(&self) -> Vec<String> {
self.denied.lock().unwrap().clone()
}
pub fn clear(&self) {
self.denied.lock().unwrap().clear();
}
}
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 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)
}
}