1use std::{future::Future, rc::Rc};
9
10use actix_web::{
11 dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
12 HttpMessage, HttpRequest,
13};
14use futures::future::{ready, LocalBoxFuture, Ready};
15use qstring::QString;
16
17use crate::router::CSRFType;
18
19pub struct Middleware<F> {
25 cookie: Rc<String>,
26 header: Rc<String>,
27 checker: Rc<F>,
28}
29
30impl<F> Clone for Middleware<F> {
31 fn clone(&self) -> Self {
32 Self {
33 cookie: self.cookie.clone(),
34 header: self.header.clone(),
35 checker: self.checker.clone(),
36 }
37 }
38}
39
40impl<F, Fut> Middleware<F>
41where
42 F: Fn(HttpRequest, String) -> Fut,
43 Fut: Future<Output = Result<bool, actix_web::Error>>,
44{
45 pub fn new(cookie: String, header: String, checker: F) -> Self {
46 Self {
47 cookie: Rc::new(cookie),
48 header: Rc::new(header),
49 checker: Rc::new(checker),
50 }
51 }
52}
53
54impl<S, B, F, Fut> Transform<S, ServiceRequest> for Middleware<F>
55where
56 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
57 S::Future: 'static,
58 B: 'static,
59 F: Fn(HttpRequest, String) -> Fut + 'static,
60 Fut: Future<Output = Result<bool, actix_web::Error>>,
61{
62 type Response = ServiceResponse<B>;
63 type Error = actix_web::Error;
64 type InitError = ();
65 type Transform = MiddlewareService<S, F>;
66 type Future = Ready<Result<Self::Transform, Self::InitError>>;
67
68 fn new_transform(&self, service: S) -> Self::Future {
69 ready(Ok(MiddlewareService {
70 service: Rc::new(service),
71 cookie: self.cookie.clone(),
72 header: self.header.clone(),
73 checker: self.checker.clone(),
74 }))
75 }
76}
77
78pub struct MiddlewareService<S, F> {
79 service: Rc<S>,
80 cookie: Rc<String>,
81 header: Rc<String>,
82 checker: Rc<F>,
83}
84
85impl<S, B, F, Fut> MiddlewareService<S, F>
86where
87 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
88 S::Future: 'static,
89 B: 'static,
90 F: Fn(HttpRequest, String) -> Fut + 'static,
91 Fut: Future<Output = Result<bool, actix_web::Error>>,
92{
93 fn get_safe_header(req: &ServiceRequest, name: &str) -> Option<String> {
94 let mut values = req
95 .headers()
96 .get_all(name)
97 .map(|x| x.to_str().unwrap_or_default())
98 .filter(|x| !x.is_empty());
99 let ret = values.next()?;
100 if values.next().is_some() {
102 return None;
103 }
104 Some(ret.to_owned())
105 }
106
107 async fn check_csrf(
108 req: &ServiceRequest,
109 cookie: &str,
110 header: &str,
111 checker: Rc<F>,
112 allow_param: bool,
113 ) -> Result<bool, actix_web::Error> {
114 let Some(cookie) = req.cookie(cookie) else {
115 return Ok(false);
116 };
117 let mut csrf = Self::get_safe_header(req, header);
118 if csrf.is_none() && allow_param {
119 let qs = QString::from(req.query_string());
120 csrf = qs.get(header).map(ToOwned::to_owned);
121 }
122 let Some(csrf) = csrf else {
123 return Ok(false);
124 };
125 if csrf != cookie.value() {
126 return Ok(false);
127 }
128 checker(req.request().clone(), csrf).await
129 }
130}
131
132impl<S, B, F, Fut> Service<ServiceRequest> for MiddlewareService<S, F>
133where
134 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
135 S::Future: 'static,
136 B: 'static,
137 F: Fn(HttpRequest, String) -> Fut + 'static,
138 Fut: Future<Output = Result<bool, actix_web::Error>>,
139{
140 type Response = ServiceResponse<B>;
141 type Error = actix_web::Error;
142 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
143
144 forward_ready!(service);
145
146 fn call(&self, req: ServiceRequest) -> Self::Future {
147 let srv = self.service.clone();
148 let header = self.header.clone();
149 let cookie = self.cookie.clone();
150 let checker = self.checker.clone();
151 Box::pin(async move {
152 let csrf = req.extensions().get::<CSRFType>().unwrap().to_owned();
155 let enforce = !csrf.is_disabled()
156 && (csrf.is_force_header() || csrf.is_force_param() || !req.method().is_safe());
157 if enforce {
158 let allow_param = csrf.is_param() || csrf.is_force_param();
161 if !Self::check_csrf(&req, &cookie, &header, checker, allow_param).await? {
162 return Err(actix_web::error::ErrorBadRequest("CSRF check failed"));
163 }
164 }
165 srv.call(req).await
166 })
167 }
168}