actix_web_lab/
x_forwarded_prefix.rs1use std::future::{Ready, ready};
6
7use actix_http::{
8 HttpMessage,
9 error::ParseError,
10 header::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderValue},
11};
12use actix_web::FromRequest;
13use derive_more::Display;
14use http::uri::PathAndQuery;
15
16#[allow(clippy::declare_interior_mutable_const)]
20pub const X_FORWARDED_PREFIX: HeaderName = HeaderName::from_static("x-forwarded-prefix");
21
22#[derive(Debug, Clone, PartialEq, Eq, Display)]
42pub struct XForwardedPrefix(pub PathAndQuery);
43
44impl_more::impl_deref_and_mut!(XForwardedPrefix => PathAndQuery);
45
46impl TryIntoHeaderValue for XForwardedPrefix {
47 type Error = InvalidHeaderValue;
48
49 fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
50 HeaderValue::try_from(self.to_string())
51 }
52}
53
54impl Header for XForwardedPrefix {
55 fn name() -> HeaderName {
56 X_FORWARDED_PREFIX
57 }
58
59 fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError> {
60 let header = msg.headers().get(Self::name());
61
62 header
63 .and_then(|hdr| hdr.to_str().ok())
64 .map(|hdr| hdr.trim())
65 .filter(|hdr| !hdr.is_empty())
66 .and_then(|hdr| hdr.parse::<actix_web::http::uri::PathAndQuery>().ok())
67 .filter(|path| path.query().is_none())
68 .map(XForwardedPrefix)
69 .ok_or(ParseError::Header)
70 }
71}
72
73#[cfg(test)]
74mod header_tests {
75 use actix_web::test::{self};
76
77 use super::*;
78
79 #[test]
80 fn deref() {
81 let mut fwd_prefix = XForwardedPrefix(PathAndQuery::from_static("/"));
82 let _: &PathAndQuery = &fwd_prefix;
83 let _: &mut PathAndQuery = &mut fwd_prefix;
84 }
85
86 #[test]
87 fn no_headers() {
88 let req = test::TestRequest::default().to_http_request();
89 assert_eq!(XForwardedPrefix::parse(&req).ok(), None);
90 }
91
92 #[test]
93 fn empty_header() {
94 let req = test::TestRequest::default()
95 .insert_header((X_FORWARDED_PREFIX, ""))
96 .to_http_request();
97
98 assert_eq!(XForwardedPrefix::parse(&req).ok(), None);
99 }
100
101 #[test]
102 fn single_header() {
103 let req = test::TestRequest::default()
104 .insert_header((X_FORWARDED_PREFIX, "/foo"))
105 .to_http_request();
106
107 assert_eq!(
108 XForwardedPrefix::parse(&req).ok().unwrap(),
109 XForwardedPrefix(PathAndQuery::from_static("/foo")),
110 );
111 }
112
113 #[test]
114 fn multiple_headers() {
115 let req = test::TestRequest::default()
116 .append_header((X_FORWARDED_PREFIX, "/foo"))
117 .append_header((X_FORWARDED_PREFIX, "/bar"))
118 .to_http_request();
119
120 assert_eq!(
121 XForwardedPrefix::parse(&req).ok().unwrap(),
122 XForwardedPrefix(PathAndQuery::from_static("/foo")),
123 );
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Display)]
143pub struct ReconstructedPath(pub PathAndQuery);
144
145impl FromRequest for ReconstructedPath {
146 type Error = actix_web::Error;
147 type Future = Ready<Result<Self, Self::Error>>;
148
149 fn from_request(
150 req: &actix_web::HttpRequest,
151 _payload: &mut actix_http::Payload,
152 ) -> Self::Future {
153 let parts = req.head().uri.clone().into_parts();
154 let path_and_query = parts
155 .path_and_query
156 .unwrap_or(PathAndQuery::from_static("/"));
157
158 let prefix = XForwardedPrefix::parse(req).unwrap();
159
160 let reconstructed = [prefix.as_str(), path_and_query.as_str()].concat();
161
162 ready(Ok(ReconstructedPath(
163 PathAndQuery::from_maybe_shared(reconstructed).unwrap(),
164 )))
165 }
166}
167
168#[cfg(test)]
169mod extractor_tests {
170 use actix_web::test::{self};
171
172 use super::*;
173
174 #[actix_web::test]
175 async fn basic() {
176 let req = test::TestRequest::with_uri("/bar")
177 .insert_header((X_FORWARDED_PREFIX, "/foo"))
178 .to_http_request();
179
180 assert_eq!(
181 ReconstructedPath::extract(&req).await.unwrap(),
182 ReconstructedPath(PathAndQuery::from_static("/foo/bar")),
183 );
184 }
185}