use std::{convert::Infallible, net::SocketAddr};
use actix_utils::future::{err, ok, Ready};
use derive_more::{Display, Error};
use crate::{
dev::{AppConfig, Payload, RequestHead},
http::{
header::{self, HeaderName},
uri::{Authority, Scheme},
},
FromRequest, HttpRequest, ResponseError,
};
static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
fn unquote(val: &str) -> &str {
val.trim().trim_start_matches('"').trim_end_matches('"')
}
fn bare_address(val: &str) -> &str {
if val.starts_with('[') {
val.split("]:")
.next()
.map(|s| s.trim_start_matches('[').trim_end_matches(']'))
.unwrap_or(val)
} else {
val.split(':').next().unwrap_or(val)
}
}
fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<&'a str> {
let hdr = req.headers.get(name)?.to_str().ok()?;
let val = hdr.split(',').next()?.trim();
Some(val)
}
#[derive(Debug, Clone, Default)]
pub struct ConnectionInfo {
host: String,
scheme: String,
peer_addr: Option<String>,
realip_remote_addr: Option<String>,
}
impl ConnectionInfo {
pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
let mut host = None;
let mut scheme = None;
let mut realip_remote_addr = None;
for (name, val) in req
.headers
.get_all(&header::FORWARDED)
.filter_map(|hdr| hdr.to_str().ok())
.flat_map(|val| val.split(';'))
.flat_map(|vals| vals.split(','))
.flat_map(|pair| {
let mut items = pair.trim().splitn(2, '=');
Some((items.next()?, items.next()?))
})
{
match name.trim().to_lowercase().as_str() {
"for" => realip_remote_addr.get_or_insert_with(|| bare_address(unquote(val))),
"proto" => scheme.get_or_insert_with(|| unquote(val)),
"host" => host.get_or_insert_with(|| unquote(val)),
"by" => {
continue;
}
_ => continue,
};
}
let scheme = scheme
.or_else(|| first_header_value(req, &X_FORWARDED_PROTO))
.or_else(|| req.uri.scheme().map(Scheme::as_str))
.or_else(|| Some("https").filter(|_| cfg.secure()))
.unwrap_or("http")
.to_owned();
let host = host
.or_else(|| first_header_value(req, &X_FORWARDED_HOST))
.or_else(|| req.headers.get(&header::HOST)?.to_str().ok())
.or_else(|| req.uri.authority().map(Authority::as_str))
.unwrap_or_else(|| cfg.host())
.to_owned();
let realip_remote_addr = realip_remote_addr
.or_else(|| first_header_value(req, &X_FORWARDED_FOR))
.map(str::to_owned);
let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string());
ConnectionInfo {
host,
scheme,
peer_addr,
realip_remote_addr,
}
}
#[inline]
pub fn realip_remote_addr(&self) -> Option<&str> {
self.realip_remote_addr
.as_deref()
.or(self.peer_addr.as_deref())
}
#[inline]
pub fn peer_addr(&self) -> Option<&str> {
self.peer_addr.as_deref()
}
#[inline]
pub fn host(&self) -> &str {
&self.host
}
#[inline]
pub fn scheme(&self) -> &str {
&self.scheme
}
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Renamed to `peer_addr`.")]
pub fn remote_addr(&self) -> Option<&str> {
self.peer_addr()
}
}
impl FromRequest for ConnectionInfo {
type Error = Infallible;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ok(req.connection_info().clone())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[display(fmt = "{}", _0)]
pub struct PeerAddr(pub SocketAddr);
impl PeerAddr {
pub fn into_inner(self) -> SocketAddr {
self.0
}
}
#[derive(Debug, Display, Error)]
#[non_exhaustive]
#[display(fmt = "Missing peer address")]
pub struct MissingPeerAddr;
impl ResponseError for MissingPeerAddr {}
impl FromRequest for PeerAddr {
type Error = MissingPeerAddr;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
match req.peer_addr() {
Some(addr) => ok(PeerAddr(addr)),
None => {
log::error!("Missing peer address.");
err(MissingPeerAddr)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::TestRequest;
const X_FORWARDED_FOR: &str = "x-forwarded-for";
const X_FORWARDED_HOST: &str = "x-forwarded-host";
const X_FORWARDED_PROTO: &str = "x-forwarded-proto";
#[test]
fn info_default() {
let req = TestRequest::default().to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "localhost:8080");
}
#[test]
fn host_header() {
let req = TestRequest::default()
.insert_header((header::HOST, "rust-lang.org"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.realip_remote_addr(), None);
}
#[test]
fn x_forwarded_for_header() {
let req = TestRequest::default()
.insert_header((X_FORWARDED_FOR, "192.0.2.60"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
}
#[test]
fn x_forwarded_host_header() {
let req = TestRequest::default()
.insert_header((X_FORWARDED_HOST, "192.0.2.60"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.host(), "192.0.2.60");
assert_eq!(info.realip_remote_addr(), None);
}
#[test]
fn x_forwarded_proto_header() {
let req = TestRequest::default()
.insert_header((X_FORWARDED_PROTO, "https"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
}
#[test]
fn forwarded_header() {
let req = TestRequest::default()
.insert_header((
header::FORWARDED,
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
let req = TestRequest::default()
.insert_header((
header::FORWARDED,
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
}
#[test]
fn forwarded_case_sensitivity() {
let req = TestRequest::default()
.insert_header((header::FORWARDED, "For=192.0.2.60"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
}
#[test]
fn forwarded_weird_whitespace() {
let req = TestRequest::default()
.insert_header((header::FORWARDED, "for= 1.2.3.4; proto= https"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("1.2.3.4"));
assert_eq!(info.scheme(), "https");
let req = TestRequest::default()
.insert_header((header::FORWARDED, " for = 1.2.3.4 "))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("1.2.3.4"));
}
#[test]
fn forwarded_for_quoted() {
let req = TestRequest::default()
.insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
}
#[test]
fn forwarded_for_ipv6() {
let req = TestRequest::default()
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]""#))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
}
#[test]
fn forwarded_for_ipv6_with_port() {
let req = TestRequest::default()
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
}
#[test]
fn forwarded_for_multiple() {
let req = TestRequest::default()
.insert_header((header::FORWARDED, "for=192.0.2.60, for=198.51.100.17"))
.to_http_request();
let info = req.connection_info();
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
}
#[test]
fn scheme_from_uri() {
let req = TestRequest::get()
.uri("https://actix.rs/test")
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
}
#[test]
fn host_from_uri() {
let req = TestRequest::get()
.uri("https://actix.rs/test")
.to_http_request();
let info = req.connection_info();
assert_eq!(info.host(), "actix.rs");
}
#[test]
fn host_from_server_hostname() {
let mut req = TestRequest::get();
req.set_server_hostname("actix.rs");
let req = req.to_http_request();
let info = req.connection_info();
assert_eq!(info.host(), "actix.rs");
}
#[actix_rt::test]
async fn conn_info_extract() {
let req = TestRequest::default()
.uri("https://actix.rs/test")
.to_http_request();
let conn_info = ConnectionInfo::extract(&req).await.unwrap();
assert_eq!(conn_info.scheme(), "https");
assert_eq!(conn_info.host(), "actix.rs");
}
#[actix_rt::test]
async fn peer_addr_extract() {
let req = TestRequest::default().to_http_request();
let res = PeerAddr::extract(&req).await;
assert!(res.is_err());
let addr = "127.0.0.1:8080".parse().unwrap();
let req = TestRequest::default().peer_addr(addr).to_http_request();
let peer_addr = PeerAddr::extract(&req).await.unwrap();
assert_eq!(peer_addr, PeerAddr(addr));
}
#[actix_rt::test]
async fn remote_address() {
let req = TestRequest::default().to_http_request();
let res = ConnectionInfo::extract(&req).await.unwrap();
assert!(res.peer_addr().is_none());
let addr = "127.0.0.1:8080".parse().unwrap();
let req = TestRequest::default().peer_addr(addr).to_http_request();
let conn_info = ConnectionInfo::extract(&req).await.unwrap();
assert_eq!(conn_info.peer_addr().unwrap(), "127.0.0.1");
}
#[actix_rt::test]
async fn real_ip_from_socket_addr() {
let req = TestRequest::default().to_http_request();
let res = ConnectionInfo::extract(&req).await.unwrap();
assert!(res.realip_remote_addr().is_none());
let addr = "127.0.0.1:8080".parse().unwrap();
let req = TestRequest::default().peer_addr(addr).to_http_request();
let conn_info = ConnectionInfo::extract(&req).await.unwrap();
assert_eq!(conn_info.realip_remote_addr().unwrap(), "127.0.0.1");
}
}