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::address::{Domain, Host, HostWithOptPort};
9use rama_net::forwarded::Forwarded;
10use rama_net::transport::TransportProtocol;
11use rama_net::{
12 AuthorityInputExt, HttpVersionInputExt, PathInputExt, ProtocolInputExt,
13 TargetHttpVersionInputExt, TransportProtocolInputExt, UriInputExt,
14};
15use rama_net::{Protocol, http::TargetHttpVersion};
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
177impl<Body> TargetHttpVersionInputExt for Request<Body> {
178 fn target_http_version(&self) -> Option<Version> {
179 self.target_http_version_with_fallback(None)
180 }
181
182 fn target_http_version_with_fallback(&self, fallback: Option<Version>) -> Option<Version> {
183 self.extensions()
184 .get_ref::<TargetHttpVersion>()
185 .map(|target| target.0)
186 .or(fallback)
187 .or(Some(self.version()))
188 }
189}
190
191impl TargetHttpVersionInputExt for Parts {
192 fn target_http_version(&self) -> Option<Version> {
193 self.target_http_version_with_fallback(None)
194 }
195
196 fn target_http_version_with_fallback(&self, fallback: Option<Version>) -> Option<Version> {
197 self.extensions()
198 .get_ref::<TargetHttpVersion>()
199 .map(|target| target.0)
200 .or(fallback)
201 .or(Some(self.version()))
202 }
203}
204
205fn transport_protocol_for_http_version(version: Version) -> TransportProtocol {
207 match version {
208 Version::HTTP_3 => TransportProtocol::Udp,
209 _ => TransportProtocol::Tcp,
210 }
211}
212
213impl<Body> TransportProtocolInputExt for Request<Body> {
214 fn transport_protocol(&self) -> Option<TransportProtocol> {
215 Some(transport_protocol_for_http_version(self.version()))
216 }
217}
218
219impl TransportProtocolInputExt for Parts {
220 fn transport_protocol(&self) -> Option<TransportProtocol> {
221 Some(transport_protocol_for_http_version(self.version()))
222 }
223}
224
225impl<Body> UriInputExt for Request<Body> {
226 fn uri(&self) -> &Uri {
227 HttpRequestParts::uri(self)
228 }
229}
230
231impl UriInputExt for Parts {
232 fn uri(&self) -> &Uri {
233 HttpRequestParts::uri(self)
234 }
235}
236
237impl<Body> PathInputExt for Request<Body> {
238 fn path_ref(&self) -> rama_net::uri::PathRef<'_> {
239 self.uri().path_ref_or_root()
240 }
241}
242
243impl PathInputExt for Parts {
244 fn path_ref(&self) -> rama_net::uri::PathRef<'_> {
245 HttpRequestParts::uri(self).path_ref_or_root()
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use crate::{Request, header::FORWARDED};
253 use rama_core::extensions::ExtensionsRef;
254 use rama_net::forwarded::{Forwarded, ForwardedElement, ForwardedVersion, NodeId};
255
256 #[test]
257 fn accessors_from_request() {
258 let req = Request::builder()
259 .uri("http://example.com:8080")
260 .version(Version::HTTP_11)
261 .body(())
262 .unwrap();
263
264 assert_eq!(req.http_version(), Some(Version::HTTP_11));
265 assert_eq!(req.protocol(), Some(&Protocol::HTTP));
266 assert_eq!(req.authority().unwrap().to_string(), "example.com:8080");
267 }
268
269 #[test]
270 fn path_accessor_from_request_and_parts() {
271 let req = Request::builder()
272 .uri("http://example.com/a%2Fb?q=1")
273 .body(())
274 .unwrap();
275
276 assert_eq!(req.path_ref(), "/a%2Fb");
277 assert_ne!(req.path_ref(), "/a/b");
278
279 let (parts, _) = req.into_parts();
280 assert_eq!(parts.path_ref(), "/a%2Fb");
281 assert_ne!(parts.path_ref(), "/a/b");
282 }
283
284 #[test]
285 fn accessors_resolve() {
286 let req = Request::builder()
287 .uri("https://example.com:8443")
288 .version(Version::HTTP_2)
289 .body(())
290 .unwrap();
291 assert_eq!(req.authority().unwrap().to_string(), "example.com:8443");
292 assert_eq!(req.protocol(), Some(&Protocol::HTTPS));
293 assert_eq!(req.http_version(), Some(Version::HTTP_2));
294
295 let req = Request::builder().uri("/path").body(()).unwrap();
298 assert_eq!(req.authority(), None);
299 assert_eq!(req.protocol(), Some(&Protocol::HTTP));
300 assert_eq!(req.http_version(), Some(Version::HTTP_11));
301 }
302
303 #[test]
304 fn accessors_from_parts() {
305 let req = Request::builder()
306 .uri("http://example.com:8080")
307 .version(Version::HTTP_11)
308 .body(())
309 .unwrap();
310
311 let (parts, _) = req.into_parts();
312
313 assert_eq!(parts.http_version(), Some(Version::HTTP_11));
314 assert_eq!(parts.protocol(), Some(&Protocol::HTTP));
315 assert_eq!(
316 parts.authority().unwrap(),
317 HostWithOptPort::try_from("example.com:8080").unwrap()
318 );
319 }
320
321 #[test]
322 fn target_version_ignores_forwarded_client_version() {
323 let req = Request::builder()
324 .version(Version::HTTP_11)
325 .body(())
326 .unwrap();
327 req.extensions()
328 .insert(Forwarded::new(ForwardedElement::new_forwarded_version(
329 ForwardedVersion::HTTP_2,
330 )));
331
332 assert_eq!(req.http_version(), Some(Version::HTTP_2));
333 assert_eq!(req.target_http_version(), Some(Version::HTTP_11));
334 }
335
336 #[test]
337 fn explicit_target_version_overrides_request_version_for_request_and_parts() {
338 let req = Request::builder()
339 .version(Version::HTTP_11)
340 .body(())
341 .unwrap();
342 req.extensions().insert(TargetHttpVersion(Version::HTTP_2));
343
344 assert_eq!(req.target_http_version(), Some(Version::HTTP_2));
345
346 let (parts, _) = req.into_parts();
347 assert_eq!(parts.target_http_version(), Some(Version::HTTP_2));
348 }
349
350 #[test]
351 fn target_version_fallback_precedes_request_version_but_not_explicit_target() {
352 let req = Request::builder()
353 .version(Version::HTTP_11)
354 .body(())
355 .unwrap();
356
357 assert_eq!(
358 req.target_http_version_with_fallback(Some(Version::HTTP_10)),
359 Some(Version::HTTP_10),
360 );
361
362 req.extensions().insert(TargetHttpVersion(Version::HTTP_2));
363 assert_eq!(
364 req.target_http_version_with_fallback(Some(Version::HTTP_10)),
365 Some(Version::HTTP_2),
366 );
367 }
368
369 #[test]
370 fn forwarded_parsing() {
371 for (forwarded_str_vec, expected_authority) in [
372 (
374 vec!["host=192.0.2.60;proto=http;by=203.0.113.43"],
375 "192.0.2.60:80",
376 ),
377 (
379 vec!["host=\"[2001:db8:cafe::17]:4711\""],
380 "[2001:db8:cafe::17]:4711",
381 ),
382 (vec!["host=192.0.2.60, host=127.0.0.1"], "192.0.2.60:80"),
384 (vec!["host=192.0.2.60", "host=127.0.0.1"], "192.0.2.60:80"),
386 ] {
387 let mut req_builder = Request::builder();
388 for header in forwarded_str_vec.clone() {
389 req_builder = req_builder.header(FORWARDED, header);
390 }
391
392 let req = req_builder.body(()).unwrap();
393
394 let forwarded: Forwarded = req
395 .headers()
396 .get(FORWARDED)
397 .unwrap()
398 .as_bytes()
399 .try_into()
400 .unwrap();
401 req.extensions().insert(forwarded);
402
403 assert_eq!(
404 req.authority().map(|a| a.to_string()).as_deref(),
405 Some(expected_authority),
406 "Failed for {forwarded_str_vec:?}"
407 );
408 assert_eq!(
409 req.protocol(),
410 Some(&Protocol::HTTP),
411 "Failed for {forwarded_str_vec:?}"
412 );
413 assert_eq!(
414 req.http_version(),
415 Some(Version::HTTP_11),
416 "Failed for {forwarded_str_vec:?}"
417 );
418 }
419 }
420
421 #[test]
422 fn https_request_behind_haproxy_plain() {
423 let req = Request::builder()
424 .uri("/en/reservation/roomdetails")
425 .version(Version::HTTP_11)
426 .header("host", "echo.ramaproxy.org")
427 .header("user-agent", "curl/8.6.0")
428 .header("accept", "*/*")
429 .body(())
430 .unwrap();
431
432 req.extensions()
433 .insert(Forwarded::new(ForwardedElement::new_forwarded_for(
434 NodeId::try_from("127.0.0.1:61234").unwrap(),
435 )));
436
437 assert_eq!(req.http_version(), Some(Version::HTTP_11));
438 assert_eq!(req.protocol(), Some(&Protocol::HTTP));
439 let authority = req.authority().unwrap();
440 assert_eq!(authority.to_string(), "echo.ramaproxy.org");
441 let default_port = req
442 .protocol_default_port()
443 .unwrap_or(Protocol::HTTP_DEFAULT_PORT);
444 assert_eq!(
445 authority.into_host_with_port_or(default_port).to_string(),
446 "echo.ramaproxy.org:80"
447 );
448 }
449
450 #[cfg(feature = "tls")]
458 #[test]
459 fn secure_transport_marks_origin_form_request_https() {
460 let req = Request::builder()
461 .uri("/ping")
462 .header("host", "example.com")
463 .body(())
464 .unwrap();
465 req.extensions().insert(SecureTransport::default());
466
467 assert_eq!(req.protocol(), Some(&Protocol::HTTPS));
468 }
469}