actix_web/
info.rs

1use std::{convert::Infallible, net::SocketAddr};
2
3use actix_utils::future::{err, ok, Ready};
4use derive_more::{Display, Error};
5
6use crate::{
7    dev::{AppConfig, Payload, RequestHead},
8    http::{
9        header::{self, HeaderName},
10        uri::{Authority, Scheme},
11    },
12    FromRequest, HttpRequest, ResponseError,
13};
14
15static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
16static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
17static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
18
19/// Trim whitespace then any quote marks.
20fn unquote(val: &str) -> &str {
21    val.trim().trim_start_matches('"').trim_end_matches('"')
22}
23
24/// Remove port and IPv6 square brackets from a peer specification.
25fn bare_address(val: &str) -> &str {
26    if val.starts_with('[') {
27        val.split("]:")
28            .next()
29            .map(|s| s.trim_start_matches('[').trim_end_matches(']'))
30            // this indicates that the IPv6 address is malformed so shouldn't
31            // usually happen, but if it does, just return the original input
32            .unwrap_or(val)
33    } else {
34        val.split(':').next().unwrap_or(val)
35    }
36}
37
38/// Extracts and trims first value for given header name.
39fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<&'a str> {
40    let hdr = req.headers.get(name)?.to_str().ok()?;
41    let val = hdr.split(',').next()?.trim();
42    Some(val)
43}
44
45/// HTTP connection information.
46///
47/// `ConnectionInfo` implements `FromRequest` and can be extracted in handlers.
48///
49/// # Examples
50/// ```
51/// # use actix_web::{HttpResponse, Responder};
52/// use actix_web::dev::ConnectionInfo;
53///
54/// async fn handler(conn: ConnectionInfo) -> impl Responder {
55///     match conn.host() {
56///         "actix.rs" => HttpResponse::Ok().body("Welcome!"),
57///         "admin.actix.rs" => HttpResponse::Ok().body("Admin portal."),
58///         _ => HttpResponse::NotFound().finish()
59///     }
60/// }
61/// # let _svc = actix_web::web::to(handler);
62/// ```
63///
64/// # Implementation Notes
65/// Parses `Forwarded` header information according to [RFC 7239][rfc7239] but does not try to
66/// interpret the values for each property. As such, the getter methods on `ConnectionInfo` return
67/// strings instead of IP addresses or other types to acknowledge that they may be
68/// [obfuscated][rfc7239-63] or [unknown][rfc7239-62].
69///
70/// If the older, related headers are also present (eg. `X-Forwarded-For`), then `Forwarded`
71/// is preferred.
72///
73/// [rfc7239]: https://datatracker.ietf.org/doc/html/rfc7239
74/// [rfc7239-62]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.2
75/// [rfc7239-63]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.3
76#[derive(Debug, Clone, Default)]
77pub struct ConnectionInfo {
78    host: String,
79    scheme: String,
80    peer_addr: Option<String>,
81    realip_remote_addr: Option<String>,
82}
83
84impl ConnectionInfo {
85    pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
86        let mut host = None;
87        let mut scheme = None;
88        let mut realip_remote_addr = None;
89
90        for (name, val) in req
91            .headers
92            .get_all(&header::FORWARDED)
93            .filter_map(|hdr| hdr.to_str().ok())
94            // "for=1.2.3.4, for=5.6.7.8; scheme=https"
95            .flat_map(|val| val.split(';'))
96            // ["for=1.2.3.4, for=5.6.7.8", " scheme=https"]
97            .flat_map(|vals| vals.split(','))
98            // ["for=1.2.3.4", " for=5.6.7.8", " scheme=https"]
99            .flat_map(|pair| {
100                let mut items = pair.trim().splitn(2, '=');
101                Some((items.next()?, items.next()?))
102            })
103        {
104            // [(name , val      ), ...                                    ]
105            // [("for", "1.2.3.4"), ("for", "5.6.7.8"), ("scheme", "https")]
106
107            // taking the first value for each property is correct because spec states that first
108            // "for" value is client and rest are proxies; multiple values other properties have
109            // no defined semantics
110            //
111            // > In a chain of proxy servers where this is fully utilized, the first
112            // > "for" parameter will disclose the client where the request was first
113            // > made, followed by any subsequent proxy identifiers.
114            // --- https://datatracker.ietf.org/doc/html/rfc7239#section-5.2
115
116            match name.trim().to_lowercase().as_str() {
117                "for" => realip_remote_addr.get_or_insert_with(|| bare_address(unquote(val))),
118                "proto" => scheme.get_or_insert_with(|| unquote(val)),
119                "host" => host.get_or_insert_with(|| unquote(val)),
120                "by" => {
121                    // TODO: implement https://datatracker.ietf.org/doc/html/rfc7239#section-5.1
122                    continue;
123                }
124                _ => continue,
125            };
126        }
127
128        let scheme = scheme
129            .or_else(|| first_header_value(req, &X_FORWARDED_PROTO))
130            .or_else(|| req.uri.scheme().map(Scheme::as_str))
131            .or_else(|| Some("https").filter(|_| cfg.secure()))
132            .unwrap_or("http")
133            .to_owned();
134
135        let host = host
136            .or_else(|| first_header_value(req, &X_FORWARDED_HOST))
137            .or_else(|| req.headers.get(&header::HOST)?.to_str().ok())
138            .or_else(|| req.uri.authority().map(Authority::as_str))
139            .unwrap_or_else(|| cfg.host())
140            .to_owned();
141
142        let realip_remote_addr = realip_remote_addr
143            .or_else(|| first_header_value(req, &X_FORWARDED_FOR))
144            .map(str::to_owned);
145
146        let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string());
147
148        ConnectionInfo {
149            host,
150            scheme,
151            peer_addr,
152            realip_remote_addr,
153        }
154    }
155
156    /// Real IP (remote address) of client that initiated request.
157    ///
158    /// The address is resolved through the following, in order:
159    /// - `Forwarded` header
160    /// - `X-Forwarded-For` header
161    /// - peer address of opened socket (same as [`remote_addr`](Self::remote_addr))
162    ///
163    /// # Security
164    /// Do not use this function for security purposes unless you can be sure that the `Forwarded`
165    /// and `X-Forwarded-For` headers cannot be spoofed by the client. If you are running without a
166    /// proxy then [obtaining the peer address](Self::peer_addr) would be more appropriate.
167    #[inline]
168    pub fn realip_remote_addr(&self) -> Option<&str> {
169        self.realip_remote_addr
170            .as_deref()
171            .or(self.peer_addr.as_deref())
172    }
173
174    /// Returns serialized IP address of the peer connection.
175    ///
176    /// See [`HttpRequest::peer_addr`] for more details.
177    #[inline]
178    pub fn peer_addr(&self) -> Option<&str> {
179        self.peer_addr.as_deref()
180    }
181
182    /// Hostname of the request.
183    ///
184    /// Hostname is resolved through the following, in order:
185    /// - `Forwarded` header
186    /// - `X-Forwarded-Host` header
187    /// - `Host` header
188    /// - request target / URI
189    /// - configured server hostname
190    #[inline]
191    pub fn host(&self) -> &str {
192        &self.host
193    }
194
195    /// Scheme of the request.
196    ///
197    /// Scheme is resolved through the following, in order:
198    /// - `Forwarded` header
199    /// - `X-Forwarded-Proto` header
200    /// - request target / URI
201    #[inline]
202    pub fn scheme(&self) -> &str {
203        &self.scheme
204    }
205
206    #[doc(hidden)]
207    #[deprecated(since = "4.0.0", note = "Renamed to `peer_addr`.")]
208    pub fn remote_addr(&self) -> Option<&str> {
209        self.peer_addr()
210    }
211}
212
213impl FromRequest for ConnectionInfo {
214    type Error = Infallible;
215    type Future = Ready<Result<Self, Self::Error>>;
216
217    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
218        ok(req.connection_info().clone())
219    }
220}
221
222/// Extractor for peer's socket address.
223///
224/// Also see [`HttpRequest::peer_addr`] and [`ConnectionInfo::peer_addr`].
225///
226/// # Examples
227/// ```
228/// # use actix_web::Responder;
229/// use actix_web::dev::PeerAddr;
230///
231/// async fn handler(peer_addr: PeerAddr) -> impl Responder {
232///     let socket_addr = peer_addr.0;
233///     socket_addr.to_string()
234/// }
235/// # let _svc = actix_web::web::to(handler);
236/// ```
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
238#[display("{}", _0)]
239pub struct PeerAddr(pub SocketAddr);
240
241impl PeerAddr {
242    /// Unwrap into inner `SocketAddr` value.
243    pub fn into_inner(self) -> SocketAddr {
244        self.0
245    }
246}
247
248#[derive(Debug, Display, Error)]
249#[non_exhaustive]
250#[display("Missing peer address")]
251pub struct MissingPeerAddr;
252
253impl ResponseError for MissingPeerAddr {}
254
255impl FromRequest for PeerAddr {
256    type Error = MissingPeerAddr;
257    type Future = Ready<Result<Self, Self::Error>>;
258
259    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
260        match req.peer_addr() {
261            Some(addr) => ok(PeerAddr(addr)),
262            None => {
263                log::error!("Missing peer address.");
264                err(MissingPeerAddr)
265            }
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use crate::test::TestRequest;
274
275    const X_FORWARDED_FOR: &str = "x-forwarded-for";
276    const X_FORWARDED_HOST: &str = "x-forwarded-host";
277    const X_FORWARDED_PROTO: &str = "x-forwarded-proto";
278
279    #[test]
280    fn info_default() {
281        let req = TestRequest::default().to_http_request();
282        let info = req.connection_info();
283        assert_eq!(info.scheme(), "http");
284        assert_eq!(info.host(), "localhost:8080");
285    }
286
287    #[test]
288    fn host_header() {
289        let req = TestRequest::default()
290            .insert_header((header::HOST, "rust-lang.org"))
291            .to_http_request();
292
293        let info = req.connection_info();
294        assert_eq!(info.scheme(), "http");
295        assert_eq!(info.host(), "rust-lang.org");
296        assert_eq!(info.realip_remote_addr(), None);
297    }
298
299    #[test]
300    fn x_forwarded_for_header() {
301        let req = TestRequest::default()
302            .insert_header((X_FORWARDED_FOR, "192.0.2.60"))
303            .to_http_request();
304        let info = req.connection_info();
305        assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
306    }
307
308    #[test]
309    fn x_forwarded_host_header() {
310        let req = TestRequest::default()
311            .insert_header((X_FORWARDED_HOST, "192.0.2.60"))
312            .to_http_request();
313        let info = req.connection_info();
314        assert_eq!(info.host(), "192.0.2.60");
315        assert_eq!(info.realip_remote_addr(), None);
316    }
317
318    #[test]
319    fn x_forwarded_proto_header() {
320        let req = TestRequest::default()
321            .insert_header((X_FORWARDED_PROTO, "https"))
322            .to_http_request();
323        let info = req.connection_info();
324        assert_eq!(info.scheme(), "https");
325    }
326
327    #[test]
328    fn forwarded_header() {
329        let req = TestRequest::default()
330            .insert_header((
331                header::FORWARDED,
332                "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
333            ))
334            .to_http_request();
335
336        let info = req.connection_info();
337        assert_eq!(info.scheme(), "https");
338        assert_eq!(info.host(), "rust-lang.org");
339        assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
340
341        let req = TestRequest::default()
342            .insert_header((
343                header::FORWARDED,
344                "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
345            ))
346            .to_http_request();
347
348        let info = req.connection_info();
349        assert_eq!(info.scheme(), "https");
350        assert_eq!(info.host(), "rust-lang.org");
351        assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
352    }
353
354    #[test]
355    fn forwarded_case_sensitivity() {
356        let req = TestRequest::default()
357            .insert_header((header::FORWARDED, "For=192.0.2.60"))
358            .to_http_request();
359        let info = req.connection_info();
360        assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
361    }
362
363    #[test]
364    fn forwarded_weird_whitespace() {
365        let req = TestRequest::default()
366            .insert_header((header::FORWARDED, "for= 1.2.3.4; proto= https"))
367            .to_http_request();
368        let info = req.connection_info();
369        assert_eq!(info.realip_remote_addr(), Some("1.2.3.4"));
370        assert_eq!(info.scheme(), "https");
371
372        let req = TestRequest::default()
373            .insert_header((header::FORWARDED, "  for = 1.2.3.4  "))
374            .to_http_request();
375        let info = req.connection_info();
376        assert_eq!(info.realip_remote_addr(), Some("1.2.3.4"));
377    }
378
379    #[test]
380    fn forwarded_for_quoted() {
381        let req = TestRequest::default()
382            .insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#))
383            .to_http_request();
384        let info = req.connection_info();
385        assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
386    }
387
388    #[test]
389    fn forwarded_for_ipv6() {
390        let req = TestRequest::default()
391            .insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]""#))
392            .to_http_request();
393        let info = req.connection_info();
394        assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
395    }
396
397    #[test]
398    fn forwarded_for_ipv6_with_port() {
399        let req = TestRequest::default()
400            .insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#))
401            .to_http_request();
402        let info = req.connection_info();
403        assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
404    }
405
406    #[test]
407    fn forwarded_for_multiple() {
408        let req = TestRequest::default()
409            .insert_header((header::FORWARDED, "for=192.0.2.60, for=198.51.100.17"))
410            .to_http_request();
411        let info = req.connection_info();
412        // takes the first value
413        assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
414    }
415
416    #[test]
417    fn scheme_from_uri() {
418        let req = TestRequest::get()
419            .uri("https://actix.rs/test")
420            .to_http_request();
421        let info = req.connection_info();
422        assert_eq!(info.scheme(), "https");
423    }
424
425    #[test]
426    fn host_from_uri() {
427        let req = TestRequest::get()
428            .uri("https://actix.rs/test")
429            .to_http_request();
430        let info = req.connection_info();
431        assert_eq!(info.host(), "actix.rs");
432    }
433
434    #[test]
435    fn host_from_server_hostname() {
436        let mut req = TestRequest::get();
437        req.set_server_hostname("actix.rs");
438        let req = req.to_http_request();
439
440        let info = req.connection_info();
441        assert_eq!(info.host(), "actix.rs");
442    }
443
444    #[actix_rt::test]
445    async fn conn_info_extract() {
446        let req = TestRequest::default()
447            .uri("https://actix.rs/test")
448            .to_http_request();
449        let conn_info = ConnectionInfo::extract(&req).await.unwrap();
450        assert_eq!(conn_info.scheme(), "https");
451        assert_eq!(conn_info.host(), "actix.rs");
452    }
453
454    #[actix_rt::test]
455    async fn peer_addr_extract() {
456        let req = TestRequest::default().to_http_request();
457        let res = PeerAddr::extract(&req).await;
458        assert!(res.is_err());
459
460        let addr = "127.0.0.1:8080".parse().unwrap();
461        let req = TestRequest::default().peer_addr(addr).to_http_request();
462        let peer_addr = PeerAddr::extract(&req).await.unwrap();
463        assert_eq!(peer_addr, PeerAddr(addr));
464    }
465
466    #[actix_rt::test]
467    async fn remote_address() {
468        let req = TestRequest::default().to_http_request();
469        let res = ConnectionInfo::extract(&req).await.unwrap();
470        assert!(res.peer_addr().is_none());
471
472        let addr = "127.0.0.1:8080".parse().unwrap();
473        let req = TestRequest::default().peer_addr(addr).to_http_request();
474        let conn_info = ConnectionInfo::extract(&req).await.unwrap();
475        assert_eq!(conn_info.peer_addr().unwrap(), "127.0.0.1");
476    }
477
478    #[actix_rt::test]
479    async fn real_ip_from_socket_addr() {
480        let req = TestRequest::default().to_http_request();
481        let res = ConnectionInfo::extract(&req).await.unwrap();
482        assert!(res.realip_remote_addr().is_none());
483
484        let addr = "127.0.0.1:8080".parse().unwrap();
485        let req = TestRequest::default().peer_addr(addr).to_http_request();
486        let conn_info = ConnectionInfo::extract(&req).await.unwrap();
487        assert_eq!(conn_info.realip_remote_addr().unwrap(), "127.0.0.1");
488    }
489}