1use crate::request::Parts;
2use crate::{HttpRequestParts, Request};
3use crate::{Uri, Version};
4#[cfg(not(feature = "tls"))]
5use rama_core::extensions::Extension;
6use rama_core::extensions::{Extensions, ExtensionsRef};
7use rama_core::telemetry::tracing;
8use rama_net::Protocol;
9use rama_net::address::{Domain, Host, HostWithOptPort};
10use rama_net::forwarded::Forwarded;
11use rama_net::transport::TransportProtocol;
12use rama_net::{
13 AuthorityInputExt, HttpVersionInputExt, PathInputExt, ProtocolInputExt,
14 TransportProtocolInputExt, UriInputExt,
15};
16
17#[cfg(feature = "tls")]
18use rama_tls::SecureTransport;
19
20#[cfg(feature = "tls")]
21fn try_get_sni_from_secure_transport(t: &SecureTransport) -> Option<Domain> {
22 use rama_tls::client::ClientHelloExtension;
23
24 t.client_hello().and_then(|h| {
25 h.extensions().iter().find_map(|e| match e {
26 ClientHelloExtension::ServerName(maybe_domain) => maybe_domain.clone(),
27 _ => None,
28 })
29 })
30}
31
32#[cfg(not(feature = "tls"))]
33#[derive(Debug, Clone, Extension)]
34#[extension(tags(tls))]
35#[non_exhaustive]
36struct SecureTransport;
37
38#[cfg(not(feature = "tls"))]
39fn try_get_sni_from_secure_transport(_: &SecureTransport) -> Option<Domain> {
40 None
41}
42
43pub(crate) fn authority_from_http_parts(parts: &impl HttpRequestParts) -> Option<HostWithOptPort> {
47 let uri = parts.uri();
48
49 let protocol = protocol_from_uri_or_extensions(parts.extensions(), uri);
50 let default_port = uri
51 .port_u16()
52 .unwrap_or_else(|| protocol.default_port().unwrap_or(80));
53
54 uri.host()
55 .map(|h| {
56 let h: Host = h.into_owned();
57 tracing::trace!(url.full = %uri, "request context: detected host {h} from (abs) uri");
58 (h, default_port).into()
59 })
60 .or_else(|| {
61 parts
62 .extensions()
63 .get_ref()
64 .and_then(try_get_sni_from_secure_transport)
65 .map(|host| {
66 tracing::trace!(url.full = %uri, "request context: detected host {host} from SNI");
67 (host, default_port).into()
68 })
69 })
70 .or_else(|| {
71 parts.extensions().get_ref::<Forwarded>().and_then(|f| {
72 f.client_host().map(|fauth| {
73 let HostWithOptPort { host, port } = fauth.0.clone();
74 let port = port.as_u16().unwrap_or(default_port);
75 tracing::trace!(url.full = %uri, "request context: detected host {host} from forwarded info");
76 (host, port).into()
77 })
78 })
79 })
80 .or_else(|| {
81 parts
82 .headers()
83 .get(crate::header::HOST)
84 .and_then(|host_header_value| {
85 HostWithOptPort::try_from(host_header_value.as_bytes()).ok()
86 })
87 })
88}
89
90pub(crate) fn http_version_from_http_parts(parts: &impl HttpRequestParts) -> Version {
93 parts
94 .extensions()
95 .get_ref::<Forwarded>()
96 .and_then(|f| {
97 f.client_version().map(|v| match v {
98 rama_net::forwarded::ForwardedVersion::HTTP_09 => Version::HTTP_09,
99 rama_net::forwarded::ForwardedVersion::HTTP_10 => Version::HTTP_10,
100 rama_net::forwarded::ForwardedVersion::HTTP_11 => Version::HTTP_11,
101 rama_net::forwarded::ForwardedVersion::HTTP_2 => Version::HTTP_2,
102 rama_net::forwarded::ForwardedVersion::HTTP_3 => Version::HTTP_3,
103 })
104 })
105 .unwrap_or_else(|| parts.version())
106}
107
108pub fn protocol_from_uri_or_extensions<'a>(ext: &'a Extensions, uri: &'a Uri) -> &'a Protocol {
116 uri.scheme().or_else(|| {
117 ext.get_ref::<Protocol>()
122 }).or_else(|| ext.get_ref::<Forwarded>()
123 .and_then(|f| f.client_proto().map(|p| {
124 tracing::trace!(url.furi = %uri, "request context: detected protocol from forwarded client proto");
125 if p.is_secure() { &Protocol::HTTPS } else { &Protocol::HTTP }
126 })))
127 .unwrap_or_else(||
128 if ext.contains::<SecureTransport>() {
129 &Protocol::HTTPS
130 } else {
131 &Protocol::HTTP
132 })
133}
134
135impl<Body> AuthorityInputExt for Request<Body> {
136 fn authority(&self) -> Option<HostWithOptPort> {
137 authority_from_http_parts(self)
138 }
139}
140
141impl AuthorityInputExt for Parts {
142 fn authority(&self) -> Option<HostWithOptPort> {
143 authority_from_http_parts(self)
144 }
145}
146
147impl<Body> ProtocolInputExt for Request<Body> {
148 fn protocol(&self) -> Option<&Protocol> {
149 Some(protocol_from_uri_or_extensions(
150 self.extensions(),
151 self.uri(),
152 ))
153 }
154}
155
156impl ProtocolInputExt for Parts {
157 fn protocol(&self) -> Option<&Protocol> {
158 Some(protocol_from_uri_or_extensions(
159 self.extensions(),
160 HttpRequestParts::uri(self),
161 ))
162 }
163}
164
165impl<Body> HttpVersionInputExt for Request<Body> {
166 fn http_version(&self) -> Option<Version> {
167 Some(http_version_from_http_parts(self))
168 }
169}
170
171impl HttpVersionInputExt for Parts {
172 fn http_version(&self) -> Option<Version> {
173 Some(http_version_from_http_parts(self))
174 }
175}
176
177fn transport_protocol_for_http_version(version: Version) -> TransportProtocol {
179 match version {
180 Version::HTTP_3 => TransportProtocol::Udp,
181 _ => TransportProtocol::Tcp,
182 }
183}
184
185impl<Body> TransportProtocolInputExt for Request<Body> {
186 fn transport_protocol(&self) -> Option<TransportProtocol> {
187 Some(transport_protocol_for_http_version(self.version()))
188 }
189}
190
191impl TransportProtocolInputExt for Parts {
192 fn transport_protocol(&self) -> Option<TransportProtocol> {
193 Some(transport_protocol_for_http_version(self.version()))
194 }
195}
196
197impl<Body> UriInputExt for Request<Body> {
198 fn uri(&self) -> &Uri {
199 HttpRequestParts::uri(self)
200 }
201}
202
203impl UriInputExt for Parts {
204 fn uri(&self) -> &Uri {
205 HttpRequestParts::uri(self)
206 }
207}
208
209impl<Body> PathInputExt for Request<Body> {
210 fn path_ref(&self) -> rama_net::uri::PathRef<'_> {
211 self.uri().path_ref_or_root()
212 }
213}
214
215impl PathInputExt for Parts {
216 fn path_ref(&self) -> rama_net::uri::PathRef<'_> {
217 HttpRequestParts::uri(self).path_ref_or_root()
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::{Request, header::FORWARDED};
225 use rama_core::extensions::ExtensionsRef;
226 use rama_net::forwarded::{Forwarded, ForwardedElement, NodeId};
227
228 #[test]
229 fn accessors_from_request() {
230 let req = Request::builder()
231 .uri("http://example.com:8080")
232 .version(Version::HTTP_11)
233 .body(())
234 .unwrap();
235
236 assert_eq!(req.http_version(), Some(Version::HTTP_11));
237 assert_eq!(req.protocol(), Some(&Protocol::HTTP));
238 assert_eq!(req.authority().unwrap().to_string(), "example.com:8080");
239 }
240
241 #[test]
242 fn path_accessor_from_request_and_parts() {
243 let req = Request::builder()
244 .uri("http://example.com/a%2Fb?q=1")
245 .body(())
246 .unwrap();
247
248 assert_eq!(req.path_ref(), "/a%2Fb");
249 assert_ne!(req.path_ref(), "/a/b");
250
251 let (parts, _) = req.into_parts();
252 assert_eq!(parts.path_ref(), "/a%2Fb");
253 assert_ne!(parts.path_ref(), "/a/b");
254 }
255
256 #[test]
257 fn accessors_resolve() {
258 let req = Request::builder()
259 .uri("https://example.com:8443")
260 .version(Version::HTTP_2)
261 .body(())
262 .unwrap();
263 assert_eq!(req.authority().unwrap().to_string(), "example.com:8443");
264 assert_eq!(req.protocol(), Some(&Protocol::HTTPS));
265 assert_eq!(req.http_version(), Some(Version::HTTP_2));
266
267 let req = Request::builder().uri("/path").body(()).unwrap();
270 assert_eq!(req.authority(), None);
271 assert_eq!(req.protocol(), Some(&Protocol::HTTP));
272 assert_eq!(req.http_version(), Some(Version::HTTP_11));
273 }
274
275 #[test]
276 fn accessors_from_parts() {
277 let req = Request::builder()
278 .uri("http://example.com:8080")
279 .version(Version::HTTP_11)
280 .body(())
281 .unwrap();
282
283 let (parts, _) = req.into_parts();
284
285 assert_eq!(parts.http_version(), Some(Version::HTTP_11));
286 assert_eq!(parts.protocol(), Some(&Protocol::HTTP));
287 assert_eq!(
288 parts.authority().unwrap(),
289 HostWithOptPort::try_from("example.com:8080").unwrap()
290 );
291 }
292
293 #[test]
294 fn forwarded_parsing() {
295 for (forwarded_str_vec, expected_authority) in [
296 (
298 vec!["host=192.0.2.60;proto=http;by=203.0.113.43"],
299 "192.0.2.60:80",
300 ),
301 (
303 vec!["host=\"[2001:db8:cafe::17]:4711\""],
304 "[2001:db8:cafe::17]:4711",
305 ),
306 (vec!["host=192.0.2.60, host=127.0.0.1"], "192.0.2.60:80"),
308 (vec!["host=192.0.2.60", "host=127.0.0.1"], "192.0.2.60:80"),
310 ] {
311 let mut req_builder = Request::builder();
312 for header in forwarded_str_vec.clone() {
313 req_builder = req_builder.header(FORWARDED, header);
314 }
315
316 let req = req_builder.body(()).unwrap();
317
318 let forwarded: Forwarded = req
319 .headers()
320 .get(FORWARDED)
321 .unwrap()
322 .as_bytes()
323 .try_into()
324 .unwrap();
325 req.extensions().insert(forwarded);
326
327 assert_eq!(
328 req.authority().map(|a| a.to_string()).as_deref(),
329 Some(expected_authority),
330 "Failed for {forwarded_str_vec:?}"
331 );
332 assert_eq!(
333 req.protocol(),
334 Some(&Protocol::HTTP),
335 "Failed for {forwarded_str_vec:?}"
336 );
337 assert_eq!(
338 req.http_version(),
339 Some(Version::HTTP_11),
340 "Failed for {forwarded_str_vec:?}"
341 );
342 }
343 }
344
345 #[test]
346 fn https_request_behind_haproxy_plain() {
347 let req = Request::builder()
348 .uri("/en/reservation/roomdetails")
349 .version(Version::HTTP_11)
350 .header("host", "echo.ramaproxy.org")
351 .header("user-agent", "curl/8.6.0")
352 .header("accept", "*/*")
353 .body(())
354 .unwrap();
355
356 req.extensions()
357 .insert(Forwarded::new(ForwardedElement::new_forwarded_for(
358 NodeId::try_from("127.0.0.1:61234").unwrap(),
359 )));
360
361 assert_eq!(req.http_version(), Some(Version::HTTP_11));
362 assert_eq!(req.protocol(), Some(&Protocol::HTTP));
363 let authority = req.authority().unwrap();
364 assert_eq!(authority.to_string(), "echo.ramaproxy.org");
365 let default_port = req
366 .protocol_default_port()
367 .unwrap_or(Protocol::HTTP_DEFAULT_PORT);
368 assert_eq!(
369 authority.into_host_with_port_or(default_port).to_string(),
370 "echo.ramaproxy.org:80"
371 );
372 }
373
374 #[cfg(feature = "tls")]
382 #[test]
383 fn secure_transport_marks_origin_form_request_https() {
384 let req = Request::builder()
385 .uri("/ping")
386 .header("host", "example.com")
387 .body(())
388 .unwrap();
389 req.extensions().insert(SecureTransport::default());
390
391 assert_eq!(req.protocol(), Some(&Protocol::HTTPS));
392 }
393}