1use super::{Ctx, get_hostname};
17use ahash::AHashSet;
18use bytes::BytesMut;
19use http::header;
20use http::{HeaderName, HeaderValue};
21use ipnet::IpNet;
22use pingora::http::RequestHeader;
23use pingora::proxy::Session;
24use snafu::{ResultExt, Snafu};
25use std::borrow::Cow;
26use std::fmt::Write;
27use std::net::IpAddr;
28use std::str::FromStr;
29use std::sync::atomic::{AtomicBool, Ordering};
30use std::sync::{LazyLock, RwLock};
31
32const HTTP_HEADER_X_FORWARDED_FOR: &str = "x-forwarded-for";
34const HTTP_HEADER_X_REAL_IP: &str = "x-real-ip";
35
36pub const HOST_NAME_TAG: &[u8] = b"$hostname";
39const HOST_TAG: &[u8] = b"$host";
40const SCHEME_TAG: &[u8] = b"$scheme";
41const REMOTE_ADDR_TAG: &[u8] = b"$remote_addr";
42const REMOTE_PORT_TAG: &[u8] = b"$remote_port";
43const SERVER_ADDR_TAG: &[u8] = b"$server_addr";
44const SERVER_PORT_TAG: &[u8] = b"$server_port";
45const PROXY_ADD_FORWARDED_TAG: &[u8] = b"$proxy_add_x_forwarded_for";
46const UPSTREAM_ADDR_TAG: &[u8] = b"$upstream_addr";
47
48static SCHEME_HTTPS: HeaderValue = HeaderValue::from_static("https");
50static SCHEME_HTTP: HeaderValue = HeaderValue::from_static("http");
51
52#[derive(Debug, Snafu)]
54pub enum Error {
55 #[snafu(display("invalid header value: {value} - {source}"))]
57 InvalidHeaderValue {
58 value: String,
59 source: header::InvalidHeaderValue,
60 },
61 #[snafu(display("invalid header name: {value} - {source}"))]
63 InvalidHeaderName {
64 value: String,
65 source: header::InvalidHeaderName,
66 },
67}
68type Result<T, E = Error> = std::result::Result<T, E>;
70
71pub type HttpHeader = (HeaderName, HeaderValue);
73
74pub fn get_host(header: &RequestHeader) -> Option<&str> {
79 if let Some(host) = header.uri.host() {
82 return Some(host);
83 }
84 header
86 .headers
87 .get(http::header::HOST)
88 .and_then(|value| value.to_str().ok())
90 .and_then(|host| host.split(':').next())
92}
93
94pub fn convert_header(value: &str) -> Result<Option<HttpHeader>> {
99 value
101 .split_once(':')
102 .map(|(k, v)| {
104 let name = HeaderName::from_str(k.trim())
106 .context(InvalidHeaderNameSnafu { value: k })?;
107 let value = HeaderValue::from_str(v.trim())
109 .context(InvalidHeaderValueSnafu { value: v })?;
110 Ok(Some((name, value)))
112 })
113 .unwrap_or(Ok(None))
115}
116
117pub fn convert_headers(header_values: &[String]) -> Result<Vec<HttpHeader>> {
122 header_values
123 .iter()
124 .filter_map(|item| convert_header(item).transpose())
127 .collect()
129}
130
131pub static HTTP_HEADER_NO_STORE: HttpHeader = (
133 header::CACHE_CONTROL,
134 HeaderValue::from_static("private, no-store"),
135);
136pub static HTTP_HEADER_NO_CACHE: HttpHeader = (
137 header::CACHE_CONTROL,
138 HeaderValue::from_static("private, no-cache"),
139);
140pub static HTTP_HEADER_CONTENT_JSON: HttpHeader = (
141 header::CONTENT_TYPE,
142 HeaderValue::from_static("application/json; charset=utf-8"),
143);
144pub static HTTP_HEADER_CONTENT_HTML: HttpHeader = (
145 header::CONTENT_TYPE,
146 HeaderValue::from_static("text/html; charset=utf-8"),
147);
148pub static HTTP_HEADER_CONTENT_TEXT: HttpHeader = (
149 header::CONTENT_TYPE,
150 HeaderValue::from_static("text/plain; charset=utf-8"),
151);
152pub static HTTP_HEADER_TRANSFER_CHUNKED: HttpHeader = (
153 header::TRANSFER_ENCODING,
154 HeaderValue::from_static("chunked"),
155);
156pub static HTTP_HEADER_NAME_X_REQUEST_ID: HeaderName =
157 HeaderName::from_static("x-request-id");
158
159#[inline]
162pub fn convert_header_value(
163 value: &HeaderValue,
164 session: &Session,
165 ctx: &Ctx,
166) -> Option<HeaderValue> {
167 let buf = value.as_bytes();
169
170 if buf.is_empty() || !(buf[0] == b'$' || buf[0] == b':') {
173 return None;
174 }
175
176 let to_header_value = |s: &str| HeaderValue::from_str(s).ok();
178
179 match buf {
181 HOST_TAG => get_host(session.req_header()).and_then(to_header_value),
182 SCHEME_TAG => Some(if ctx.conn.tls_version.is_some() {
183 SCHEME_HTTPS.clone()
184 } else {
185 SCHEME_HTTP.clone()
186 }),
187 HOST_NAME_TAG => to_header_value(get_hostname()),
188 REMOTE_ADDR_TAG => {
189 ctx.conn.remote_addr.as_deref().and_then(to_header_value)
190 },
191 REMOTE_PORT_TAG => ctx.conn.remote_port.and_then(|p| {
192 HeaderValue::from_str(itoa::Buffer::new().format(p)).ok()
195 }),
196 SERVER_ADDR_TAG => {
197 ctx.conn.server_addr.as_deref().and_then(to_header_value)
198 },
199 SERVER_PORT_TAG => ctx.conn.server_port.and_then(|p| {
200 HeaderValue::from_str(itoa::Buffer::new().format(p)).ok()
201 }),
202 UPSTREAM_ADDR_TAG => {
203 if !ctx.upstream.address.is_empty() {
204 to_header_value(&ctx.upstream.address)
205 } else {
206 None
207 }
208 },
209 PROXY_ADD_FORWARDED_TAG => {
210 ctx.conn.remote_addr.as_deref().and_then(|remote_addr| {
211 let existing = session.get_header(HTTP_HEADER_X_FORWARDED_FOR);
213 let capacity =
214 existing.map(|v| v.as_bytes().len() + 2).unwrap_or(0)
215 + remote_addr.len();
216 let mut value_buf = BytesMut::with_capacity(capacity);
217 if let Some(existing) = existing {
218 value_buf.extend_from_slice(existing.as_bytes());
219 value_buf.extend_from_slice(b", ");
220 }
221 value_buf.extend_from_slice(remote_addr.as_bytes());
222 HeaderValue::from_bytes(&value_buf).ok()
223 })
224 },
225 _ => handle_special_headers(buf, session, ctx),
227 }
228}
229
230#[inline]
233fn handle_special_headers(
234 buf: &[u8],
235 session: &Session,
236 ctx: &Ctx,
237) -> Option<HeaderValue> {
238 if buf.starts_with(b"$http_") {
240 let key = std::str::from_utf8(&buf[6..]).ok()?;
242 return session.get_header(key).cloned();
244 }
245 if buf.starts_with(b"$") {
247 let var_name = std::str::from_utf8(&buf[1..]).ok()?;
248 return std::env::var(var_name)
250 .ok()
251 .and_then(|v| HeaderValue::from_str(&v).ok());
252 }
253 if buf.starts_with(b":") {
255 let key = std::str::from_utf8(&buf[1..]).ok()?;
256 let mut value = BytesMut::with_capacity(20);
258 ctx.append_log_value(&mut value, key);
259 if !value.is_empty() {
260 return HeaderValue::from_bytes(&value).ok();
262 }
263 }
264 None
266}
267
268pub fn get_remote_addr(session: &Session) -> Option<(String, u16)> {
270 session
271 .client_addr()
272 .and_then(|addr| addr.as_inet())
274 .map(|addr| (addr.ip().to_string(), addr.port()))
276}
277
278struct TrustedProxies {
281 nets: Vec<IpNet>,
282 ips: AHashSet<IpAddr>,
283}
284
285impl TrustedProxies {
286 fn parse(values: &[String]) -> Self {
289 let mut nets = Vec::new();
290 let mut ips = AHashSet::new();
291 for item in values {
292 if let Ok(net) = IpNet::from_str(item) {
293 nets.push(net);
294 } else if let Ok(ip) = IpAddr::from_str(item) {
295 ips.insert(ip);
296 }
297 }
298 Self { nets, ips }
299 }
300
301 fn contains(&self, peer: &str) -> bool {
303 let Ok(addr) = peer.parse::<IpAddr>() else {
304 return false;
305 };
306 self.ips.contains(&addr)
307 || self.nets.iter().any(|net| net.contains(&addr))
308 }
309}
310
311static TRUSTED_PROXIES_ENABLED: AtomicBool = AtomicBool::new(false);
317static TRUSTED_PROXIES: LazyLock<RwLock<Option<TrustedProxies>>> =
318 LazyLock::new(|| RwLock::new(None));
319
320pub fn set_trusted_proxies(proxies: &Option<Vec<String>>) {
327 let parsed = match proxies {
328 Some(list) if !list.is_empty() => Some(TrustedProxies::parse(list)),
329 _ => None,
330 };
331 if let Ok(mut guard) = TRUSTED_PROXIES.write() {
332 TRUSTED_PROXIES_ENABLED.store(parsed.is_some(), Ordering::Relaxed);
333 *guard = parsed;
334 }
335}
336
337fn is_trusted_proxy(peer: &str) -> bool {
339 TRUSTED_PROXIES
340 .read()
341 .ok()
342 .and_then(|guard| guard.as_ref().map(|tp| tp.contains(peer)))
343 .unwrap_or(false)
344}
345
346#[inline]
351pub fn ensure_client_ip<'a>(session: &Session, ctx: &'a mut Ctx) -> &'a str {
352 if ctx.conn.client_ip.is_none() {
353 ctx.conn.client_ip = Some(get_client_ip(session));
354 }
355 ctx.conn.client_ip.as_deref().unwrap_or_default()
357}
358
359pub fn get_client_ip(session: &Session) -> String {
369 if TRUSTED_PROXIES_ENABLED.load(Ordering::Relaxed) {
372 let peer = get_remote_addr(session).map(|(addr, _)| addr);
373 let trusted = peer.as_deref().map(is_trusted_proxy).unwrap_or(false);
374 if !trusted {
375 return peer.unwrap_or_default();
376 }
377 }
378 if let Some(value) = session.get_header(HTTP_HEADER_X_FORWARDED_FOR) {
380 if let Ok(s) = value.to_str()
382 && let Some(ip) = s.split(',').next()
383 {
384 let trimmed_ip = ip.trim();
385 if !trimmed_ip.is_empty() {
386 return trimmed_ip.to_string();
387 }
388 }
389 }
390 if let Some(value) = session.get_header(HTTP_HEADER_X_REAL_IP) {
392 return value.to_str().unwrap_or_default().to_string();
393 }
394 if let Some((addr, _)) = get_remote_addr(session) {
396 return addr;
397 }
398 "".to_string()
400}
401
402pub fn get_req_header_value<'a>(
404 req_header: &'a RequestHeader,
405 key: &str,
406) -> Option<&'a str> {
407 if let Some(value) = req_header.headers.get(key) {
409 if let Ok(value) = value.to_str() {
411 return Some(value);
412 }
413 }
414 None
415}
416
417pub fn get_cookie_value<'a>(
419 req_header: &'a RequestHeader,
420 cookie_name: &str,
421) -> Option<&'a str> {
422 get_req_header_value(req_header, "cookie")?
424 .split(';')
426 .find_map(|item| {
428 item.trim()
431 .strip_prefix(cookie_name)?
432 .strip_prefix('=')
433 .or_else(|| {
434 let (k, v) = item.split_once('=')?;
436 if k.trim() == cookie_name {
437 Some(v.trim())
438 } else {
439 None
440 }
441 })
442 })
443}
444
445pub fn get_query_value<'a>(
447 req_header: &'a RequestHeader,
448 name: &str,
449) -> Option<&'a str> {
450 req_header
452 .uri
453 .query()?
454 .split('&')
456 .find_map(|item| {
458 let (k, v) = item.split_once('=')?;
460 if k == name { Some(v) } else { None }
462 })
463}
464
465pub fn remove_query_from_header(
469 req_header: &mut RequestHeader,
470 name: &str,
471) -> Result<(), http::uri::InvalidUri> {
472 let Some(query_str) = req_header.uri.query() else {
474 return Ok(());
475 };
476
477 let mut new_query = String::with_capacity(query_str.len());
480
481 for item in query_str.split('&') {
483 let key = item.split('=').next().unwrap_or(item);
485
486 if key != name {
488 if !new_query.is_empty() {
490 new_query.push('&');
491 }
492 new_query.push_str(item);
494 }
495 }
496
497 let path = req_header.uri.path();
499 let new_uri_str = if new_query.is_empty() {
501 Cow::Borrowed(path)
503 } else {
504 let mut s = String::with_capacity(path.len() + 1 + new_query.len());
506 let _ = write!(&mut s, "{path}?{new_query}");
508 Cow::Owned(s)
509 };
510
511 let new_uri = http::Uri::from_str(&new_uri_str)?;
513 req_header.set_uri(new_uri);
515
516 Ok(())
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522 use crate::{ConnectionInfo, UpstreamInfo};
523 use pretty_assertions::assert_eq;
524 use tokio_test::io::Builder;
525
526 #[test]
527 fn test_convert_headers() {
528 let headers = convert_headers(&[
529 "Content-Type: application/octet-stream".to_string(),
530 "X-Server: $hostname".to_string(),
531 "X-User: $USER".to_string(),
532 ])
533 .unwrap();
534 assert_eq!(3, headers.len());
535 assert_eq!("content-type", headers[0].0.to_string());
536 assert_eq!("application/octet-stream", headers[0].1.to_str().unwrap());
537 assert_eq!("x-server", headers[1].0.to_string());
538 assert_eq!(false, headers[1].1.to_str().unwrap().is_empty());
539 assert_eq!("x-user", headers[2].0.to_string());
540 assert_eq!(false, headers[2].1.to_str().unwrap().is_empty());
541 }
542
543 #[test]
544 fn test_static_value() {
545 assert_eq!(
546 "cache-control: private, no-store",
547 format!(
548 "{}: {}",
549 HTTP_HEADER_NO_STORE.0.to_string(),
550 HTTP_HEADER_NO_STORE.1.to_str().unwrap_or_default()
551 )
552 );
553
554 assert_eq!(
555 "cache-control: private, no-cache",
556 format!(
557 "{}: {}",
558 HTTP_HEADER_NO_CACHE.0.to_string(),
559 HTTP_HEADER_NO_CACHE.1.to_str().unwrap_or_default()
560 )
561 );
562
563 assert_eq!(
564 "content-type: application/json; charset=utf-8",
565 format!(
566 "{}: {}",
567 HTTP_HEADER_CONTENT_JSON.0.to_string(),
568 HTTP_HEADER_CONTENT_JSON.1.to_str().unwrap_or_default()
569 )
570 );
571
572 assert_eq!(
573 "content-type: text/html; charset=utf-8",
574 format!(
575 "{}: {}",
576 HTTP_HEADER_CONTENT_HTML.0.to_string(),
577 HTTP_HEADER_CONTENT_HTML.1.to_str().unwrap_or_default()
578 )
579 );
580
581 assert_eq!(
582 "transfer-encoding: chunked",
583 format!(
584 "{}: {}",
585 HTTP_HEADER_TRANSFER_CHUNKED.0.to_string(),
586 HTTP_HEADER_TRANSFER_CHUNKED.1.to_str().unwrap_or_default()
587 )
588 );
589
590 assert_eq!("x-request-id", HTTP_HEADER_NAME_X_REQUEST_ID.to_string());
591
592 assert_eq!(
593 "content-type: text/plain; charset=utf-8",
594 format!(
595 "{}: {}",
596 HTTP_HEADER_CONTENT_TEXT.0.to_string(),
597 HTTP_HEADER_CONTENT_TEXT.1.to_str().unwrap_or_default()
598 )
599 );
600 }
601
602 #[tokio::test]
603 async fn test_convert_header_value() {
604 let headers = ["Host: pingap.io"].join("\r\n");
605 let input_header =
606 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
607 let mock_io = Builder::new().read(input_header.as_bytes()).build();
608 let mut session = Session::new_h1(Box::new(mock_io));
609 session.read_request().await.unwrap();
610 let default_state = Ctx {
611 upstream: UpstreamInfo {
612 address: "10.1.1.3:4123".to_string(),
613 ..Default::default()
614 },
615 conn: ConnectionInfo {
616 id: 102,
617 remote_addr: Some("10.1.1.1".to_string()),
618 remote_port: Some(6000),
619 server_addr: Some("10.1.1.2".to_string()),
620 server_port: Some(6001),
621 tls_version: Some("tls1.3".into()),
622 ..Default::default()
623 },
624 ..Default::default()
625 };
626
627 let value = convert_header_value(
628 &HeaderValue::from_str("$host").unwrap(),
629 &session,
630 &Ctx {
631 ..Default::default()
632 },
633 );
634 assert_eq!(true, value.is_some());
635 assert_eq!("pingap.io", value.unwrap().to_str().unwrap());
636
637 let value = convert_header_value(
638 &HeaderValue::from_str("$scheme").unwrap(),
639 &session,
640 &Ctx {
641 ..Default::default()
642 },
643 );
644 assert_eq!(true, value.is_some());
645 assert_eq!("http", value.unwrap().to_str().unwrap());
646 let value = convert_header_value(
647 &HeaderValue::from_str("$scheme").unwrap(),
648 &session,
649 &default_state,
650 );
651 assert_eq!(true, value.is_some());
652 assert_eq!("https", value.unwrap().to_str().unwrap());
653
654 let value = convert_header_value(
655 &HeaderValue::from_str("$remote_addr").unwrap(),
656 &session,
657 &default_state,
658 );
659 assert_eq!(true, value.is_some());
660 assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
661
662 let value = convert_header_value(
663 &HeaderValue::from_str("$remote_port").unwrap(),
664 &session,
665 &default_state,
666 );
667 assert_eq!(true, value.is_some());
668 assert_eq!("6000", value.unwrap().to_str().unwrap());
669
670 let value = convert_header_value(
671 &HeaderValue::from_str("$server_addr").unwrap(),
672 &session,
673 &default_state,
674 );
675 assert_eq!(true, value.is_some());
676 assert_eq!("10.1.1.2", value.unwrap().to_str().unwrap());
677
678 let value = convert_header_value(
679 &HeaderValue::from_str("$server_port").unwrap(),
680 &session,
681 &default_state,
682 );
683 assert_eq!(true, value.is_some());
684 assert_eq!("6001", value.unwrap().to_str().unwrap());
685
686 let value = convert_header_value(
687 &HeaderValue::from_str("$upstream_addr").unwrap(),
688 &session,
689 &default_state,
690 );
691 assert_eq!(true, value.is_some());
692 assert_eq!("10.1.1.3:4123", value.unwrap().to_str().unwrap());
693
694 let value = convert_header_value(
695 &HeaderValue::from_str(":connection_id").unwrap(),
696 &session,
697 &default_state,
698 );
699 assert_eq!(true, value.is_some());
700 assert_eq!("102", value.unwrap().to_str().unwrap());
701
702 let headers = ["X-Forwarded-For: 1.1.1.1, 2.2.2.2"].join("\r\n");
703 let input_header =
704 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
705 let mock_io = Builder::new().read(input_header.as_bytes()).build();
706 let mut session = Session::new_h1(Box::new(mock_io));
707 session.read_request().await.unwrap();
708 let value = convert_header_value(
709 &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
710 &session,
711 &Ctx {
712 conn: ConnectionInfo {
713 remote_addr: Some("10.1.1.1".to_string()),
714 ..Default::default()
715 },
716 ..Default::default()
717 },
718 );
719 assert_eq!(true, value.is_some());
720 assert_eq!(
721 "1.1.1.1, 2.2.2.2, 10.1.1.1",
722 value.unwrap().to_str().unwrap()
723 );
724
725 let headers = [""].join("\r\n");
726 let input_header =
727 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
728 let mock_io = Builder::new().read(input_header.as_bytes()).build();
729 let mut session = Session::new_h1(Box::new(mock_io));
730 session.read_request().await.unwrap();
731 let value = convert_header_value(
732 &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
733 &session,
734 &Ctx {
735 conn: ConnectionInfo {
736 remote_addr: Some("10.1.1.1".to_string()),
737 ..Default::default()
738 },
739 ..Default::default()
740 },
741 );
742 assert_eq!(true, value.is_some());
743 assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
744
745 let headers = [""].join("\r\n");
746 let input_header =
747 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
748 let mock_io = Builder::new().read(input_header.as_bytes()).build();
749 let mut session = Session::new_h1(Box::new(mock_io));
750 session.read_request().await.unwrap();
751 let value = convert_header_value(
752 &HeaderValue::from_str("$upstream_addr").unwrap(),
753 &session,
754 &Ctx {
755 upstream: UpstreamInfo {
756 address: "10.1.1.1:8001".to_string(),
757 ..Default::default()
758 },
759 ..Default::default()
760 },
761 );
762 assert_eq!(true, value.is_some());
763 assert_eq!("10.1.1.1:8001", value.unwrap().to_str().unwrap());
764
765 let headers = ["Origin: https://github.com"].join("\r\n");
766 let input_header =
767 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
768 let mock_io = Builder::new().read(input_header.as_bytes()).build();
769 let mut session = Session::new_h1(Box::new(mock_io));
770 session.read_request().await.unwrap();
771 let value = convert_header_value(
772 &HeaderValue::from_str("$http_origin").unwrap(),
773 &session,
774 &Ctx::default(),
775 );
776 assert_eq!(true, value.is_some());
777 assert_eq!("https://github.com", value.unwrap().to_str().unwrap());
778
779 let headers = ["Origin: https://github.com"].join("\r\n");
780 let input_header =
781 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
782 let mock_io = Builder::new().read(input_header.as_bytes()).build();
783 let mut session = Session::new_h1(Box::new(mock_io));
784 session.read_request().await.unwrap();
785 let value = convert_header_value(
786 &HeaderValue::from_str("$hostname").unwrap(),
787 &session,
788 &Ctx::default(),
789 );
790 assert_eq!(true, value.is_some());
791
792 let headers = ["Origin: https://github.com"].join("\r\n");
793 let input_header =
794 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
795 let mock_io = Builder::new().read(input_header.as_bytes()).build();
796 let mut session = Session::new_h1(Box::new(mock_io));
797 session.read_request().await.unwrap();
798 let value = convert_header_value(
799 &HeaderValue::from_str("$HOME").unwrap(),
800 &session,
801 &Ctx::default(),
802 );
803 assert_eq!(true, value.is_some());
804
805 let headers = ["Origin: https://github.com"].join("\r\n");
806 let input_header =
807 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
808 let mock_io = Builder::new().read(input_header.as_bytes()).build();
809 let mut session = Session::new_h1(Box::new(mock_io));
810 session.read_request().await.unwrap();
811 let value = convert_header_value(
812 &HeaderValue::from_str("UUID").unwrap(),
813 &session,
814 &Ctx::default(),
815 );
816 assert_eq!(false, value.is_some());
817 }
818
819 #[tokio::test]
820 async fn test_get_host() {
821 let headers = ["Host: pingap.io"].join("\r\n");
822 let input_header =
823 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
824 let mock_io = Builder::new().read(input_header.as_bytes()).build();
825 let mut session = Session::new_h1(Box::new(mock_io));
826 session.read_request().await.unwrap();
827 assert_eq!(get_host(session.req_header()), Some("pingap.io"));
828 }
829
830 #[test]
831 fn test_remove_query_from_header() {
832 let mut req =
833 RequestHeader::build("GET", b"/?apikey=123", None).unwrap();
834 remove_query_from_header(&mut req, "apikey").unwrap();
835 assert_eq!("/", req.uri.to_string());
836
837 let mut req =
838 RequestHeader::build("GET", b"/?apikey=123&name=pingap", None)
839 .unwrap();
840 remove_query_from_header(&mut req, "apikey").unwrap();
841 assert_eq!("/?name=pingap", req.uri.to_string());
842 }
843
844 #[tokio::test]
845 async fn test_get_client_ip() {
846 let headers = ["X-Forwarded-For:192.168.1.1"].join("\r\n");
847 let input_header =
848 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
849 let mock_io = Builder::new().read(input_header.as_bytes()).build();
850 let mut session = Session::new_h1(Box::new(mock_io));
851 session.read_request().await.unwrap();
852 assert_eq!(get_client_ip(&session), "192.168.1.1");
853
854 let headers = ["X-Real-Ip:192.168.1.2"].join("\r\n");
855 let input_header =
856 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
857 let mock_io = Builder::new().read(input_header.as_bytes()).build();
858 let mut session = Session::new_h1(Box::new(mock_io));
859 session.read_request().await.unwrap();
860 assert_eq!(get_client_ip(&session), "192.168.1.2");
861
862 set_trusted_proxies(&Some(vec!["10.0.0.0/8".to_string()]));
866 let headers = ["X-Forwarded-For:192.168.1.1"].join("\r\n");
867 let input_header =
868 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
869 let mock_io = Builder::new().read(input_header.as_bytes()).build();
870 let mut session = Session::new_h1(Box::new(mock_io));
871 session.read_request().await.unwrap();
872 assert_ne!(get_client_ip(&session), "192.168.1.1");
873
874 set_trusted_proxies(&None);
876 let headers = ["X-Forwarded-For:192.168.1.1"].join("\r\n");
877 let input_header =
878 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
879 let mock_io = Builder::new().read(input_header.as_bytes()).build();
880 let mut session = Session::new_h1(Box::new(mock_io));
881 session.read_request().await.unwrap();
882 assert_eq!(get_client_ip(&session), "192.168.1.1");
883 }
884
885 #[tokio::test]
886 async fn test_get_header_value() {
887 let headers = ["Host: pingap.io"].join("\r\n");
888 let input_header =
889 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
890 let mock_io = Builder::new().read(input_header.as_bytes()).build();
891 let mut session = Session::new_h1(Box::new(mock_io));
892 session.read_request().await.unwrap();
893 assert_eq!(
894 get_req_header_value(session.req_header(), "Host"),
895 Some("pingap.io")
896 );
897 }
898
899 #[tokio::test]
900 async fn test_get_cookie_value() {
901 let headers = ["Cookie: name=pingap"].join("\r\n");
902 let input_header =
903 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
904 let mock_io = Builder::new().read(input_header.as_bytes()).build();
905 let mut session = Session::new_h1(Box::new(mock_io));
906 session.read_request().await.unwrap();
907 assert_eq!(
908 get_cookie_value(session.req_header(), "name"),
909 Some("pingap")
910 );
911 }
912
913 #[tokio::test]
914 async fn test_get_query_value() {
915 let headers = ["X-Forwarded-For:192.168.1.1"].join("\r\n");
916 let input_header =
917 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
918 let mock_io = Builder::new().read(input_header.as_bytes()).build();
919 let mut session = Session::new_h1(Box::new(mock_io));
920 session.read_request().await.unwrap();
921 assert_eq!(get_query_value(session.req_header(), "size"), Some("1"));
922 }
923
924 #[test]
927 fn test_convert_header_edge_cases() {
928 assert!(convert_header("").unwrap().is_none());
930 assert!(convert_header("no-colon").unwrap().is_none());
932 assert!(convert_header("Invalid Name: value").is_err());
934 assert!(convert_header("Valid-Name: invalid\r\nvalue").is_err());
936 }
937
938 #[test]
940 fn test_get_host_variants() {
941 let uri_string = "http://user:pass@authority.com/path";
943
944 let uri = http::Uri::from_str(uri_string).unwrap();
946 let mut req_with_authority =
947 RequestHeader::build("GET", b"/path", None).unwrap();
948 req_with_authority.set_uri(uri);
949 assert_eq!(get_host(&req_with_authority), Some("authority.com"));
950
951 let mut req_with_host_header =
953 RequestHeader::build("GET", b"/path", None).unwrap();
954 req_with_host_header
955 .insert_header("Host", "header-host.com:8080")
956 .unwrap();
957 assert_eq!(get_host(&req_with_host_header), Some("header-host.com"));
958
959 let req_no_host = RequestHeader::build("GET", b"/path", None).unwrap();
961 assert_eq!(get_host(&req_no_host), None);
962 }
963
964 #[test]
966 fn test_get_cookie_value_advanced() {
967 let mut req = RequestHeader::build("GET", b"/", None).unwrap();
968 req.insert_header("Cookie", "id=123; session=abc; theme=dark")
969 .unwrap();
970
971 assert_eq!(get_cookie_value(&req, "session"), Some("abc"));
972 assert_eq!(get_cookie_value(&req, "id"), Some("123"));
973 assert_eq!(get_cookie_value(&req, "theme"), Some("dark"));
974 assert_eq!(get_cookie_value(&req, "lang"), None);
976 assert_eq!(get_cookie_value(&req, "the"), None);
978 }
979
980 #[test]
981 fn test_remove_query_from_header_variants() {
982 let mut req =
984 RequestHeader::build("GET", b"/path?key=val", None).unwrap();
985 remove_query_from_header(&mut req, "key").unwrap();
986 assert_eq!(req.uri.to_string(), "/path");
987
988 let mut req =
990 RequestHeader::build("GET", b"/path?key1=val1&key2=val2", None)
991 .unwrap();
992 remove_query_from_header(&mut req, "key1").unwrap();
993 assert_eq!(req.uri.to_string(), "/path?key2=val2");
994
995 let mut req =
997 RequestHeader::build("GET", b"/path?key1=val1&key2=val2", None)
998 .unwrap();
999 remove_query_from_header(&mut req, "key2").unwrap();
1000 assert_eq!(req.uri.to_string(), "/path?key1=val1");
1001
1002 let mut req =
1004 RequestHeader::build("GET", b"/path?key1=v1&key2=v2&key3=v3", None)
1005 .unwrap();
1006 remove_query_from_header(&mut req, "key2").unwrap();
1007 assert_eq!(req.uri.to_string(), "/path?key1=v1&key3=v3");
1008
1009 let mut req =
1011 RequestHeader::build("GET", b"/path?key=val", None).unwrap();
1012 remove_query_from_header(&mut req, "nonexistent").unwrap();
1013 assert_eq!(req.uri.to_string(), "/path?key=val");
1014
1015 let mut req = RequestHeader::build("GET", b"/path", None).unwrap();
1017 remove_query_from_header(&mut req, "key").unwrap();
1018 assert_eq!(req.uri.to_string(), "/path");
1019 }
1020}