privatus_analytics/
actix.rs1use crate::config::{Config, CspNonce};
28use crate::tag::{inject_tag, should_inject};
29use crate::value::Props;
30use actix_web::body::{to_bytes, EitherBody, MessageBody};
31use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
32use actix_web::http::header;
33use actix_web::web::Bytes;
34use actix_web::{Error, HttpMessage, HttpRequest};
35use std::fmt;
36use std::future::{ready, Future, Ready};
37use std::pin::Pin;
38use std::rc::Rc;
39use std::sync::Arc;
40use std::task::{Context, Poll};
41
42type PropsFn = dyn Fn(&HttpRequest) -> Props;
43
44#[derive(Clone)]
46pub struct PrivatusMiddleware {
47 config: Arc<Config>,
48 props_fn: Option<Rc<PropsFn>>,
49}
50
51impl PrivatusMiddleware {
52 pub fn new(config: Config) -> Self {
54 Self::from_arc(Arc::new(config))
55 }
56
57 pub fn from_arc(config: Arc<Config>) -> Self {
59 PrivatusMiddleware {
60 config,
61 props_fn: None,
62 }
63 }
64
65 pub fn props_fn<F>(mut self, props_fn: F) -> Self
71 where
72 F: Fn(&HttpRequest) -> Props + 'static,
73 {
74 self.props_fn = Some(Rc::new(props_fn));
75 self
76 }
77}
78
79impl fmt::Debug for PrivatusMiddleware {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.debug_struct("PrivatusMiddleware")
82 .field("config", &self.config)
83 .field("props_fn", &self.props_fn.is_some())
84 .finish()
85 }
86}
87
88impl<S, B> Transform<S, ServiceRequest> for PrivatusMiddleware
89where
90 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
91 B: MessageBody + 'static,
92{
93 type Response = ServiceResponse<EitherBody<B>>;
94 type Error = Error;
95 type Transform = PrivatusMiddlewareService<S>;
96 type InitError = ();
97 type Future = Ready<Result<Self::Transform, Self::InitError>>;
98
99 fn new_transform(&self, service: S) -> Self::Future {
100 ready(Ok(PrivatusMiddlewareService {
101 service,
102 config: self.config.clone(),
103 props_fn: self.props_fn.clone(),
104 }))
105 }
106}
107
108pub struct PrivatusMiddlewareService<S> {
110 service: S,
111 config: Arc<Config>,
112 props_fn: Option<Rc<PropsFn>>,
113}
114
115impl<S> fmt::Debug for PrivatusMiddlewareService<S> {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.debug_struct("PrivatusMiddlewareService")
118 .field("config", &self.config)
119 .finish()
120 }
121}
122
123impl<S, B> Service<ServiceRequest> for PrivatusMiddlewareService<S>
124where
125 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
126 B: MessageBody + 'static,
127{
128 type Response = ServiceResponse<EitherBody<B>>;
129 type Error = Error;
130 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
131
132 fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
133 self.service.poll_ready(cx)
134 }
135
136 fn call(&self, request: ServiceRequest) -> Self::Future {
137 let config = self.config.clone();
138 let props_fn = self.props_fn.clone();
139 let xhr = request
140 .headers()
141 .get("x-requested-with")
142 .and_then(|v| v.to_str().ok())
143 .map(str::to_owned);
144 let future = self.service.call(request);
145
146 Box::pin(async move {
147 let response = future.await?;
148 let headers = response.headers();
149 let content_type = headers
150 .get(header::CONTENT_TYPE)
151 .and_then(|v| v.to_str().ok());
152 let encoded = headers.contains_key(header::CONTENT_ENCODING);
153 let inject = config.is_auto_inject()
154 && should_inject(
155 response.status().as_u16(),
156 content_type,
157 encoded,
158 xhr.as_deref(),
159 )
160 && config.tag().will_render();
161 if !inject {
162 return Ok(response.map_into_left_body());
163 }
164
165 let (request, response) = response.into_parts();
166 let mut tag = config.tag();
167 if let Some(props_fn) = &props_fn {
168 tag = tag.props(props_fn(&request));
169 }
170 if let Some(props) = request.extensions().get::<Props>() {
171 tag = tag.props(props);
172 }
173 if let Some(props) = response.extensions().get::<Props>() {
174 tag = tag.props(props);
175 }
176 let nonce = response
177 .extensions()
178 .get::<CspNonce>()
179 .cloned()
180 .or_else(|| request.extensions().get::<CspNonce>().cloned());
181 if let Some(nonce) = nonce {
182 tag = tag.nonce(nonce.0);
183 }
184 let tag = tag.to_html();
185
186 let (mut response, body) = response.into_parts();
187 let bytes = to_bytes(body)
188 .await
189 .map_err(|e| actix_web::error::ErrorInternalServerError(e.into().to_string()))?;
190 let bytes = std::str::from_utf8(&bytes)
191 .ok()
192 .and_then(|page| inject_tag(page, tag.as_str()))
193 .map_or(bytes, Bytes::from);
194 response.headers_mut().remove(header::CONTENT_LENGTH);
195 let response = response.set_body(bytes).map_into_boxed_body();
196 Ok(ServiceResponse::new(request, response).map_into_right_body())
197 })
198 }
199}