1use std::{convert::Infallible, net::SocketAddr};
2
3#[expect(deprecated, reason = "preserve the public ready future type")]
4use actix_utils::future::{ready, Ready};
5use derive_more::{Display, Error};
6
7use crate::{
8 dev::{AppConfig, Payload, RequestHead},
9 http::{
10 header::{self, HeaderName},
11 uri::{Authority, Scheme},
12 },
13 FromRequest, HttpRequest, ResponseError,
14};
15
16static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
17static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
18static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
19
20fn unquote(val: &str) -> &str {
22 val.trim().trim_start_matches('"').trim_end_matches('"')
23}
24
25fn bare_address(val: &str) -> &str {
27 if val.starts_with('[') {
28 val.split("]:")
29 .next()
30 .map(|s| s.trim_start_matches('[').trim_end_matches(']'))
31 .unwrap_or(val)
34 } else {
35 val.split(':').next().unwrap_or(val)
36 }
37}
38
39fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<&'a str> {
41 let hdr = req.headers.get(name)?.to_str().ok()?;
42 let val = hdr.split(',').next()?.trim();
43 Some(val)
44}
45
46#[derive(Debug, Clone, Default)]
78pub struct ConnectionInfo {
79 host: String,
80 scheme: String,
81 peer_addr: Option<String>,
82 realip_remote_addr: Option<String>,
83}
84
85impl ConnectionInfo {
86 pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
87 let mut host = None;
88 let mut scheme = None;
89 let mut realip_remote_addr = None;
90
91 for (name, val) in req
92 .headers
93 .get_all(&header::FORWARDED)
94 .filter_map(|hdr| hdr.to_str().ok())
95 .flat_map(|val| val.split(';'))
97 .flat_map(|vals| vals.split(','))
99 .flat_map(|pair| {
101 let mut items = pair.trim().splitn(2, '=');
102 Some((items.next()?, items.next()?))
103 })
104 {
105 match name.trim().to_lowercase().as_str() {
118 "for" => realip_remote_addr.get_or_insert_with(|| bare_address(unquote(val))),
119 "proto" => scheme.get_or_insert_with(|| unquote(val)),
120 "host" => host.get_or_insert_with(|| unquote(val)),
121 "by" => {
122 continue;
124 }
125 _ => continue,
126 };
127 }
128
129 let scheme = scheme
130 .or_else(|| first_header_value(req, &X_FORWARDED_PROTO))
131 .or_else(|| req.uri.scheme().map(Scheme::as_str))
132 .or_else(|| cfg.secure().then_some("https"))
133 .unwrap_or("http")
134 .to_owned();
135
136 let host = host
137 .or_else(|| first_header_value(req, &X_FORWARDED_HOST))
138 .or_else(|| req.headers.get(&header::HOST)?.to_str().ok())
139 .or_else(|| req.uri.authority().map(Authority::as_str))
140 .unwrap_or_else(|| cfg.host())
141 .to_owned();
142
143 let realip_remote_addr = realip_remote_addr
144 .or_else(|| first_header_value(req, &X_FORWARDED_FOR))
145 .map(str::to_owned);
146
147 let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string());
148
149 ConnectionInfo {
150 host,
151 scheme,
152 peer_addr,
153 realip_remote_addr,
154 }
155 }
156
157 #[inline]
169 pub fn realip_remote_addr(&self) -> Option<&str> {
170 self.realip_remote_addr
171 .as_deref()
172 .or(self.peer_addr.as_deref())
173 }
174
175 #[inline]
179 pub fn peer_addr(&self) -> Option<&str> {
180 self.peer_addr.as_deref()
181 }
182
183 #[inline]
192 pub fn host(&self) -> &str {
193 &self.host
194 }
195
196 #[inline]
203 pub fn scheme(&self) -> &str {
204 &self.scheme
205 }
206
207 #[doc(hidden)]
208 #[deprecated(since = "4.0.0", note = "Renamed to `peer_addr`.")]
209 pub fn remote_addr(&self) -> Option<&str> {
210 self.peer_addr()
211 }
212}
213
214#[expect(deprecated, reason = "preserve the public ready future type")]
215impl FromRequest for ConnectionInfo {
216 type Error = Infallible;
217 type Future = Ready<Result<Self, Self::Error>>;
218
219 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
220 ready(Ok(req.connection_info().clone()))
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
240#[display("{}", _0)]
241pub struct PeerAddr(pub SocketAddr);
242
243impl PeerAddr {
244 pub fn into_inner(self) -> SocketAddr {
246 self.0
247 }
248}
249
250#[derive(Debug, Display, Error)]
251#[non_exhaustive]
252#[display("Missing peer address")]
253pub struct MissingPeerAddr;
254
255impl ResponseError for MissingPeerAddr {}
256
257#[expect(deprecated, reason = "preserve the public ready future type")]
258impl FromRequest for PeerAddr {
259 type Error = MissingPeerAddr;
260 type Future = Ready<Result<Self, Self::Error>>;
261
262 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
263 match req.peer_addr() {
264 Some(addr) => ready(Ok(PeerAddr(addr))),
265 None => {
266 log::error!("Missing peer address.");
267 ready(Err(MissingPeerAddr))
268 }
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use crate::test::TestRequest;
277
278 const X_FORWARDED_FOR: &str = "x-forwarded-for";
279 const X_FORWARDED_HOST: &str = "x-forwarded-host";
280 const X_FORWARDED_PROTO: &str = "x-forwarded-proto";
281
282 #[test]
283 fn info_default() {
284 let req = TestRequest::default().to_http_request();
285 let info = req.connection_info();
286 assert_eq!(info.scheme(), "http");
287 assert_eq!(info.host(), "localhost:8080");
288 }
289
290 #[test]
291 fn host_header() {
292 let req = TestRequest::default()
293 .insert_header((header::HOST, "rust-lang.org"))
294 .to_http_request();
295
296 let info = req.connection_info();
297 assert_eq!(info.scheme(), "http");
298 assert_eq!(info.host(), "rust-lang.org");
299 assert_eq!(info.realip_remote_addr(), None);
300 }
301
302 #[test]
303 fn x_forwarded_for_header() {
304 let req = TestRequest::default()
305 .insert_header((X_FORWARDED_FOR, "192.0.2.60"))
306 .to_http_request();
307 let info = req.connection_info();
308 assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
309 }
310
311 #[test]
312 fn x_forwarded_host_header() {
313 let req = TestRequest::default()
314 .insert_header((X_FORWARDED_HOST, "192.0.2.60"))
315 .to_http_request();
316 let info = req.connection_info();
317 assert_eq!(info.host(), "192.0.2.60");
318 assert_eq!(info.realip_remote_addr(), None);
319 }
320
321 #[test]
322 fn x_forwarded_proto_header() {
323 let req = TestRequest::default()
324 .insert_header((X_FORWARDED_PROTO, "https"))
325 .to_http_request();
326 let info = req.connection_info();
327 assert_eq!(info.scheme(), "https");
328 }
329
330 #[test]
331 fn forwarded_header() {
332 let req = TestRequest::default()
333 .insert_header((
334 header::FORWARDED,
335 "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
336 ))
337 .to_http_request();
338
339 let info = req.connection_info();
340 assert_eq!(info.scheme(), "https");
341 assert_eq!(info.host(), "rust-lang.org");
342 assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
343
344 let req = TestRequest::default()
345 .insert_header((
346 header::FORWARDED,
347 "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
348 ))
349 .to_http_request();
350
351 let info = req.connection_info();
352 assert_eq!(info.scheme(), "https");
353 assert_eq!(info.host(), "rust-lang.org");
354 assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
355 }
356
357 #[test]
358 fn forwarded_case_sensitivity() {
359 let req = TestRequest::default()
360 .insert_header((header::FORWARDED, "For=192.0.2.60"))
361 .to_http_request();
362 let info = req.connection_info();
363 assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
364 }
365
366 #[test]
367 fn forwarded_weird_whitespace() {
368 let req = TestRequest::default()
369 .insert_header((header::FORWARDED, "for= 1.2.3.4; proto= https"))
370 .to_http_request();
371 let info = req.connection_info();
372 assert_eq!(info.realip_remote_addr(), Some("1.2.3.4"));
373 assert_eq!(info.scheme(), "https");
374
375 let req = TestRequest::default()
376 .insert_header((header::FORWARDED, " for = 1.2.3.4 "))
377 .to_http_request();
378 let info = req.connection_info();
379 assert_eq!(info.realip_remote_addr(), Some("1.2.3.4"));
380 }
381
382 #[test]
383 fn forwarded_for_quoted() {
384 let req = TestRequest::default()
385 .insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#))
386 .to_http_request();
387 let info = req.connection_info();
388 assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
389 }
390
391 #[test]
392 fn forwarded_for_ipv6() {
393 let req = TestRequest::default()
394 .insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]""#))
395 .to_http_request();
396 let info = req.connection_info();
397 assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
398 }
399
400 #[test]
401 fn forwarded_for_ipv6_with_port() {
402 let req = TestRequest::default()
403 .insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#))
404 .to_http_request();
405 let info = req.connection_info();
406 assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
407 }
408
409 #[test]
410 fn forwarded_for_multiple() {
411 let req = TestRequest::default()
412 .insert_header((header::FORWARDED, "for=192.0.2.60, for=198.51.100.17"))
413 .to_http_request();
414 let info = req.connection_info();
415 assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
417 }
418
419 #[test]
420 fn scheme_from_uri() {
421 let req = TestRequest::get()
422 .uri("https://actix.rs/test")
423 .to_http_request();
424 let info = req.connection_info();
425 assert_eq!(info.scheme(), "https");
426 }
427
428 #[test]
429 fn host_from_uri() {
430 let req = TestRequest::get()
431 .uri("https://actix.rs/test")
432 .to_http_request();
433 let info = req.connection_info();
434 assert_eq!(info.host(), "actix.rs");
435 }
436
437 #[test]
438 fn host_from_server_hostname() {
439 let mut req = TestRequest::get();
440 req.set_server_hostname("actix.rs");
441 let req = req.to_http_request();
442
443 let info = req.connection_info();
444 assert_eq!(info.host(), "actix.rs");
445 }
446
447 #[actix_rt::test]
448 async fn conn_info_extract() {
449 let req = TestRequest::default()
450 .uri("https://actix.rs/test")
451 .to_http_request();
452 let conn_info = ConnectionInfo::extract(&req).await.unwrap();
453 assert_eq!(conn_info.scheme(), "https");
454 assert_eq!(conn_info.host(), "actix.rs");
455 }
456
457 #[actix_rt::test]
458 async fn peer_addr_extract() {
459 let req = TestRequest::default().to_http_request();
460 let res = PeerAddr::extract(&req).await;
461 assert!(res.is_err());
462
463 let addr = "127.0.0.1:8080".parse().unwrap();
464 let req = TestRequest::default().peer_addr(addr).to_http_request();
465 let peer_addr = PeerAddr::extract(&req).await.unwrap();
466 assert_eq!(peer_addr, PeerAddr(addr));
467 }
468
469 #[actix_rt::test]
470 async fn remote_address() {
471 let req = TestRequest::default().to_http_request();
472 let res = ConnectionInfo::extract(&req).await.unwrap();
473 assert!(res.peer_addr().is_none());
474
475 let addr = "127.0.0.1:8080".parse().unwrap();
476 let req = TestRequest::default().peer_addr(addr).to_http_request();
477 let conn_info = ConnectionInfo::extract(&req).await.unwrap();
478 assert_eq!(conn_info.peer_addr().unwrap(), "127.0.0.1");
479 }
480
481 #[actix_rt::test]
482 async fn real_ip_from_socket_addr() {
483 let req = TestRequest::default().to_http_request();
484 let res = ConnectionInfo::extract(&req).await.unwrap();
485 assert!(res.realip_remote_addr().is_none());
486
487 let addr = "127.0.0.1:8080".parse().unwrap();
488 let req = TestRequest::default().peer_addr(addr).to_http_request();
489 let conn_info = ConnectionInfo::extract(&req).await.unwrap();
490 assert_eq!(conn_info.realip_remote_addr().unwrap(), "127.0.0.1");
491 }
492}