1use super::{Ctx, get_hostname};
17use bytes::BytesMut;
18use http::header;
19use http::{HeaderName, HeaderValue};
20use pingora::http::RequestHeader;
21use pingora::proxy::Session;
22use snafu::{ResultExt, Snafu};
23use std::borrow::Cow;
24use std::fmt::Write;
25use std::str::FromStr;
26
27const HTTP_HEADER_X_FORWARDED_FOR: &str = "x-forwarded-for";
29const HTTP_HEADER_X_REAL_IP: &str = "x-real-ip";
30
31pub const HOST_NAME_TAG: &[u8] = b"$hostname";
34const HOST_TAG: &[u8] = b"$host";
35const SCHEME_TAG: &[u8] = b"$scheme";
36const REMOTE_ADDR_TAG: &[u8] = b"$remote_addr";
37const REMOTE_PORT_TAG: &[u8] = b"$remote_port";
38const SERVER_ADDR_TAG: &[u8] = b"$server_addr";
39const SERVER_PORT_TAG: &[u8] = b"$server_port";
40const PROXY_ADD_FORWARDED_TAG: &[u8] = b"$proxy_add_x_forwarded_for";
41const UPSTREAM_ADDR_TAG: &[u8] = b"$upstream_addr";
42
43static SCHEME_HTTPS: HeaderValue = HeaderValue::from_static("https");
45static SCHEME_HTTP: HeaderValue = HeaderValue::from_static("http");
46
47#[derive(Debug, Snafu)]
49pub enum Error {
50 #[snafu(display("invalid header value: {value} - {source}"))]
52 InvalidHeaderValue {
53 value: String,
54 source: header::InvalidHeaderValue,
55 },
56 #[snafu(display("invalid header name: {value} - {source}"))]
58 InvalidHeaderName {
59 value: String,
60 source: header::InvalidHeaderName,
61 },
62}
63type Result<T, E = Error> = std::result::Result<T, E>;
65
66pub type HttpHeader = (HeaderName, HeaderValue);
68
69pub fn get_host(header: &RequestHeader) -> Option<&str> {
74 if let Some(host) = header.uri.host() {
77 return Some(host);
78 }
79 header
81 .headers
82 .get(http::header::HOST)
83 .and_then(|value| value.to_str().ok())
85 .and_then(|host| host.split(':').next())
87}
88
89pub fn convert_header(value: &str) -> Result<Option<HttpHeader>> {
94 value
96 .split_once(':')
97 .map(|(k, v)| {
99 let name = HeaderName::from_str(k.trim())
101 .context(InvalidHeaderNameSnafu { value: k })?;
102 let value = HeaderValue::from_str(v.trim())
104 .context(InvalidHeaderValueSnafu { value: v })?;
105 Ok(Some((name, value)))
107 })
108 .unwrap_or(Ok(None))
110}
111
112pub fn convert_headers(header_values: &[String]) -> Result<Vec<HttpHeader>> {
117 header_values
118 .iter()
119 .filter_map(|item| convert_header(item).transpose())
122 .collect()
124}
125
126pub static HTTP_HEADER_NO_STORE: HttpHeader = (
128 header::CACHE_CONTROL,
129 HeaderValue::from_static("private, no-store"),
130);
131pub static HTTP_HEADER_NO_CACHE: HttpHeader = (
132 header::CACHE_CONTROL,
133 HeaderValue::from_static("private, no-cache"),
134);
135pub static HTTP_HEADER_CONTENT_JSON: HttpHeader = (
136 header::CONTENT_TYPE,
137 HeaderValue::from_static("application/json; charset=utf-8"),
138);
139pub static HTTP_HEADER_CONTENT_HTML: HttpHeader = (
140 header::CONTENT_TYPE,
141 HeaderValue::from_static("text/html; charset=utf-8"),
142);
143pub static HTTP_HEADER_CONTENT_TEXT: HttpHeader = (
144 header::CONTENT_TYPE,
145 HeaderValue::from_static("text/plain; charset=utf-8"),
146);
147pub static HTTP_HEADER_TRANSFER_CHUNKED: HttpHeader = (
148 header::TRANSFER_ENCODING,
149 HeaderValue::from_static("chunked"),
150);
151pub static HTTP_HEADER_NAME_X_REQUEST_ID: HeaderName =
152 HeaderName::from_static("x-request-id");
153
154#[inline]
157pub fn convert_header_value(
158 value: &HeaderValue,
159 session: &Session,
160 ctx: &Ctx,
161) -> Option<HeaderValue> {
162 let buf = value.as_bytes();
164
165 if buf.is_empty() || !(buf[0] == b'$' || buf[0] == b':') {
168 return None;
169 }
170
171 let to_header_value = |s: &str| HeaderValue::from_str(s).ok();
173
174 match buf {
176 HOST_TAG => get_host(session.req_header()).and_then(to_header_value),
177 SCHEME_TAG => Some(if ctx.conn.tls_version.is_some() {
178 SCHEME_HTTPS.clone()
179 } else {
180 SCHEME_HTTP.clone()
181 }),
182 HOST_NAME_TAG => to_header_value(get_hostname()),
183 REMOTE_ADDR_TAG => {
184 ctx.conn.remote_addr.as_deref().and_then(to_header_value)
185 },
186 REMOTE_PORT_TAG => ctx.conn.remote_port.and_then(|p| {
187 HeaderValue::from_str(itoa::Buffer::new().format(p)).ok()
190 }),
191 SERVER_ADDR_TAG => {
192 ctx.conn.server_addr.as_deref().and_then(to_header_value)
193 },
194 SERVER_PORT_TAG => ctx.conn.server_port.and_then(|p| {
195 HeaderValue::from_str(itoa::Buffer::new().format(p)).ok()
196 }),
197 UPSTREAM_ADDR_TAG => {
198 if !ctx.upstream.address.is_empty() {
199 to_header_value(&ctx.upstream.address)
200 } else {
201 None
202 }
203 },
204 PROXY_ADD_FORWARDED_TAG => {
205 ctx.conn.remote_addr.as_deref().and_then(|remote_addr| {
206 let mut value_buf = BytesMut::new();
208 if let Some(existing) =
209 session.get_header(HTTP_HEADER_X_FORWARDED_FOR)
210 {
211 value_buf.extend_from_slice(existing.as_bytes());
212 value_buf.extend_from_slice(b", ");
213 }
214 value_buf.extend_from_slice(remote_addr.as_bytes());
215 HeaderValue::from_bytes(&value_buf).ok()
216 })
217 },
218 _ => handle_special_headers(buf, session, ctx),
220 }
221}
222
223#[inline]
226fn handle_special_headers(
227 buf: &[u8],
228 session: &Session,
229 ctx: &Ctx,
230) -> Option<HeaderValue> {
231 if buf.starts_with(b"$http_") {
233 let key = std::str::from_utf8(&buf[6..]).ok()?;
235 return session.get_header(key).cloned();
237 }
238 if buf.starts_with(b"$") {
240 let var_name = std::str::from_utf8(&buf[1..]).ok()?;
241 return std::env::var(var_name)
243 .ok()
244 .and_then(|v| HeaderValue::from_str(&v).ok());
245 }
246 if buf.starts_with(b":") {
248 let key = std::str::from_utf8(&buf[1..]).ok()?;
249 let mut value = BytesMut::with_capacity(20);
251 ctx.append_log_value(&mut value, key);
252 if !value.is_empty() {
253 return HeaderValue::from_bytes(&value).ok();
255 }
256 }
257 None
259}
260
261pub fn get_remote_addr(session: &Session) -> Option<(String, u16)> {
263 session
264 .client_addr()
265 .and_then(|addr| addr.as_inet())
267 .map(|addr| (addr.ip().to_string(), addr.port()))
269}
270
271pub fn get_client_ip(session: &Session) -> String {
278 if let Some(value) = session.get_header(HTTP_HEADER_X_FORWARDED_FOR) {
280 if let Ok(s) = value.to_str() {
282 if let Some(ip) = s.split(',').next() {
283 let trimmed_ip = ip.trim();
284 if !trimmed_ip.is_empty() {
285 return trimmed_ip.to_string();
286 }
287 }
288 }
289 }
290 if let Some(value) = session.get_header(HTTP_HEADER_X_REAL_IP) {
292 return value.to_str().unwrap_or_default().to_string();
293 }
294 if let Some((addr, _)) = get_remote_addr(session) {
296 return addr;
297 }
298 "".to_string()
300}
301
302pub fn get_req_header_value<'a>(
304 req_header: &'a RequestHeader,
305 key: &str,
306) -> Option<&'a str> {
307 if let Some(value) = req_header.headers.get(key) {
309 if let Ok(value) = value.to_str() {
311 return Some(value);
312 }
313 }
314 None
315}
316
317pub fn get_cookie_value<'a>(
319 req_header: &'a RequestHeader,
320 cookie_name: &str,
321) -> Option<&'a str> {
322 get_req_header_value(req_header, "cookie")?
324 .split(';')
326 .find_map(|item| {
328 item.trim()
331 .strip_prefix(cookie_name)?
332 .strip_prefix('=')
333 .or_else(|| {
334 let (k, v) = item.split_once('=')?;
336 if k.trim() == cookie_name {
337 Some(v.trim())
338 } else {
339 None
340 }
341 })
342 })
343}
344
345pub fn get_query_value<'a>(
347 req_header: &'a RequestHeader,
348 name: &str,
349) -> Option<&'a str> {
350 req_header
352 .uri
353 .query()?
354 .split('&')
356 .find_map(|item| {
358 let (k, v) = item.split_once('=')?;
360 if k == name { Some(v) } else { None }
362 })
363}
364
365pub fn remove_query_from_header(
369 req_header: &mut RequestHeader,
370 name: &str,
371) -> Result<(), http::uri::InvalidUri> {
372 let Some(query_str) = req_header.uri.query() else {
374 return Ok(());
375 };
376
377 let mut new_query = String::with_capacity(query_str.len());
380
381 for item in query_str.split('&') {
383 let key = item.split('=').next().unwrap_or(item);
385
386 if key != name {
388 if !new_query.is_empty() {
390 new_query.push('&');
391 }
392 new_query.push_str(item);
394 }
395 }
396
397 let path = req_header.uri.path();
399 let new_uri_str = if new_query.is_empty() {
401 Cow::Borrowed(path)
403 } else {
404 let mut s = String::with_capacity(path.len() + 1 + new_query.len());
406 let _ = write!(&mut s, "{}?{}", path, &new_query);
408 Cow::Owned(s)
409 };
410
411 let new_uri = http::Uri::from_str(&new_uri_str)?;
413 req_header.set_uri(new_uri);
415
416 Ok(())
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use crate::{ConnectionInfo, UpstreamInfo};
423 use pretty_assertions::assert_eq;
424 use tokio_test::io::Builder;
425
426 #[test]
427 fn test_convert_headers() {
428 let headers = convert_headers(&[
429 "Content-Type: application/octet-stream".to_string(),
430 "X-Server: $hostname".to_string(),
431 "X-User: $USER".to_string(),
432 ])
433 .unwrap();
434 assert_eq!(3, headers.len());
435 assert_eq!("content-type", headers[0].0.to_string());
436 assert_eq!("application/octet-stream", headers[0].1.to_str().unwrap());
437 assert_eq!("x-server", headers[1].0.to_string());
438 assert_eq!(false, headers[1].1.to_str().unwrap().is_empty());
439 assert_eq!("x-user", headers[2].0.to_string());
440 assert_eq!(false, headers[2].1.to_str().unwrap().is_empty());
441 }
442
443 #[test]
444 fn test_static_value() {
445 assert_eq!(
446 "cache-control: private, no-store",
447 format!(
448 "{}: {}",
449 HTTP_HEADER_NO_STORE.0.to_string(),
450 HTTP_HEADER_NO_STORE.1.to_str().unwrap_or_default()
451 )
452 );
453
454 assert_eq!(
455 "cache-control: private, no-cache",
456 format!(
457 "{}: {}",
458 HTTP_HEADER_NO_CACHE.0.to_string(),
459 HTTP_HEADER_NO_CACHE.1.to_str().unwrap_or_default()
460 )
461 );
462
463 assert_eq!(
464 "content-type: application/json; charset=utf-8",
465 format!(
466 "{}: {}",
467 HTTP_HEADER_CONTENT_JSON.0.to_string(),
468 HTTP_HEADER_CONTENT_JSON.1.to_str().unwrap_or_default()
469 )
470 );
471
472 assert_eq!(
473 "content-type: text/html; charset=utf-8",
474 format!(
475 "{}: {}",
476 HTTP_HEADER_CONTENT_HTML.0.to_string(),
477 HTTP_HEADER_CONTENT_HTML.1.to_str().unwrap_or_default()
478 )
479 );
480
481 assert_eq!(
482 "transfer-encoding: chunked",
483 format!(
484 "{}: {}",
485 HTTP_HEADER_TRANSFER_CHUNKED.0.to_string(),
486 HTTP_HEADER_TRANSFER_CHUNKED.1.to_str().unwrap_or_default()
487 )
488 );
489
490 assert_eq!(
491 "x-request-id",
492 format!("{}", HTTP_HEADER_NAME_X_REQUEST_ID.to_string(),)
493 );
494
495 assert_eq!(
496 "content-type: text/plain; charset=utf-8",
497 format!(
498 "{}: {}",
499 HTTP_HEADER_CONTENT_TEXT.0.to_string(),
500 HTTP_HEADER_CONTENT_TEXT.1.to_str().unwrap_or_default()
501 )
502 );
503 }
504
505 #[tokio::test]
506 async fn test_convert_header_value() {
507 let headers = ["Host: pingap.io"].join("\r\n");
508 let input_header =
509 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
510 let mock_io = Builder::new().read(input_header.as_bytes()).build();
511 let mut session = Session::new_h1(Box::new(mock_io));
512 session.read_request().await.unwrap();
513 let default_state = Ctx {
514 upstream: UpstreamInfo {
515 address: "10.1.1.3:4123".to_string(),
516 ..Default::default()
517 },
518 conn: ConnectionInfo {
519 id: 102,
520 remote_addr: Some("10.1.1.1".to_string()),
521 remote_port: Some(6000),
522 server_addr: Some("10.1.1.2".to_string()),
523 server_port: Some(6001),
524 tls_version: Some("tls1.3".to_string()),
525 ..Default::default()
526 },
527 ..Default::default()
528 };
529
530 let value = convert_header_value(
531 &HeaderValue::from_str("$host").unwrap(),
532 &session,
533 &Ctx {
534 ..Default::default()
535 },
536 );
537 assert_eq!(true, value.is_some());
538 assert_eq!("pingap.io", value.unwrap().to_str().unwrap());
539
540 let value = convert_header_value(
541 &HeaderValue::from_str("$scheme").unwrap(),
542 &session,
543 &Ctx {
544 ..Default::default()
545 },
546 );
547 assert_eq!(true, value.is_some());
548 assert_eq!("http", value.unwrap().to_str().unwrap());
549 let value = convert_header_value(
550 &HeaderValue::from_str("$scheme").unwrap(),
551 &session,
552 &default_state,
553 );
554 assert_eq!(true, value.is_some());
555 assert_eq!("https", value.unwrap().to_str().unwrap());
556
557 let value = convert_header_value(
558 &HeaderValue::from_str("$remote_addr").unwrap(),
559 &session,
560 &default_state,
561 );
562 assert_eq!(true, value.is_some());
563 assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
564
565 let value = convert_header_value(
566 &HeaderValue::from_str("$remote_port").unwrap(),
567 &session,
568 &default_state,
569 );
570 assert_eq!(true, value.is_some());
571 assert_eq!("6000", value.unwrap().to_str().unwrap());
572
573 let value = convert_header_value(
574 &HeaderValue::from_str("$server_addr").unwrap(),
575 &session,
576 &default_state,
577 );
578 assert_eq!(true, value.is_some());
579 assert_eq!("10.1.1.2", value.unwrap().to_str().unwrap());
580
581 let value = convert_header_value(
582 &HeaderValue::from_str("$server_port").unwrap(),
583 &session,
584 &default_state,
585 );
586 assert_eq!(true, value.is_some());
587 assert_eq!("6001", value.unwrap().to_str().unwrap());
588
589 let value = convert_header_value(
590 &HeaderValue::from_str("$upstream_addr").unwrap(),
591 &session,
592 &default_state,
593 );
594 assert_eq!(true, value.is_some());
595 assert_eq!("10.1.1.3:4123", value.unwrap().to_str().unwrap());
596
597 let value = convert_header_value(
598 &HeaderValue::from_str(":connection_id").unwrap(),
599 &session,
600 &default_state,
601 );
602 assert_eq!(true, value.is_some());
603 assert_eq!("102", value.unwrap().to_str().unwrap());
604
605 let headers = ["X-Forwarded-For: 1.1.1.1, 2.2.2.2"].join("\r\n");
606 let input_header =
607 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
608 let mock_io = Builder::new().read(input_header.as_bytes()).build();
609 let mut session = Session::new_h1(Box::new(mock_io));
610 session.read_request().await.unwrap();
611 let value = convert_header_value(
612 &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
613 &session,
614 &Ctx {
615 conn: ConnectionInfo {
616 remote_addr: Some("10.1.1.1".to_string()),
617 ..Default::default()
618 },
619 ..Default::default()
620 },
621 );
622 assert_eq!(true, value.is_some());
623 assert_eq!(
624 "1.1.1.1, 2.2.2.2, 10.1.1.1",
625 value.unwrap().to_str().unwrap()
626 );
627
628 let headers = [""].join("\r\n");
629 let input_header =
630 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
631 let mock_io = Builder::new().read(input_header.as_bytes()).build();
632 let mut session = Session::new_h1(Box::new(mock_io));
633 session.read_request().await.unwrap();
634 let value = convert_header_value(
635 &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
636 &session,
637 &Ctx {
638 conn: ConnectionInfo {
639 remote_addr: Some("10.1.1.1".to_string()),
640 ..Default::default()
641 },
642 ..Default::default()
643 },
644 );
645 assert_eq!(true, value.is_some());
646 assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
647
648 let headers = [""].join("\r\n");
649 let input_header =
650 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
651 let mock_io = Builder::new().read(input_header.as_bytes()).build();
652 let mut session = Session::new_h1(Box::new(mock_io));
653 session.read_request().await.unwrap();
654 let value = convert_header_value(
655 &HeaderValue::from_str("$upstream_addr").unwrap(),
656 &session,
657 &Ctx {
658 upstream: UpstreamInfo {
659 address: "10.1.1.1:8001".to_string(),
660 ..Default::default()
661 },
662 ..Default::default()
663 },
664 );
665 assert_eq!(true, value.is_some());
666 assert_eq!("10.1.1.1:8001", value.unwrap().to_str().unwrap());
667
668 let headers = ["Origin: https://github.com"].join("\r\n");
669 let input_header =
670 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
671 let mock_io = Builder::new().read(input_header.as_bytes()).build();
672 let mut session = Session::new_h1(Box::new(mock_io));
673 session.read_request().await.unwrap();
674 let value = convert_header_value(
675 &HeaderValue::from_str("$http_origin").unwrap(),
676 &session,
677 &Ctx::default(),
678 );
679 assert_eq!(true, value.is_some());
680 assert_eq!("https://github.com", value.unwrap().to_str().unwrap());
681
682 let headers = ["Origin: https://github.com"].join("\r\n");
683 let input_header =
684 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
685 let mock_io = Builder::new().read(input_header.as_bytes()).build();
686 let mut session = Session::new_h1(Box::new(mock_io));
687 session.read_request().await.unwrap();
688 let value = convert_header_value(
689 &HeaderValue::from_str("$hostname").unwrap(),
690 &session,
691 &Ctx::default(),
692 );
693 assert_eq!(true, value.is_some());
694
695 let headers = ["Origin: https://github.com"].join("\r\n");
696 let input_header =
697 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
698 let mock_io = Builder::new().read(input_header.as_bytes()).build();
699 let mut session = Session::new_h1(Box::new(mock_io));
700 session.read_request().await.unwrap();
701 let value = convert_header_value(
702 &HeaderValue::from_str("$HOME").unwrap(),
703 &session,
704 &Ctx::default(),
705 );
706 assert_eq!(true, value.is_some());
707
708 let headers = ["Origin: https://github.com"].join("\r\n");
709 let input_header =
710 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
711 let mock_io = Builder::new().read(input_header.as_bytes()).build();
712 let mut session = Session::new_h1(Box::new(mock_io));
713 session.read_request().await.unwrap();
714 let value = convert_header_value(
715 &HeaderValue::from_str("UUID").unwrap(),
716 &session,
717 &Ctx::default(),
718 );
719 assert_eq!(false, value.is_some());
720 }
721
722 #[tokio::test]
723 async fn test_get_host() {
724 let headers = ["Host: pingap.io"].join("\r\n");
725 let input_header =
726 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
727 let mock_io = Builder::new().read(input_header.as_bytes()).build();
728 let mut session = Session::new_h1(Box::new(mock_io));
729 session.read_request().await.unwrap();
730 assert_eq!(get_host(session.req_header()), Some("pingap.io"));
731 }
732
733 #[test]
734 fn test_remove_query_from_header() {
735 let mut req =
736 RequestHeader::build("GET", b"/?apikey=123", None).unwrap();
737 remove_query_from_header(&mut req, "apikey").unwrap();
738 assert_eq!("/", req.uri.to_string());
739
740 let mut req =
741 RequestHeader::build("GET", b"/?apikey=123&name=pingap", None)
742 .unwrap();
743 remove_query_from_header(&mut req, "apikey").unwrap();
744 assert_eq!("/?name=pingap", req.uri.to_string());
745 }
746
747 #[tokio::test]
748 async fn test_get_client_ip() {
749 let headers = ["X-Forwarded-For:192.168.1.1"].join("\r\n");
750 let input_header =
751 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
752 let mock_io = Builder::new().read(input_header.as_bytes()).build();
753 let mut session = Session::new_h1(Box::new(mock_io));
754 session.read_request().await.unwrap();
755 assert_eq!(get_client_ip(&session), "192.168.1.1");
756
757 let headers = ["X-Real-Ip:192.168.1.2"].join("\r\n");
758 let input_header =
759 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
760 let mock_io = Builder::new().read(input_header.as_bytes()).build();
761 let mut session = Session::new_h1(Box::new(mock_io));
762 session.read_request().await.unwrap();
763 assert_eq!(get_client_ip(&session), "192.168.1.2");
764 }
765
766 #[tokio::test]
767 async fn test_get_header_value() {
768 let headers = ["Host: pingap.io"].join("\r\n");
769 let input_header =
770 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
771 let mock_io = Builder::new().read(input_header.as_bytes()).build();
772 let mut session = Session::new_h1(Box::new(mock_io));
773 session.read_request().await.unwrap();
774 assert_eq!(
775 get_req_header_value(session.req_header(), "Host"),
776 Some("pingap.io")
777 );
778 }
779
780 #[tokio::test]
781 async fn test_get_cookie_value() {
782 let headers = ["Cookie: name=pingap"].join("\r\n");
783 let input_header =
784 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
785 let mock_io = Builder::new().read(input_header.as_bytes()).build();
786 let mut session = Session::new_h1(Box::new(mock_io));
787 session.read_request().await.unwrap();
788 assert_eq!(
789 get_cookie_value(session.req_header(), "name"),
790 Some("pingap")
791 );
792 }
793
794 #[tokio::test]
795 async fn test_get_query_value() {
796 let headers = ["X-Forwarded-For:192.168.1.1"].join("\r\n");
797 let input_header =
798 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
799 let mock_io = Builder::new().read(input_header.as_bytes()).build();
800 let mut session = Session::new_h1(Box::new(mock_io));
801 session.read_request().await.unwrap();
802 assert_eq!(get_query_value(session.req_header(), "size"), Some("1"));
803 }
804
805 #[test]
808 fn test_convert_header_edge_cases() {
809 assert!(convert_header("").unwrap().is_none());
811 assert!(convert_header("no-colon").unwrap().is_none());
813 assert!(convert_header("Invalid Name: value").is_err());
815 assert!(convert_header("Valid-Name: invalid\r\nvalue").is_err());
817 }
818
819 #[test]
821 fn test_get_host_variants() {
822 let uri_string = "http://user:pass@authority.com/path";
824
825 let uri = http::Uri::from_str(uri_string).unwrap();
827 let mut req_with_authority =
828 RequestHeader::build("GET", b"/path", None).unwrap();
829 req_with_authority.set_uri(uri);
830 assert_eq!(get_host(&req_with_authority), Some("authority.com"));
831
832 let mut req_with_host_header =
834 RequestHeader::build("GET", b"/path", None).unwrap();
835 req_with_host_header
836 .insert_header("Host", "header-host.com:8080")
837 .unwrap();
838 assert_eq!(get_host(&req_with_host_header), Some("header-host.com"));
839
840 let req_no_host = RequestHeader::build("GET", b"/path", None).unwrap();
842 assert_eq!(get_host(&req_no_host), None);
843 }
844
845 #[test]
847 fn test_get_cookie_value_advanced() {
848 let mut req = RequestHeader::build("GET", b"/", None).unwrap();
849 req.insert_header("Cookie", "id=123; session=abc; theme=dark")
850 .unwrap();
851
852 assert_eq!(get_cookie_value(&req, "session"), Some("abc"));
853 assert_eq!(get_cookie_value(&req, "id"), Some("123"));
854 assert_eq!(get_cookie_value(&req, "theme"), Some("dark"));
855 assert_eq!(get_cookie_value(&req, "lang"), None);
857 assert_eq!(get_cookie_value(&req, "the"), None);
859 }
860
861 #[test]
862 fn test_remove_query_from_header_variants() {
863 let mut req =
865 RequestHeader::build("GET", b"/path?key=val", None).unwrap();
866 remove_query_from_header(&mut req, "key").unwrap();
867 assert_eq!(req.uri.to_string(), "/path");
868
869 let mut req =
871 RequestHeader::build("GET", b"/path?key1=val1&key2=val2", None)
872 .unwrap();
873 remove_query_from_header(&mut req, "key1").unwrap();
874 assert_eq!(req.uri.to_string(), "/path?key2=val2");
875
876 let mut req =
878 RequestHeader::build("GET", b"/path?key1=val1&key2=val2", None)
879 .unwrap();
880 remove_query_from_header(&mut req, "key2").unwrap();
881 assert_eq!(req.uri.to_string(), "/path?key1=val1");
882
883 let mut req =
885 RequestHeader::build("GET", b"/path?key1=v1&key2=v2&key3=v3", None)
886 .unwrap();
887 remove_query_from_header(&mut req, "key2").unwrap();
888 assert_eq!(req.uri.to_string(), "/path?key1=v1&key3=v3");
889
890 let mut req =
892 RequestHeader::build("GET", b"/path?key=val", None).unwrap();
893 remove_query_from_header(&mut req, "nonexistent").unwrap();
894 assert_eq!(req.uri.to_string(), "/path?key=val");
895
896 let mut req = RequestHeader::build("GET", b"/path", None).unwrap();
898 remove_query_from_header(&mut req, "key").unwrap();
899 assert_eq!(req.uri.to_string(), "/path");
900 }
901}