1use crate::config::Route;
9use crate::vendored::hyper_ext::{empty_body, string_body, ProxyBody};
10use hyper::header::{HeaderName, HeaderValue, CONNECTION, FORWARDED, HOST, UPGRADE};
11use hyper::{Method, Version};
12use hyper::{Request, Response, StatusCode};
13use std::net::{IpAddr, SocketAddr};
14
15pub mod headers {
17 use hyper::header::HeaderName;
18
19 pub static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
20 pub static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
21 pub static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
22 pub static X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip");
23}
24
25const HOP_BY_HOP_HEADERS: &[&str] = &[
33 "connection",
34 "keep-alive",
35 "proxy-authenticate",
36 "proxy-authorization",
37 "te",
38 "trailer",
39 "transfer-encoding",
40 "upgrade",
41 "http2-settings",
47 "proxy-connection",
49];
50
51const CONNECTION_TOKEN_EXEMPT: &[&str] = &["host", "content-length"];
57
58pub struct MessageHandler;
60
61fn strip_port(authority: &str) -> &str {
66 if authority.starts_with('[') {
67 return match authority.find(']') {
68 Some(end) => &authority[..end + 1],
69 None => authority,
70 };
71 }
72 match authority.split_once(':') {
73 Some((host, _)) => host,
74 None => authority,
75 }
76}
77
78impl MessageHandler {
79 pub fn upgrade_protocol(request: &Request<ProxyBody>) -> Option<String> {
87 if request.version() != Version::HTTP_11 {
88 return None;
89 }
90
91 let headers = request.headers();
92 let asks_for_upgrade = headers.get_all(CONNECTION).iter().any(|value| {
93 value
94 .to_str()
95 .map(|v| {
96 v.split(',')
97 .any(|t| t.trim().eq_ignore_ascii_case("upgrade"))
98 })
99 .unwrap_or(false)
100 });
101 if !asks_for_upgrade {
102 return None;
103 }
104
105 headers
106 .get(UPGRADE)
107 .and_then(|v| v.to_str().ok())
108 .map(|v| v.trim().to_ascii_lowercase())
109 .filter(|v| !v.is_empty())
110 }
111
112 pub fn h2_connect_protocol(request: &Request<ProxyBody>) -> Option<String> {
119 if request.version() != Version::HTTP_2 || request.method() != Method::CONNECT {
120 return None;
121 }
122 request
123 .extensions()
124 .get::<hyper::ext::Protocol>()
125 .map(|protocol| protocol.as_str().to_ascii_lowercase())
126 }
127
128 pub fn restore_upgrade_headers(request: &mut Request<ProxyBody>, protocol: &str) {
135 if let Ok(value) = HeaderValue::from_str(protocol) {
136 let headers = request.headers_mut();
137 headers.insert(UPGRADE, value);
138 headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
139 }
140 }
141
142 pub fn upgrade_allowed(protocol: &str, allowed: &[String]) -> bool {
149 allowed
150 .iter()
151 .any(|p| p.trim().eq_ignore_ascii_case(protocol))
152 }
153
154 pub fn unsafe_path_reason(path: &str) -> Option<&'static str> {
182 let lower = path.to_ascii_lowercase();
183 if lower.contains("%2f") || lower.contains("%5c") || path.contains('\\') {
184 return Some("Encoded or backslash path separators are not allowed");
185 }
186 for segment in lower.split('/') {
187 let segment = segment.split(';').next().unwrap_or("");
188 let decoded = segment.replace("%2e", ".");
189 if decoded == "." || decoded == ".." {
190 return Some("Dot-segments are not allowed in the request path");
191 }
192 }
193 None
194 }
195
196 pub fn strip_hop_by_hop_headers(request: &mut Request<ProxyBody>) {
204 let headers = request.headers_mut();
205
206 let mut named: Vec<String> = Vec::new();
209 for value in headers.get_all(CONNECTION).iter() {
210 let Ok(value) = value.to_str() else { continue };
211 for token in value.split(',') {
212 let token = token.trim().to_ascii_lowercase();
213 if !token.is_empty() {
214 named.push(token);
215 }
216 }
217 }
218
219 for name in HOP_BY_HOP_HEADERS {
220 headers.remove(*name);
221 }
222
223 for token in named {
224 if CONNECTION_TOKEN_EXEMPT.contains(&token.as_str()) {
225 continue;
226 }
227 if let Ok(name) = HeaderName::from_bytes(token.as_bytes()) {
228 headers.remove(name);
229 }
230 }
231 }
232
233 pub fn add_forwarding_headers(
251 request: &mut Request<ProxyBody>,
252 client_addr: SocketAddr,
253 proto: &str,
254 trust_client: bool,
255 ) {
256 let authority = request.uri().authority().map(|a| a.to_string());
257 let headers = request.headers_mut();
258 let client_ip = client_addr.ip().to_canonical();
259 let client_ip_text = client_ip.to_string();
260
261 let mut chain: Vec<String> = Vec::new();
264 if trust_client {
265 for value in headers.get_all(&headers::X_FORWARDED_FOR).iter() {
266 if let Ok(value) = value.to_str() {
267 let value = value.trim();
268 if !value.is_empty() {
269 chain.push(value.to_string());
270 }
271 }
272 }
273 }
274 chain.push(client_ip_text.clone());
275 headers.remove(&headers::X_FORWARDED_FOR);
276 if let Ok(value) = HeaderValue::from_str(&chain.join(", ")) {
277 headers.insert(headers::X_FORWARDED_FOR.clone(), value);
278 }
279
280 if let Ok(value) = HeaderValue::from_str(&client_ip_text) {
282 headers.insert(headers::X_REAL_IP.clone(), value);
283 }
284
285 let mut forwarded: Vec<String> = Vec::new();
289 if trust_client {
290 for value in headers.get_all(FORWARDED).iter() {
291 if let Ok(value) = value.to_str() {
292 let value = value.trim();
293 if !value.is_empty() {
294 forwarded.push(value.to_string());
295 }
296 }
297 }
298 }
299 headers.remove(FORWARDED);
300 if trust_client {
301 forwarded.push(format!("for={};proto={}", forwarded_node(client_ip), proto));
302 if let Ok(value) = HeaderValue::from_str(&forwarded.join(", ")) {
303 headers.insert(FORWARDED, value);
304 }
305 }
306
307 if !(trust_client && headers.contains_key(&headers::X_FORWARDED_PROTO)) {
309 if let Ok(value) = HeaderValue::from_str(proto) {
310 headers.insert(headers::X_FORWARDED_PROTO.clone(), value);
311 }
312 }
313
314 if !(trust_client && headers.contains_key(&headers::X_FORWARDED_HOST)) {
318 let original_host = headers
319 .get(HOST)
320 .cloned()
321 .or_else(|| authority.and_then(|a| HeaderValue::from_str(&a).ok()));
322 match original_host {
323 Some(host) => {
324 headers.insert(headers::X_FORWARDED_HOST.clone(), host);
325 }
326 None => {
327 headers.remove(&headers::X_FORWARDED_HOST);
328 }
329 }
330 }
331 }
332
333 pub fn rewrite_host_header(request: &mut Request<ProxyBody>, upstream_host: &str) {
335 if let Ok(value) = HeaderValue::from_str(upstream_host) {
336 request.headers_mut().insert(HOST, value);
337 }
338 }
339
340 pub fn strip_path_prefix(request: &mut Request<ProxyBody>, prefix: &str) {
346 let uri = request.uri();
347 let path = uri.path();
348
349 if !crate::config::path_has_prefix(path, prefix) {
350 return;
351 }
352 if let Some(new_path) = path.strip_prefix(prefix) {
353 let new_path = if new_path.is_empty() || !new_path.starts_with('/') {
354 format!("/{}", new_path.trim_start_matches('/'))
355 } else {
356 new_path.to_string()
357 };
358
359 let new_uri = if let Some(query) = uri.query() {
360 format!("{}?{}", new_path, query)
361 } else {
362 new_path
363 };
364
365 if let Ok(new_uri) = new_uri.parse() {
366 *request.uri_mut() = new_uri;
367 }
368 }
369 }
370
371 pub fn apply_route_headers(request: &mut Request<ProxyBody>, route: &Route) {
373 let headers = request.headers_mut();
374
375 for (name, value) in &route.add_headers {
377 if let (Ok(name), Ok(value)) = (
378 HeaderName::from_bytes(name.as_bytes()),
379 HeaderValue::from_str(value),
380 ) {
381 headers.insert(name, value);
382 }
383 }
384
385 for name in &route.remove_headers {
387 if let Ok(name) = HeaderName::from_bytes(name.as_bytes()) {
388 headers.remove(name);
389 }
390 }
391 }
392
393 pub fn error_response(status: StatusCode, message: &str) -> Response<ProxyBody> {
395 Response::builder()
396 .status(status)
397 .header("content-type", "text/plain; charset=utf-8")
398 .body(string_body(message.to_string()))
399 .unwrap_or_else(|_| {
400 Response::builder()
401 .status(StatusCode::INTERNAL_SERVER_ERROR)
402 .body(empty_body())
403 .unwrap()
404 })
405 }
406
407 pub fn bad_request(message: &str) -> Response<ProxyBody> {
409 Self::error_response(StatusCode::BAD_REQUEST, message)
410 }
411
412 pub fn bad_gateway(message: &str) -> Response<ProxyBody> {
414 Self::error_response(StatusCode::BAD_GATEWAY, message)
415 }
416
417 pub fn service_unavailable(message: &str) -> Response<ProxyBody> {
419 Self::error_response(StatusCode::SERVICE_UNAVAILABLE, message)
420 }
421
422 pub fn request_host(request: &Request<impl hyper::body::Body>) -> String {
434 let from_header = request
435 .headers()
436 .get(hyper::header::HOST)
437 .and_then(|value| value.to_str().ok())
438 .map(str::to_owned);
439
440 let host = from_header
441 .or_else(|| request.uri().authority().map(|a| a.to_string()))
442 .unwrap_or_default();
443
444 strip_port(&host).to_owned()
445 }
446
447 pub fn gateway_timeout(message: &str) -> Response<ProxyBody> {
453 Self::error_response(StatusCode::GATEWAY_TIMEOUT, message)
454 }
455
456 pub fn too_many_requests() -> Response<ProxyBody> {
458 Self::error_response(StatusCode::TOO_MANY_REQUESTS, "Too Many Requests")
459 }
460
461 pub fn not_found() -> Response<ProxyBody> {
463 Self::error_response(StatusCode::NOT_FOUND, "Not Found")
464 }
465
466 pub fn not_implemented(message: &str) -> Response<ProxyBody> {
468 Self::error_response(StatusCode::NOT_IMPLEMENTED, message)
469 }
470}
471
472fn forwarded_node(ip: IpAddr) -> String {
475 match ip {
476 IpAddr::V4(v4) => v4.to_string(),
477 IpAddr::V6(v6) => format!("\"[{v6}]\""),
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484
485 fn request_with(headers: &[(&str, &str)], uri: &str) -> Request<ProxyBody> {
486 let mut builder = Request::builder().uri(uri);
487 for (name, value) in headers {
488 builder = builder.header(*name, *value);
489 }
490 builder.body(empty_body()).unwrap()
491 }
492
493 #[test]
494 fn the_host_header_gives_the_host() {
495 let r = request_with(&[("host", "api.example.com")], "/x");
496 assert_eq!(MessageHandler::request_host(&r), "api.example.com");
497 }
498
499 #[test]
500 fn the_uri_authority_is_used_when_there_is_no_host_header() {
501 let r = request_with(&[], "http://api.example.com/x");
506 assert_eq!(MessageHandler::request_host(&r), "api.example.com");
507 }
508
509 #[test]
510 fn the_header_wins_over_the_authority() {
511 let r = request_with(&[("host", "from-header.test")], "http://from-uri.test/x");
512 assert_eq!(MessageHandler::request_host(&r), "from-header.test");
513 }
514
515 #[test]
516 fn the_port_is_not_part_of_the_host() {
517 assert_eq!(
518 MessageHandler::request_host(&request_with(&[("host", "api.example.com:8443")], "/x")),
519 "api.example.com"
520 );
521 assert_eq!(
522 MessageHandler::request_host(&request_with(&[], "http://api.example.com:8443/x")),
523 "api.example.com"
524 );
525 }
526
527 #[test]
528 fn an_ipv6_literal_keeps_its_brackets() {
529 assert_eq!(
532 MessageHandler::request_host(&request_with(&[("host", "[::1]:8443")], "/x")),
533 "[::1]"
534 );
535 assert_eq!(
536 MessageHandler::request_host(&request_with(&[("host", "[::1]")], "/x")),
537 "[::1]"
538 );
539 }
540
541 #[test]
542 fn a_request_with_neither_has_an_empty_host() {
543 let r = request_with(&[], "/x");
544 assert_eq!(MessageHandler::request_host(&r), "");
545 }
546
547 use crate::vendored::hyper_ext::empty_body;
548
549 #[test]
550 fn test_strip_path_prefix() {
551 let mut request = Request::builder()
552 .uri("/api/v1/users")
553 .body(empty_body())
554 .unwrap();
555
556 MessageHandler::strip_path_prefix(&mut request, "/api/v1");
557
558 assert_eq!(request.uri().path(), "/users");
559 }
560
561 #[test]
562 fn test_strip_path_prefix_root() {
563 let mut request = Request::builder().uri("/api").body(empty_body()).unwrap();
564
565 MessageHandler::strip_path_prefix(&mut request, "/api");
566
567 assert_eq!(request.uri().path(), "/");
568 }
569
570 #[test]
571 fn test_h2_connect_protocol_detected() {
572 let mut request = Request::builder()
575 .method(Method::CONNECT)
576 .version(Version::HTTP_2)
577 .uri("/chat")
578 .body(empty_body())
579 .unwrap();
580 request
581 .extensions_mut()
582 .insert(hyper::ext::Protocol::from_static("websocket"));
583
584 assert_eq!(
585 MessageHandler::h2_connect_protocol(&request).as_deref(),
586 Some("websocket")
587 );
588 assert!(MessageHandler::upgrade_protocol(&request).is_none());
590 }
591
592 #[test]
593 fn test_h2_connect_without_protocol_is_a_plain_connect() {
594 let request = Request::builder()
595 .method(Method::CONNECT)
596 .version(Version::HTTP_2)
597 .uri("/chat")
598 .body(empty_body())
599 .unwrap();
600
601 assert!(MessageHandler::h2_connect_protocol(&request).is_none());
602 }
603
604 #[test]
605 fn test_h1_connect_is_not_extended_connect() {
606 let mut request = Request::builder()
608 .method(Method::CONNECT)
609 .version(Version::HTTP_11)
610 .uri("/chat")
611 .body(empty_body())
612 .unwrap();
613 request
614 .extensions_mut()
615 .insert(hyper::ext::Protocol::from_static("websocket"));
616
617 assert!(MessageHandler::h2_connect_protocol(&request).is_none());
618 }
619
620 #[test]
621 fn test_upgrade_protocol_detected() {
622 let request = Request::builder()
623 .uri("/chat")
624 .header("host", "a")
625 .header("upgrade", "websocket")
626 .header("connection", "Upgrade")
627 .body(empty_body())
628 .unwrap();
629
630 assert_eq!(
631 MessageHandler::upgrade_protocol(&request).as_deref(),
632 Some("websocket")
633 );
634 }
635
636 #[test]
637 fn test_upgrade_needs_connection_token() {
638 let request = Request::builder()
640 .uri("/chat")
641 .header("host", "a")
642 .header("upgrade", "websocket")
643 .body(empty_body())
644 .unwrap();
645
646 assert!(MessageHandler::upgrade_protocol(&request).is_none());
647 }
648
649 #[test]
650 fn test_upgrade_token_found_among_others() {
651 let request = Request::builder()
652 .uri("/chat")
653 .header("host", "a")
654 .header("upgrade", "WebSocket")
655 .header("connection", "keep-alive, UPGRADE")
656 .body(empty_body())
657 .unwrap();
658
659 assert_eq!(
660 MessageHandler::upgrade_protocol(&request).as_deref(),
661 Some("websocket")
662 );
663 }
664
665 #[test]
666 fn test_no_upgrade_without_upgrade_header() {
667 let request = Request::builder()
668 .uri("/chat")
669 .header("host", "a")
670 .header("connection", "upgrade")
671 .body(empty_body())
672 .unwrap();
673
674 assert!(MessageHandler::upgrade_protocol(&request).is_none());
675 }
676
677 #[test]
678 fn test_http2_never_upgrades() {
679 let request = Request::builder()
681 .uri("/chat")
682 .version(Version::HTTP_2)
683 .header("upgrade", "websocket")
684 .header("connection", "Upgrade")
685 .body(empty_body())
686 .unwrap();
687
688 assert!(MessageHandler::upgrade_protocol(&request).is_none());
689 }
690
691 #[test]
692 fn test_restore_upgrade_headers_normalises_connection() {
693 let mut request = Request::builder()
695 .uri("/chat")
696 .header("host", "a")
697 .header("upgrade", "websocket")
698 .header("connection", "Upgrade, X-Sneaky")
699 .header("x-sneaky", "leaked")
700 .header("x-legit", "keep-me")
701 .body(empty_body())
702 .unwrap();
703
704 let protocol = MessageHandler::upgrade_protocol(&request).unwrap();
705 MessageHandler::strip_hop_by_hop_headers(&mut request);
706 MessageHandler::restore_upgrade_headers(&mut request, &protocol);
707
708 assert_eq!(request.headers().get("upgrade").unwrap(), "websocket");
709 assert_eq!(request.headers().get("connection").unwrap(), "upgrade");
710 assert!(!request.headers().contains_key("x-sneaky"));
711 assert_eq!(request.headers().get("x-legit").unwrap(), "keep-me");
712 }
713
714 #[test]
715 fn test_strips_each_hop_by_hop_header() {
716 for name in HOP_BY_HOP_HEADERS {
718 let mut request = Request::builder()
719 .uri("/test")
720 .header("host", "example.com")
721 .header(*name, "some-value")
722 .body(empty_body())
723 .unwrap();
724
725 MessageHandler::strip_hop_by_hop_headers(&mut request);
726
727 assert!(
728 !request.headers().contains_key(*name),
729 "hop-by-hop header {} was forwarded",
730 name
731 );
732 assert_eq!(request.headers().get("host").unwrap(), "example.com");
734 }
735 }
736
737 #[test]
738 fn test_strips_headers_named_in_connection() {
739 let mut request = Request::builder()
741 .uri("/test")
742 .header("host", "example.com")
743 .header("connection", "keep-alive, X-Hop")
744 .header("x-hop", "SHOULD-BE-STRIPPED")
745 .body(empty_body())
746 .unwrap();
747
748 MessageHandler::strip_hop_by_hop_headers(&mut request);
749
750 assert!(!request.headers().contains_key("connection"));
751 assert!(!request.headers().contains_key("x-hop"));
752 assert_eq!(request.headers().get("host").unwrap(), "example.com");
753 }
754
755 #[test]
756 fn test_connection_tokens_are_case_insensitive_and_trimmed() {
757 let mut request = Request::builder()
758 .uri("/test")
759 .header("connection", " X-UPPER ,\tx-lower,,")
760 .header("x-upper", "a")
761 .header("x-lower", "b")
762 .body(empty_body())
763 .unwrap();
764
765 MessageHandler::strip_hop_by_hop_headers(&mut request);
766
767 assert!(!request.headers().contains_key("x-upper"));
768 assert!(!request.headers().contains_key("x-lower"));
769 }
770
771 #[test]
772 fn test_connection_token_cannot_strip_host_or_content_length() {
773 let mut request = Request::builder()
776 .uri("/test")
777 .header("host", "example.com")
778 .header("content-length", "0")
779 .header("connection", "host, content-length")
780 .body(empty_body())
781 .unwrap();
782
783 MessageHandler::strip_hop_by_hop_headers(&mut request);
784
785 assert!(!request.headers().contains_key("connection"));
786 assert_eq!(request.headers().get("host").unwrap(), "example.com");
787 assert_eq!(request.headers().get("content-length").unwrap(), "0");
788 }
789
790 #[test]
791 fn test_connection_token_naming_forwarded_header_does_not_suppress_ours() {
792 let mut request = Request::builder()
795 .uri("/test")
796 .header("host", "example.com")
797 .header("connection", "x-forwarded-for")
798 .header("x-forwarded-for", "1.2.3.4")
799 .body(empty_body())
800 .unwrap();
801
802 MessageHandler::strip_hop_by_hop_headers(&mut request);
803 let client_addr: SocketAddr = "192.168.1.100:12345".parse().unwrap();
804 MessageHandler::add_forwarding_headers(&mut request, client_addr, "http", false);
805
806 assert_eq!(
807 request.headers().get("x-forwarded-for").unwrap(),
808 "192.168.1.100"
809 );
810 }
811
812 #[test]
813 fn test_strip_is_a_no_op_without_hop_by_hop_headers() {
814 let mut request = Request::builder()
815 .uri("/test")
816 .header("host", "example.com")
817 .header("accept", "*/*")
818 .body(empty_body())
819 .unwrap();
820
821 MessageHandler::strip_hop_by_hop_headers(&mut request);
822
823 assert_eq!(request.headers().len(), 2);
824 }
825
826 #[test]
827 fn test_forwarding_headers() {
828 let mut request = Request::builder()
829 .uri("/test")
830 .header("host", "example.com")
831 .body(empty_body())
832 .unwrap();
833
834 let client_addr: SocketAddr = "192.168.1.100:12345".parse().unwrap();
835 MessageHandler::add_forwarding_headers(&mut request, client_addr, "https", false);
836
837 assert_eq!(
838 request.headers().get("x-forwarded-for").unwrap(),
839 "192.168.1.100"
840 );
841 assert_eq!(request.headers().get("x-forwarded-proto").unwrap(), "https");
842 assert_eq!(
843 request.headers().get("x-forwarded-host").unwrap(),
844 "example.com"
845 );
846 }
847
848 fn forwarded_request(headers: &[(&str, &str)]) -> Request<ProxyBody> {
849 let mut request = request_with(headers, "/test");
850 let client_addr: SocketAddr = "192.0.2.7:4000".parse().unwrap();
851 MessageHandler::add_forwarding_headers(&mut request, client_addr, "https", false);
852 request
853 }
854
855 #[test]
856 fn client_forwarding_headers_are_overwritten_by_default() {
857 let request = forwarded_request(&[
861 ("host", "example.com"),
862 ("x-real-ip", "10.0.0.1"),
863 ("x-forwarded-for", "10.0.0.1"),
864 ("x-forwarded-for", "10.0.0.2"),
865 ("forwarded", "for=10.0.0.1;proto=https"),
866 ("x-forwarded-proto", "https-spoofed"),
867 ("x-forwarded-host", "spoofed.test"),
868 ]);
869 let h = request.headers();
870 assert_eq!(h.get("x-real-ip").unwrap(), "192.0.2.7");
871 assert_eq!(h.get_all("x-forwarded-for").iter().count(), 1);
872 assert_eq!(h.get("x-forwarded-for").unwrap(), "192.0.2.7");
873 assert!(!h.contains_key("forwarded"));
874 assert_eq!(h.get("x-forwarded-proto").unwrap(), "https");
875 assert_eq!(h.get("x-forwarded-host").unwrap(), "example.com");
876 }
877
878 #[test]
879 fn a_trusted_chain_is_kept_and_extended() {
880 let mut request = request_with(
881 &[
882 ("host", "internal.test"),
883 ("x-real-ip", "10.0.0.1"),
884 ("x-forwarded-for", "198.51.100.1, 10.0.0.1"),
885 ("forwarded", "for=198.51.100.1;proto=https"),
886 ("x-forwarded-proto", "https"),
887 ("x-forwarded-host", "public.example.com"),
888 ],
889 "/test",
890 );
891 let peer: SocketAddr = "[2001:db8::5]:4000".parse().unwrap();
892 MessageHandler::add_forwarding_headers(&mut request, peer, "http", true);
893
894 let h = request.headers();
895 assert_eq!(
896 h.get("x-forwarded-for").unwrap(),
897 "198.51.100.1, 10.0.0.1, 2001:db8::5"
898 );
899 assert_eq!(
900 h.get("forwarded").unwrap(),
901 "for=198.51.100.1;proto=https, for=\"[2001:db8::5]\";proto=http"
902 );
903 assert_eq!(h.get("x-forwarded-proto").unwrap(), "https");
906 assert_eq!(h.get("x-forwarded-host").unwrap(), "public.example.com");
907 assert_eq!(h.get("x-real-ip").unwrap(), "2001:db8::5");
909 }
910
911 #[test]
912 fn a_mapped_ipv4_peer_is_reported_as_ipv4() {
913 let mut request = request_with(&[("host", "a")], "/");
914 let peer: SocketAddr = "[::ffff:192.0.2.9]:1".parse().unwrap();
915 MessageHandler::add_forwarding_headers(&mut request, peer, "http", false);
916 assert_eq!(
917 request.headers().get("x-forwarded-for").unwrap(),
918 "192.0.2.9"
919 );
920 }
921
922 #[test]
923 fn dot_segments_and_encoded_separators_are_refused() {
924 for path in [
925 "/public/../admin",
926 "/public/./admin",
927 "/public/..",
928 "/..",
929 "/public/%2e%2e/admin",
930 "/public/%2E%2E/admin",
931 "/public/.%2e/admin",
932 "/public/%2e./admin",
933 "/public/%2e/admin",
934 "/public%2f..%2fadmin",
935 "/public%2F..%2Fadmin",
936 "/public/..%2fadmin",
937 "/public%5c..%5cadmin",
938 "/public/..;/admin",
939 "/public/..;jsessionid=x/admin",
940 ] {
941 assert!(
942 MessageHandler::unsafe_path_reason(path).is_some(),
943 "{path} should be refused"
944 );
945 }
946 }
947
948 #[test]
949 fn ordinary_paths_are_allowed() {
950 for path in [
951 "/",
952 "/public",
953 "/public/",
954 "/public/a.b",
955 "/public/...",
956 "/public/.well-known/x",
957 "/public/..x",
958 "/public/x..",
959 "/public/%2e%2e%2e",
960 "/public/a%20b",
961 "/public//double",
962 "",
963 ] {
964 assert!(
965 MessageHandler::unsafe_path_reason(path).is_none(),
966 "{path} should be allowed"
967 );
968 }
969 }
970
971 #[test]
972 fn a_prefix_is_stripped_only_on_a_segment_boundary() {
973 let mut request = request_with(&[], "/apis/x?q=1");
974 MessageHandler::strip_path_prefix(&mut request, "/api");
975 assert_eq!(request.uri().path(), "/apis/x");
976
977 let mut request = request_with(&[], "/api/x?q=1");
978 MessageHandler::strip_path_prefix(&mut request, "/api");
979 assert_eq!(request.uri().path(), "/x");
980 assert_eq!(request.uri().query(), Some("q=1"));
981
982 let mut request = request_with(&[], "/api/x");
984 MessageHandler::strip_path_prefix(&mut request, "/api/");
985 assert_eq!(request.uri().path(), "/x");
986 }
987
988 #[test]
989 fn only_allowed_protocols_may_be_upgraded_to() {
990 let allowed = vec!["websocket".to_string()];
991 assert!(MessageHandler::upgrade_allowed("websocket", &allowed));
992 assert!(MessageHandler::upgrade_allowed("WebSocket", &allowed));
993 assert!(!MessageHandler::upgrade_allowed("h2c", &allowed));
996 assert!(!MessageHandler::upgrade_allowed("websocket, h2c", &allowed));
997 assert!(!MessageHandler::upgrade_allowed("websocket", &[]));
998 }
999
1000 #[test]
1001 fn an_h2c_upgrade_leaves_nothing_behind_when_stripped() {
1002 let mut request = request_with(
1006 &[
1007 ("host", "a"),
1008 ("connection", "Upgrade"),
1009 ("upgrade", "h2c"),
1010 ("http2-settings", "AAMAAABkAAQCAAAAAAIAAAAA"),
1011 ],
1012 "/",
1013 );
1014 MessageHandler::strip_hop_by_hop_headers(&mut request);
1015 let h = request.headers();
1016 assert!(!h.contains_key("upgrade"));
1017 assert!(!h.contains_key("connection"));
1018 assert!(!h.contains_key("http2-settings"));
1019 }
1020}