1use std::{
4 future::Future,
5 marker::PhantomData,
6 pin::Pin,
7 rc::Rc,
8 task::{Context, Poll},
9};
10
11use actix_http::error::HttpError;
12#[expect(deprecated, reason = "preserve the public ready future type")]
13use actix_utils::future::{ready, Ready};
14use futures_core::ready;
15use pin_project_lite::pin_project;
16
17use crate::{
18 dev::{Service, Transform},
19 http::header::{HeaderMap, HeaderName, HeaderValue, TryIntoHeaderPair, CONTENT_TYPE},
20 service::{ServiceRequest, ServiceResponse},
21 Error,
22};
23
24#[derive(Debug, Clone, Default)]
41pub struct DefaultHeaders {
42 inner: Rc<Inner>,
43}
44
45#[derive(Debug, Default)]
46struct Inner {
47 headers: HeaderMap,
48}
49
50impl DefaultHeaders {
51 #[inline]
53 pub fn new() -> DefaultHeaders {
54 DefaultHeaders::default()
55 }
56
57 #[allow(clippy::should_implement_trait)]
62 pub fn add(mut self, header: impl TryIntoHeaderPair) -> Self {
63 match header.try_into_pair() {
67 Ok((key, value)) => Rc::get_mut(&mut self.inner)
68 .expect("All default headers must be added before cloning.")
69 .headers
70 .append(key, value),
71 Err(err) => panic!("Invalid header: {}", err.into()),
72 }
73
74 self
75 }
76
77 #[doc(hidden)]
78 #[deprecated(
79 since = "4.0.0",
80 note = "Prefer `.add((key, value))`. Will be removed in v5."
81 )]
82 pub fn header<K, V>(self, key: K, value: V) -> Self
83 where
84 HeaderName: TryFrom<K>,
85 <HeaderName as TryFrom<K>>::Error: Into<HttpError>,
86 HeaderValue: TryFrom<V>,
87 <HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
88 {
89 self.add((
90 HeaderName::try_from(key)
91 .map_err(Into::into)
92 .expect("Invalid header name"),
93 HeaderValue::try_from(value)
94 .map_err(Into::into)
95 .expect("Invalid header value"),
96 ))
97 }
98
99 pub fn add_content_type(self) -> Self {
103 #[allow(clippy::declare_interior_mutable_const)]
104 const HV_MIME: HeaderValue = HeaderValue::from_static("application/octet-stream");
105 self.add((CONTENT_TYPE, HV_MIME))
106 }
107}
108
109#[expect(deprecated, reason = "preserve the public ready future type")]
110impl<S, B> Transform<S, ServiceRequest> for DefaultHeaders
111where
112 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
113 S::Future: 'static,
114{
115 type Response = ServiceResponse<B>;
116 type Error = Error;
117 type Transform = DefaultHeadersMiddleware<S>;
118 type InitError = ();
119 type Future = Ready<Result<Self::Transform, Self::InitError>>;
120
121 fn new_transform(&self, service: S) -> Self::Future {
122 ready(Ok(DefaultHeadersMiddleware {
123 service,
124 inner: Rc::clone(&self.inner),
125 }))
126 }
127}
128
129pub struct DefaultHeadersMiddleware<S> {
130 service: S,
131 inner: Rc<Inner>,
132}
133
134impl<S, B> Service<ServiceRequest> for DefaultHeadersMiddleware<S>
135where
136 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
137 S::Future: 'static,
138{
139 type Response = ServiceResponse<B>;
140 type Error = Error;
141 type Future = DefaultHeaderFuture<S, B>;
142
143 actix_service::forward_ready!(service);
144
145 fn call(&self, req: ServiceRequest) -> Self::Future {
146 let inner = Rc::clone(&self.inner);
147 let fut = self.service.call(req);
148
149 DefaultHeaderFuture {
150 fut,
151 inner,
152 _body: PhantomData,
153 }
154 }
155}
156
157pin_project! {
158 pub struct DefaultHeaderFuture<S: Service<ServiceRequest>, B> {
159 #[pin]
160 fut: S::Future,
161 inner: Rc<Inner>,
162 _body: PhantomData<B>,
163 }
164}
165
166impl<S, B> Future for DefaultHeaderFuture<S, B>
167where
168 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
169{
170 type Output = <S::Future as Future>::Output;
171
172 #[allow(clippy::borrow_interior_mutable_const)]
173 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
174 let this = self.project();
175 let mut res = ready!(this.fut.poll(cx))?;
176
177 for (key, value) in this.inner.headers.iter() {
179 if !res.headers().contains_key(key) {
180 res.headers_mut().insert(key.clone(), value.clone());
181 }
182 }
183
184 Poll::Ready(Ok(res))
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use std::future::ready;
191
192 use actix_service::IntoService;
193
194 use super::*;
195 use crate::{
196 test::{self, TestRequest},
197 HttpResponse,
198 };
199
200 #[actix_rt::test]
201 async fn adding_default_headers() {
202 let mw = DefaultHeaders::new()
203 .add(("X-TEST", "0001"))
204 .add(("X-TEST-TWO", HeaderValue::from_static("123")))
205 .new_transform(test::ok_service())
206 .await
207 .unwrap();
208
209 let req = TestRequest::default().to_srv_request();
210 let res = mw.call(req).await.unwrap();
211 assert_eq!(res.headers().get("x-test").unwrap(), "0001");
212 assert_eq!(res.headers().get("x-test-two").unwrap(), "123");
213 }
214
215 #[actix_rt::test]
216 async fn no_override_existing() {
217 let req = TestRequest::default().to_srv_request();
218 let srv = |req: ServiceRequest| {
219 ready(Ok(req.into_response(
220 HttpResponse::Ok()
221 .insert_header((CONTENT_TYPE, "0002"))
222 .finish(),
223 )))
224 };
225 let mw = DefaultHeaders::new()
226 .add((CONTENT_TYPE, "0001"))
227 .new_transform(srv.into_service())
228 .await
229 .unwrap();
230 let resp = mw.call(req).await.unwrap();
231 assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
232 }
233
234 #[actix_rt::test]
235 async fn adding_content_type() {
236 let mw = DefaultHeaders::new()
237 .add_content_type()
238 .new_transform(test::ok_service())
239 .await
240 .unwrap();
241
242 let req = TestRequest::default().to_srv_request();
243 let resp = mw.call(req).await.unwrap();
244 assert_eq!(
245 resp.headers().get(CONTENT_TYPE).unwrap(),
246 "application/octet-stream"
247 );
248 }
249
250 #[test]
251 #[should_panic]
252 fn invalid_header_name() {
253 DefaultHeaders::new().add((":", "hello"));
254 }
255
256 #[test]
257 #[should_panic]
258 fn invalid_header_value() {
259 DefaultHeaders::new().add(("x-test", "\n"));
260 }
261}