rws_server/blocklist/
mod.rs1#[cfg(test)]
26mod tests;
27
28use std::sync::atomic::{AtomicUsize, Ordering};
29use std::sync::{Mutex, OnceLock};
30
31use crate::application::Application;
32use crate::error::{AppError, IntoResponse};
33use crate::middleware::Middleware;
34use crate::request::Request;
35use crate::response::Response;
36use crate::server::ConnectionInfo;
37
38pub struct Blocklist {
40 denied: Mutex<Vec<String>>,
41 count: AtomicUsize,
45}
46
47impl Blocklist {
48 fn new() -> Self {
49 Blocklist { denied: Mutex::new(Vec::new()), count: AtomicUsize::new(0) }
50 }
51
52 pub fn block(&self, ip: &str) {
54 let mut guard = self.denied.lock().unwrap();
55 if !guard.iter().any(|e| e == ip) {
56 guard.push(ip.to_string());
57 }
58 self.count.store(guard.len(), Ordering::Relaxed);
59 }
60
61 pub fn unblock(&self, ip: &str) {
63 let mut guard = self.denied.lock().unwrap();
64 guard.retain(|e| e != ip);
65 self.count.store(guard.len(), Ordering::Relaxed);
66 }
67
68 pub fn is_blocked(&self, ip: &str) -> bool {
70 if self.count.load(Ordering::Relaxed) == 0 {
71 return false;
72 }
73 self.denied.lock().unwrap().iter().any(|e| e == ip)
74 }
75
76 pub fn list(&self) -> Vec<String> {
78 self.denied.lock().unwrap().clone()
79 }
80
81 pub fn clear(&self) {
83 let mut guard = self.denied.lock().unwrap();
84 guard.clear();
85 self.count.store(0, Ordering::Relaxed);
86 }
87}
88
89static INSTANCE: OnceLock<Blocklist> = OnceLock::new();
90
91pub fn global() -> &'static Blocklist {
93 INSTANCE.get_or_init(Blocklist::new)
94}
95
96pub struct BlocklistLayer;
101
102impl Middleware for BlocklistLayer {
103 fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
105 true
106 }
107
108
109 fn handle(
110 &self,
111 request: &Request,
112 connection: &ConnectionInfo,
113 next: &dyn Application,
114 ) -> Result<Response, String> {
115 if global().is_blocked(&crate::ip_filter::client_ip(request, &connection.client.ip)) {
116 return Ok(AppError::Forbidden.into_response());
117 }
118 next.execute(request, connection)
119 }
120
121 fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
123 true
124 }
125
126 fn is_passthrough(&self, request: &Request, connection: &ConnectionInfo) -> bool {
130 !global().is_blocked(&crate::ip_filter::client_ip(request, &connection.client.ip))
131 }
132}