1use super::{get_hostname, Ctx};
16use bytes::BytesMut;
17use http::header;
18use http::{HeaderName, HeaderValue};
19use once_cell::sync::Lazy;
20use pingora::http::RequestHeader;
21use pingora::proxy::Session;
22use snafu::{ResultExt, Snafu};
23use std::collections::HashMap;
24use std::str::FromStr;
25use url::Url;
26use urlencoding::encode;
27
28pub static HTTP_HEADER_X_FORWARDED_FOR: Lazy<http::HeaderName> =
29 Lazy::new(|| HeaderName::from_str("X-Forwarded-For").unwrap());
30
31pub static HTTP_HEADER_X_REAL_IP: Lazy<http::HeaderName> =
32 Lazy::new(|| HeaderName::from_str("X-Real-Ip").unwrap());
33
34pub const HOST_NAME_TAG: &[u8] = b"$hostname";
35const HOST_TAG: &[u8] = b"$host";
36const SCHEME_TAG: &[u8] = b"$scheme";
37const REMOTE_ADDR_TAG: &[u8] = b"$remote_addr";
38const REMOTE_PORT_TAG: &[u8] = b"$remote_port";
39const SERVER_ADDR_TAG: &[u8] = b"$server_addr";
40const SERVER_PORT_TAG: &[u8] = b"$server_port";
41const PROXY_ADD_FORWARDED_TAG: &[u8] = b"$proxy_add_x_forwarded_for";
42const UPSTREAM_ADDR_TAG: &[u8] = b"$upstream_addr";
43
44static SCHEME_HTTPS: HeaderValue = HeaderValue::from_static("https");
45static SCHEME_HTTP: HeaderValue = HeaderValue::from_static("http");
46
47#[derive(Debug, Snafu)]
48pub enum Error {
49 #[snafu(display("Invalid header value: {value} - {source}"))]
50 InvalidHeaderValue {
51 value: String,
52 source: header::InvalidHeaderValue,
53 },
54 #[snafu(display("Invalid header name: {value} - {source}"))]
55 InvalidHeaderName {
56 value: String,
57 source: header::InvalidHeaderName,
58 },
59}
60type Result<T, E = Error> = std::result::Result<T, E>;
61
62pub type HttpHeader = (HeaderName, HeaderValue);
63
64pub fn get_host(header: &RequestHeader) -> Option<&str> {
68 if let Some(host) = header.uri.host() {
69 return Some(host);
70 }
71 if let Some(host) = header.headers.get(http::header::HOST) {
72 if let Ok(value) = host.to_str().map(|host| host.split(':').next()) {
73 return value;
74 }
75 }
76 None
77}
78
79pub fn convert_header(value: &str) -> Result<Option<HttpHeader>> {
88 value
89 .split_once(':')
90 .map(|(k, v)| {
91 let name = HeaderName::from_str(k.trim())
92 .context(InvalidHeaderNameSnafu { value: k })?;
93 let value = HeaderValue::from_str(v.trim())
94 .context(InvalidHeaderValueSnafu { value: v })?;
95 Ok(Some((name, value)))
96 })
97 .unwrap_or(Ok(None))
98}
99
100pub fn convert_headers(header_values: &[String]) -> Result<Vec<HttpHeader>> {
109 let mut arr = vec![];
110 for item in header_values {
111 if let Some(item) = convert_header(item)? {
112 arr.push(item);
113 }
114 }
115 Ok(arr)
116}
117
118pub static HTTP_HEADER_NO_STORE: Lazy<HttpHeader> = Lazy::new(|| {
119 (
120 header::CACHE_CONTROL,
121 HeaderValue::from_str("private, no-store").unwrap(),
122 )
123});
124
125pub static HTTP_HEADER_NO_CACHE: Lazy<HttpHeader> = Lazy::new(|| {
126 (
127 header::CACHE_CONTROL,
128 HeaderValue::from_str("private, no-cache").unwrap(),
129 )
130});
131
132pub static HTTP_HEADER_CONTENT_JSON: Lazy<HttpHeader> = Lazy::new(|| {
133 (
134 header::CONTENT_TYPE,
135 HeaderValue::from_str("application/json; charset=utf-8").unwrap(),
136 )
137});
138
139pub static HTTP_HEADER_CONTENT_HTML: Lazy<HttpHeader> = Lazy::new(|| {
140 (
141 header::CONTENT_TYPE,
142 HeaderValue::from_str("text/html; charset=utf-8").unwrap(),
143 )
144});
145
146pub static HTTP_HEADER_CONTENT_TEXT: Lazy<HttpHeader> = Lazy::new(|| {
147 (
148 header::CONTENT_TYPE,
149 HeaderValue::from_str("text/plain; charset=utf-8").unwrap(),
150 )
151});
152
153pub static HTTP_HEADER_TRANSFER_CHUNKED: Lazy<HttpHeader> = Lazy::new(|| {
154 (
155 header::TRANSFER_ENCODING,
156 HeaderValue::from_str("chunked").unwrap(),
157 )
158});
159
160pub static HTTP_HEADER_NAME_X_REQUEST_ID: Lazy<HeaderName> =
161 Lazy::new(|| HeaderName::from_str("X-Request-Id").unwrap());
162
163#[inline]
174pub fn convert_header_value(
175 value: &HeaderValue,
176 session: &Session,
177 ctx: &Ctx,
178) -> Option<HeaderValue> {
179 let buf = value.as_bytes();
180
181 if buf.is_empty() || !(buf[0] == b'$' || buf[0] == b':') {
183 return None;
184 }
185
186 let to_header_value = |s: &str| HeaderValue::from_str(s).ok();
188
189 match buf {
190 HOST_TAG => get_host(session.req_header()).and_then(to_header_value),
191 SCHEME_TAG => Some(if ctx.tls_version.is_some() {
192 SCHEME_HTTPS.clone()
193 } else {
194 SCHEME_HTTP.clone()
195 }),
196 HOST_NAME_TAG => to_header_value(get_hostname()),
197 REMOTE_ADDR_TAG => ctx.remote_addr.as_deref().and_then(to_header_value),
198 REMOTE_PORT_TAG => ctx
199 .remote_port
200 .map(|p| p.to_string())
201 .and_then(|s| to_header_value(&s)),
202 SERVER_ADDR_TAG => ctx.server_addr.as_deref().and_then(to_header_value),
203 SERVER_PORT_TAG => ctx
204 .server_port
205 .map(|p| p.to_string())
206 .and_then(|s| to_header_value(&s)),
207 UPSTREAM_ADDR_TAG => {
208 if !ctx.upstream_address.is_empty() {
209 to_header_value(&ctx.upstream_address)
210 } else {
211 None
212 }
213 },
214 PROXY_ADD_FORWARDED_TAG => {
215 ctx.remote_addr.as_deref().and_then(|remote_addr| {
216 let value = match session
217 .get_header(HTTP_HEADER_X_FORWARDED_FOR.clone())
218 {
219 Some(existing) => format!(
220 "{}, {}",
221 existing.to_str().unwrap_or_default(),
222 remote_addr
223 ),
224 None => remote_addr.to_string(),
225 };
226 to_header_value(&value)
227 })
228 },
229 _ => handle_special_headers(buf, session, ctx),
230 }
231}
232
233const HTTP_HEADER_PREFIX: &[u8] = b"$http_";
234const HTTP_HEADER_PREFIX_LEN: usize = HTTP_HEADER_PREFIX.len();
235
236#[inline]
237fn handle_special_headers(
238 buf: &[u8],
239 session: &Session,
240 ctx: &Ctx,
241) -> Option<HeaderValue> {
242 if buf.starts_with(HTTP_HEADER_PREFIX) {
244 return handle_http_header(buf, session);
245 }
246 if buf.starts_with(b"$") {
248 return handle_env_var(buf);
249 }
250 if buf.starts_with(b":") {
252 return handle_context_value(buf, ctx);
253 }
254 None
255}
256
257#[inline]
258fn handle_http_header(buf: &[u8], session: &Session) -> Option<HeaderValue> {
259 let key = std::str::from_utf8(&buf[HTTP_HEADER_PREFIX_LEN..]).ok()?;
261 session.get_header(key).cloned()
263}
264
265#[inline]
266fn handle_env_var(buf: &[u8]) -> Option<HeaderValue> {
267 let var_name = std::str::from_utf8(&buf[1..]).ok()?;
269 std::env::var(var_name)
271 .ok()
272 .and_then(|v| HeaderValue::from_str(&v).ok())
273}
274
275#[inline]
276fn handle_context_value(buf: &[u8], ctx: &Ctx) -> Option<HeaderValue> {
277 let key = std::str::from_utf8(&buf[1..]).ok()?;
279 let mut value = BytesMut::with_capacity(20);
281 value = ctx.append_value(value, key);
283 if !value.is_empty() {
285 HeaderValue::from_bytes(&value).ok()
286 } else {
287 None
288 }
289}
290
291pub fn get_remote_addr(session: &Session) -> Option<(String, u16)> {
293 session
294 .client_addr()
295 .and_then(|addr| addr.as_inet())
296 .map(|addr| (addr.ip().to_string(), addr.port()))
297}
298
299pub fn get_client_ip(session: &Session) -> String {
303 if let Some(value) = session.get_header(HTTP_HEADER_X_FORWARDED_FOR.clone())
304 {
305 let arr: Vec<&str> =
306 value.to_str().unwrap_or_default().split(',').collect();
307 if !arr.is_empty() {
308 return arr[0].trim().to_string();
309 }
310 }
311 if let Some(value) = session.get_header(HTTP_HEADER_X_REAL_IP.clone()) {
312 return value.to_str().unwrap_or_default().to_string();
313 }
314 if let Some((addr, _)) = get_remote_addr(session) {
315 return addr;
316 }
317 "".to_string()
318}
319
320pub fn get_req_header_value<'a>(
329 req_header: &'a RequestHeader,
330 key: &str,
331) -> Option<&'a str> {
332 if let Some(value) = req_header.headers.get(key) {
333 if let Ok(value) = value.to_str() {
334 return Some(value);
335 }
336 }
337 None
338}
339
340pub fn get_cookie_value<'a>(
349 req_header: &'a RequestHeader,
350 cookie_name: &str,
351) -> Option<&'a str> {
352 if let Some(cookie_value) = get_req_header_value(req_header, "Cookie") {
353 for item in cookie_value.split(';') {
354 if let Some((k, v)) = item.split_once('=') {
355 if k == cookie_name {
356 return Some(v.trim());
357 }
358 }
359 }
360 }
361 None
362}
363
364pub fn convert_query_map(value: &str) -> HashMap<String, String> {
372 let mut m = HashMap::new();
373 let value = if !value.contains('?') {
374 format!("http://host?{value}")
375 } else {
376 value.to_string()
377 };
378 let Ok(value) = Url::parse(&value) else {
379 return m;
380 };
381 for item in value.query().unwrap_or_default().split('&') {
382 if let Some((key, value)) = item.split_once('=') {
383 m.insert(key.to_string(), encode(value).to_string());
384 } else {
385 m.insert(item.to_string(), "".to_string());
386 }
387 }
388 m
389}
390
391pub fn get_query_value<'a>(
400 req_header: &'a RequestHeader,
401 name: &str,
402) -> Option<&'a str> {
403 if let Some(query) = req_header.uri.query() {
404 for item in query.split('&') {
405 if let Some((k, v)) = item.split_once('=') {
406 if k == name {
407 return Some(v.trim());
408 }
409 }
410 }
411 }
412 None
413}
414
415pub fn remove_query_from_header(
424 req_header: &mut RequestHeader,
425 name: &str,
426) -> Result<(), http::uri::InvalidUri> {
427 if let Some(query) = req_header.uri.query() {
428 let mut query_list = vec![];
429 for item in query.split('&') {
430 if let Some((k, v)) = item.split_once('=') {
431 if k != name {
432 query_list.push(format!("{k}={v}"));
433 }
434 } else if item != name {
435 query_list.push(item.to_string());
436 }
437 }
438 let query = query_list.join("&");
439 let mut new_path = req_header.uri.path().to_string();
440 if !query.is_empty() {
441 new_path = format!("{new_path}?{query}");
442 }
443 return new_path
444 .parse::<http::Uri>()
445 .map(|uri| req_header.set_uri(uri));
446 }
447
448 Ok(())
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use pretty_assertions::assert_eq;
455 use tokio_test::io::Builder;
456
457 #[test]
458 fn test_convert_headers() {
459 let headers = convert_headers(&[
460 "Content-Type: application/octet-stream".to_string(),
461 "X-Server: $hostname".to_string(),
462 "X-User: $USER".to_string(),
463 ])
464 .unwrap();
465 assert_eq!(3, headers.len());
466 assert_eq!("content-type", headers[0].0.to_string());
467 assert_eq!("application/octet-stream", headers[0].1.to_str().unwrap());
468 assert_eq!("x-server", headers[1].0.to_string());
469 assert_eq!(false, headers[1].1.to_str().unwrap().is_empty());
470 assert_eq!("x-user", headers[2].0.to_string());
471 assert_eq!(false, headers[2].1.to_str().unwrap().is_empty());
472 }
473
474 #[test]
475 fn test_static_value() {
476 assert_eq!(
477 "cache-control: private, no-store",
478 format!(
479 "{}: {}",
480 HTTP_HEADER_NO_STORE.0.to_string(),
481 HTTP_HEADER_NO_STORE.1.to_str().unwrap_or_default()
482 )
483 );
484
485 assert_eq!(
486 "cache-control: private, no-cache",
487 format!(
488 "{}: {}",
489 HTTP_HEADER_NO_CACHE.0.to_string(),
490 HTTP_HEADER_NO_CACHE.1.to_str().unwrap_or_default()
491 )
492 );
493
494 assert_eq!(
495 "content-type: application/json; charset=utf-8",
496 format!(
497 "{}: {}",
498 HTTP_HEADER_CONTENT_JSON.0.to_string(),
499 HTTP_HEADER_CONTENT_JSON.1.to_str().unwrap_or_default()
500 )
501 );
502
503 assert_eq!(
504 "content-type: text/html; charset=utf-8",
505 format!(
506 "{}: {}",
507 HTTP_HEADER_CONTENT_HTML.0.to_string(),
508 HTTP_HEADER_CONTENT_HTML.1.to_str().unwrap_or_default()
509 )
510 );
511
512 assert_eq!(
513 "transfer-encoding: chunked",
514 format!(
515 "{}: {}",
516 HTTP_HEADER_TRANSFER_CHUNKED.0.to_string(),
517 HTTP_HEADER_TRANSFER_CHUNKED.1.to_str().unwrap_or_default()
518 )
519 );
520
521 assert_eq!(
522 "x-request-id",
523 format!("{}", HTTP_HEADER_NAME_X_REQUEST_ID.to_string(),)
524 );
525
526 assert_eq!(
527 "content-type: text/plain; charset=utf-8",
528 format!(
529 "{}: {}",
530 HTTP_HEADER_CONTENT_TEXT.0.to_string(),
531 HTTP_HEADER_CONTENT_TEXT.1.to_str().unwrap_or_default()
532 )
533 );
534 }
535
536 #[tokio::test]
537 async fn test_convert_header_value() {
538 let headers = ["Host: pingap.io"].join("\r\n");
539 let input_header =
540 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
541 let mock_io = Builder::new().read(input_header.as_bytes()).build();
542 let mut session = Session::new_h1(Box::new(mock_io));
543 session.read_request().await.unwrap();
544 let default_state = Ctx {
545 tls_version: Some("tls1.3".to_string()),
546 remote_addr: Some("10.1.1.1".to_string()),
547 remote_port: Some(6000),
548 server_addr: Some("10.1.1.2".to_string()),
549 server_port: Some(6001),
550 upstream_address: "10.1.1.3:4123".to_string(),
551 connection_id: 102,
552 ..Default::default()
553 };
554
555 let value = convert_header_value(
556 &HeaderValue::from_str("$host").unwrap(),
557 &session,
558 &Ctx {
559 ..Default::default()
560 },
561 );
562 assert_eq!(true, value.is_some());
563 assert_eq!("pingap.io", value.unwrap().to_str().unwrap());
564
565 let value = convert_header_value(
566 &HeaderValue::from_str("$scheme").unwrap(),
567 &session,
568 &Ctx {
569 ..Default::default()
570 },
571 );
572 assert_eq!(true, value.is_some());
573 assert_eq!("http", value.unwrap().to_str().unwrap());
574 let value = convert_header_value(
575 &HeaderValue::from_str("$scheme").unwrap(),
576 &session,
577 &default_state,
578 );
579 assert_eq!(true, value.is_some());
580 assert_eq!("https", value.unwrap().to_str().unwrap());
581
582 let value = convert_header_value(
583 &HeaderValue::from_str("$remote_addr").unwrap(),
584 &session,
585 &default_state,
586 );
587 assert_eq!(true, value.is_some());
588 assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
589
590 let value = convert_header_value(
591 &HeaderValue::from_str("$remote_port").unwrap(),
592 &session,
593 &default_state,
594 );
595 assert_eq!(true, value.is_some());
596 assert_eq!("6000", value.unwrap().to_str().unwrap());
597
598 let value = convert_header_value(
599 &HeaderValue::from_str("$server_addr").unwrap(),
600 &session,
601 &default_state,
602 );
603 assert_eq!(true, value.is_some());
604 assert_eq!("10.1.1.2", value.unwrap().to_str().unwrap());
605
606 let value = convert_header_value(
607 &HeaderValue::from_str("$server_port").unwrap(),
608 &session,
609 &default_state,
610 );
611 assert_eq!(true, value.is_some());
612 assert_eq!("6001", value.unwrap().to_str().unwrap());
613
614 let value = convert_header_value(
615 &HeaderValue::from_str("$upstream_addr").unwrap(),
616 &session,
617 &default_state,
618 );
619 assert_eq!(true, value.is_some());
620 assert_eq!("10.1.1.3:4123", value.unwrap().to_str().unwrap());
621
622 let value = convert_header_value(
623 &HeaderValue::from_str(":connection_id").unwrap(),
624 &session,
625 &default_state,
626 );
627 assert_eq!(true, value.is_some());
628 assert_eq!("102", value.unwrap().to_str().unwrap());
629
630 let headers = ["X-Forwarded-For: 1.1.1.1, 2.2.2.2"].join("\r\n");
631 let input_header =
632 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
633 let mock_io = Builder::new().read(input_header.as_bytes()).build();
634 let mut session = Session::new_h1(Box::new(mock_io));
635 session.read_request().await.unwrap();
636 let value = convert_header_value(
637 &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
638 &session,
639 &Ctx {
640 remote_addr: Some("10.1.1.1".to_string()),
641 ..Default::default()
642 },
643 );
644 assert_eq!(true, value.is_some());
645 assert_eq!(
646 "1.1.1.1, 2.2.2.2, 10.1.1.1",
647 value.unwrap().to_str().unwrap()
648 );
649
650 let headers = [""].join("\r\n");
651 let input_header =
652 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
653 let mock_io = Builder::new().read(input_header.as_bytes()).build();
654 let mut session = Session::new_h1(Box::new(mock_io));
655 session.read_request().await.unwrap();
656 let value = convert_header_value(
657 &HeaderValue::from_str("$proxy_add_x_forwarded_for").unwrap(),
658 &session,
659 &Ctx {
660 remote_addr: Some("10.1.1.1".to_string()),
661 ..Default::default()
662 },
663 );
664 assert_eq!(true, value.is_some());
665 assert_eq!("10.1.1.1", value.unwrap().to_str().unwrap());
666
667 let headers = [""].join("\r\n");
668 let input_header =
669 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
670 let mock_io = Builder::new().read(input_header.as_bytes()).build();
671 let mut session = Session::new_h1(Box::new(mock_io));
672 session.read_request().await.unwrap();
673 let value = convert_header_value(
674 &HeaderValue::from_str("$upstream_addr").unwrap(),
675 &session,
676 &Ctx {
677 upstream_address: "10.1.1.1:8001".to_string(),
678 ..Default::default()
679 },
680 );
681 assert_eq!(true, value.is_some());
682 assert_eq!("10.1.1.1:8001", value.unwrap().to_str().unwrap());
683
684 let headers = ["Origin: https://github.com"].join("\r\n");
685 let input_header =
686 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
687 let mock_io = Builder::new().read(input_header.as_bytes()).build();
688 let mut session = Session::new_h1(Box::new(mock_io));
689 session.read_request().await.unwrap();
690 let value = convert_header_value(
691 &HeaderValue::from_str("$http_origin").unwrap(),
692 &session,
693 &Ctx::default(),
694 );
695 assert_eq!(true, value.is_some());
696 assert_eq!("https://github.com", value.unwrap().to_str().unwrap());
697
698 let headers = ["Origin: https://github.com"].join("\r\n");
699 let input_header =
700 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
701 let mock_io = Builder::new().read(input_header.as_bytes()).build();
702 let mut session = Session::new_h1(Box::new(mock_io));
703 session.read_request().await.unwrap();
704 let value = convert_header_value(
705 &HeaderValue::from_str("$hostname").unwrap(),
706 &session,
707 &Ctx::default(),
708 );
709 assert_eq!(true, value.is_some());
710
711 let headers = ["Origin: https://github.com"].join("\r\n");
712 let input_header =
713 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
714 let mock_io = Builder::new().read(input_header.as_bytes()).build();
715 let mut session = Session::new_h1(Box::new(mock_io));
716 session.read_request().await.unwrap();
717 let value = convert_header_value(
718 &HeaderValue::from_str("$HOME").unwrap(),
719 &session,
720 &Ctx::default(),
721 );
722 assert_eq!(true, value.is_some());
723
724 let headers = ["Origin: https://github.com"].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 let value = convert_header_value(
731 &HeaderValue::from_str("UUID").unwrap(),
732 &session,
733 &Ctx::default(),
734 );
735 assert_eq!(false, value.is_some());
736 }
737
738 #[tokio::test]
739 async fn test_get_host() {
740 let headers = ["Host: pingap.io"].join("\r\n");
741 let input_header =
742 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
743 let mock_io = Builder::new().read(input_header.as_bytes()).build();
744 let mut session = Session::new_h1(Box::new(mock_io));
745 session.read_request().await.unwrap();
746 assert_eq!(get_host(session.req_header()), Some("pingap.io"));
747 }
748
749 #[test]
750 fn test_remove_query_from_header() {
751 let mut req =
752 RequestHeader::build("GET", b"/?apikey=123", None).unwrap();
753 remove_query_from_header(&mut req, "apikey").unwrap();
754 assert_eq!("/", req.uri.to_string());
755
756 let mut req =
757 RequestHeader::build("GET", b"/?apikey=123&name=pingap", None)
758 .unwrap();
759 remove_query_from_header(&mut req, "apikey").unwrap();
760 assert_eq!("/?name=pingap", req.uri.to_string());
761 }
762
763 #[tokio::test]
764 async fn test_get_client_ip() {
765 let headers = ["X-Forwarded-For:192.168.1.1"].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 assert_eq!(get_client_ip(&session), "192.168.1.1");
772
773 let headers = ["X-Real-Ip:192.168.1.2"].join("\r\n");
774 let input_header =
775 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
776 let mock_io = Builder::new().read(input_header.as_bytes()).build();
777 let mut session = Session::new_h1(Box::new(mock_io));
778 session.read_request().await.unwrap();
779 assert_eq!(get_client_ip(&session), "192.168.1.2");
780 }
781
782 #[tokio::test]
783 async fn test_get_header_value() {
784 let headers = ["Host: pingap.io"].join("\r\n");
785 let input_header =
786 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
787 let mock_io = Builder::new().read(input_header.as_bytes()).build();
788 let mut session = Session::new_h1(Box::new(mock_io));
789 session.read_request().await.unwrap();
790 assert_eq!(
791 get_req_header_value(session.req_header(), "Host"),
792 Some("pingap.io")
793 );
794 }
795
796 #[tokio::test]
797 async fn test_get_cookie_value() {
798 let headers = ["Cookie: name=pingap"].join("\r\n");
799 let input_header =
800 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
801 let mock_io = Builder::new().read(input_header.as_bytes()).build();
802 let mut session = Session::new_h1(Box::new(mock_io));
803 session.read_request().await.unwrap();
804 assert_eq!(
805 get_cookie_value(session.req_header(), "name"),
806 Some("pingap")
807 );
808 }
809
810 #[test]
811 fn test_convert_query_map() {
812 let query = "apikey=123&name=pingap";
813 let map = convert_query_map(query);
814 assert_eq!(map.len(), 2);
815 assert_eq!(map["apikey"], "123");
816 assert_eq!(map["name"], "pingap");
817
818 let query = "https://pingap.io/vicanso/pingap?apikey=123&name=pingap";
819 let map = convert_query_map(query);
820 assert_eq!(map.len(), 2);
821 assert_eq!(map["apikey"], "123");
822 assert_eq!(map["name"], "pingap");
823 }
824
825 #[tokio::test]
826 async fn test_get_query_value() {
827 let headers = ["X-Forwarded-For:192.168.1.1"].join("\r\n");
828 let input_header =
829 format!("GET /vicanso/pingap?size=1 HTTP/1.1\r\n{headers}\r\n\r\n");
830 let mock_io = Builder::new().read(input_header.as_bytes()).build();
831 let mut session = Session::new_h1(Box::new(mock_io));
832 session.read_request().await.unwrap();
833 assert_eq!(get_query_value(session.req_header(), "size"), Some("1"));
834 }
835}