1use crate::config::Route;
9use crate::vendored::hyper_ext::{empty_body, string_body, ProxyBody};
10use hyper::header::{
11 HeaderName, HeaderValue, CONNECTION, CONTENT_LENGTH, HOST, TRANSFER_ENCODING, UPGRADE,
12};
13use hyper::{Method, Version};
14use hyper::{Request, Response, StatusCode};
15use std::net::SocketAddr;
16
17pub mod headers {
19 use hyper::header::HeaderName;
20
21 pub static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
22 pub static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
23 pub static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
24 pub static X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip");
25}
26
27const HOP_BY_HOP_HEADERS: &[&str] = &[
35 "connection",
36 "keep-alive",
37 "proxy-authenticate",
38 "proxy-authorization",
39 "te",
40 "trailer",
41 "transfer-encoding",
42 "upgrade",
43];
44
45const CONNECTION_TOKEN_EXEMPT: &[&str] = &["host", "content-length"];
51
52pub struct MessageHandler;
54
55fn strip_port(authority: &str) -> &str {
60 if authority.starts_with('[') {
61 return match authority.find(']') {
62 Some(end) => &authority[..end + 1],
63 None => authority,
64 };
65 }
66 match authority.split_once(':') {
67 Some((host, _)) => host,
68 None => authority,
69 }
70}
71
72impl MessageHandler {
73 pub fn upgrade_protocol(request: &Request<ProxyBody>) -> Option<String> {
81 if request.version() != Version::HTTP_11 {
82 return None;
83 }
84
85 let headers = request.headers();
86 let asks_for_upgrade = headers.get_all(CONNECTION).iter().any(|value| {
87 value
88 .to_str()
89 .map(|v| {
90 v.split(',')
91 .any(|t| t.trim().eq_ignore_ascii_case("upgrade"))
92 })
93 .unwrap_or(false)
94 });
95 if !asks_for_upgrade {
96 return None;
97 }
98
99 headers
100 .get(UPGRADE)
101 .and_then(|v| v.to_str().ok())
102 .map(|v| v.trim().to_ascii_lowercase())
103 .filter(|v| !v.is_empty())
104 }
105
106 pub fn h2_connect_protocol(request: &Request<ProxyBody>) -> Option<String> {
113 if request.version() != Version::HTTP_2 || request.method() != Method::CONNECT {
114 return None;
115 }
116 request
117 .extensions()
118 .get::<hyper::ext::Protocol>()
119 .map(|protocol| protocol.as_str().to_ascii_lowercase())
120 }
121
122 pub fn restore_upgrade_headers(request: &mut Request<ProxyBody>, protocol: &str) {
129 if let Ok(value) = HeaderValue::from_str(protocol) {
130 let headers = request.headers_mut();
131 headers.insert(UPGRADE, value);
132 headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
133 }
134 }
135
136 pub fn has_ambiguous_framing(request: &Request<ProxyBody>) -> bool {
148 let headers = request.headers();
149 headers.contains_key(CONTENT_LENGTH) && headers.contains_key(TRANSFER_ENCODING)
150 }
151
152 pub fn strip_hop_by_hop_headers(request: &mut Request<ProxyBody>) {
160 let headers = request.headers_mut();
161
162 let mut named: Vec<String> = Vec::new();
165 for value in headers.get_all(CONNECTION).iter() {
166 let Ok(value) = value.to_str() else { continue };
167 for token in value.split(',') {
168 let token = token.trim().to_ascii_lowercase();
169 if !token.is_empty() {
170 named.push(token);
171 }
172 }
173 }
174
175 for name in HOP_BY_HOP_HEADERS {
176 headers.remove(*name);
177 }
178
179 for token in named {
180 if CONNECTION_TOKEN_EXEMPT.contains(&token.as_str()) {
181 continue;
182 }
183 if let Ok(name) = HeaderName::from_bytes(token.as_bytes()) {
184 headers.remove(name);
185 }
186 }
187 }
188
189 pub fn add_forwarding_headers(
191 request: &mut Request<ProxyBody>,
192 client_addr: SocketAddr,
193 proto: &str,
194 ) {
195 let authority = request.uri().authority().map(|a| a.to_string());
196 let headers = request.headers_mut();
197 let client_ip = client_addr.ip().to_string();
198
199 if let Some(existing) = headers.get(&headers::X_FORWARDED_FOR) {
201 let mut new_value = existing.to_str().unwrap_or("").to_string();
202 new_value.push_str(", ");
203 new_value.push_str(&client_ip);
204 if let Ok(value) = HeaderValue::from_str(&new_value) {
205 headers.insert(headers::X_FORWARDED_FOR.clone(), value);
206 }
207 } else if let Ok(value) = HeaderValue::from_str(&client_ip) {
208 headers.insert(headers::X_FORWARDED_FOR.clone(), value);
209 }
210
211 if !headers.contains_key(&headers::X_REAL_IP) {
213 if let Ok(value) = HeaderValue::from_str(&client_ip) {
214 headers.insert(headers::X_REAL_IP.clone(), value);
215 }
216 }
217
218 if let Ok(value) = HeaderValue::from_str(proto) {
220 headers.insert(headers::X_FORWARDED_PROTO.clone(), value);
221 }
222
223 let original_host = headers
227 .get(HOST)
228 .cloned()
229 .or_else(|| authority.and_then(|a| HeaderValue::from_str(&a).ok()));
230 if let Some(host) = original_host {
231 headers.insert(headers::X_FORWARDED_HOST.clone(), host);
232 }
233 }
234
235 pub fn rewrite_host_header(request: &mut Request<ProxyBody>, upstream_host: &str) {
237 if let Ok(value) = HeaderValue::from_str(upstream_host) {
238 request.headers_mut().insert(HOST, value);
239 }
240 }
241
242 pub fn strip_path_prefix(request: &mut Request<ProxyBody>, prefix: &str) {
244 let uri = request.uri();
245 let path = uri.path();
246
247 if let Some(new_path) = path.strip_prefix(prefix) {
248 let new_path = if new_path.is_empty() || !new_path.starts_with('/') {
249 format!("/{}", new_path.trim_start_matches('/'))
250 } else {
251 new_path.to_string()
252 };
253
254 let new_uri = if let Some(query) = uri.query() {
255 format!("{}?{}", new_path, query)
256 } else {
257 new_path
258 };
259
260 if let Ok(new_uri) = new_uri.parse() {
261 *request.uri_mut() = new_uri;
262 }
263 }
264 }
265
266 pub fn apply_route_headers(request: &mut Request<ProxyBody>, route: &Route) {
268 let headers = request.headers_mut();
269
270 for (name, value) in &route.add_headers {
272 if let (Ok(name), Ok(value)) = (
273 HeaderName::from_bytes(name.as_bytes()),
274 HeaderValue::from_str(value),
275 ) {
276 headers.insert(name, value);
277 }
278 }
279
280 for name in &route.remove_headers {
282 if let Ok(name) = HeaderName::from_bytes(name.as_bytes()) {
283 headers.remove(name);
284 }
285 }
286 }
287
288 pub fn error_response(status: StatusCode, message: &str) -> Response<ProxyBody> {
290 Response::builder()
291 .status(status)
292 .header("content-type", "text/plain; charset=utf-8")
293 .body(string_body(message.to_string()))
294 .unwrap_or_else(|_| {
295 Response::builder()
296 .status(StatusCode::INTERNAL_SERVER_ERROR)
297 .body(empty_body())
298 .unwrap()
299 })
300 }
301
302 pub fn bad_request(message: &str) -> Response<ProxyBody> {
304 Self::error_response(StatusCode::BAD_REQUEST, message)
305 }
306
307 pub fn bad_gateway(message: &str) -> Response<ProxyBody> {
309 Self::error_response(StatusCode::BAD_GATEWAY, message)
310 }
311
312 pub fn service_unavailable(message: &str) -> Response<ProxyBody> {
314 Self::error_response(StatusCode::SERVICE_UNAVAILABLE, message)
315 }
316
317 pub fn request_host(request: &Request<impl hyper::body::Body>) -> String {
329 let from_header = request
330 .headers()
331 .get(hyper::header::HOST)
332 .and_then(|value| value.to_str().ok())
333 .map(str::to_owned);
334
335 let host = from_header
336 .or_else(|| request.uri().authority().map(|a| a.to_string()))
337 .unwrap_or_default();
338
339 strip_port(&host).to_owned()
340 }
341
342 pub fn gateway_timeout(message: &str) -> Response<ProxyBody> {
348 Self::error_response(StatusCode::GATEWAY_TIMEOUT, message)
349 }
350
351 pub fn too_many_requests() -> Response<ProxyBody> {
353 Self::error_response(StatusCode::TOO_MANY_REQUESTS, "Too Many Requests")
354 }
355
356 pub fn not_found() -> Response<ProxyBody> {
358 Self::error_response(StatusCode::NOT_FOUND, "Not Found")
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 fn request_with(headers: &[(&str, &str)], uri: &str) -> Request<ProxyBody> {
367 let mut builder = Request::builder().uri(uri);
368 for (name, value) in headers {
369 builder = builder.header(*name, *value);
370 }
371 builder.body(empty_body()).unwrap()
372 }
373
374 #[test]
375 fn the_host_header_gives_the_host() {
376 let r = request_with(&[("host", "api.example.com")], "/x");
377 assert_eq!(MessageHandler::request_host(&r), "api.example.com");
378 }
379
380 #[test]
381 fn the_uri_authority_is_used_when_there_is_no_host_header() {
382 let r = request_with(&[], "http://api.example.com/x");
387 assert_eq!(MessageHandler::request_host(&r), "api.example.com");
388 }
389
390 #[test]
391 fn the_header_wins_over_the_authority() {
392 let r = request_with(&[("host", "from-header.test")], "http://from-uri.test/x");
393 assert_eq!(MessageHandler::request_host(&r), "from-header.test");
394 }
395
396 #[test]
397 fn the_port_is_not_part_of_the_host() {
398 assert_eq!(
399 MessageHandler::request_host(&request_with(&[("host", "api.example.com:8443")], "/x")),
400 "api.example.com"
401 );
402 assert_eq!(
403 MessageHandler::request_host(&request_with(&[], "http://api.example.com:8443/x")),
404 "api.example.com"
405 );
406 }
407
408 #[test]
409 fn an_ipv6_literal_keeps_its_brackets() {
410 assert_eq!(
413 MessageHandler::request_host(&request_with(&[("host", "[::1]:8443")], "/x")),
414 "[::1]"
415 );
416 assert_eq!(
417 MessageHandler::request_host(&request_with(&[("host", "[::1]")], "/x")),
418 "[::1]"
419 );
420 }
421
422 #[test]
423 fn a_request_with_neither_has_an_empty_host() {
424 let r = request_with(&[], "/x");
425 assert_eq!(MessageHandler::request_host(&r), "");
426 }
427
428 use crate::vendored::hyper_ext::empty_body;
429
430 #[test]
431 fn test_strip_path_prefix() {
432 let mut request = Request::builder()
433 .uri("/api/v1/users")
434 .body(empty_body())
435 .unwrap();
436
437 MessageHandler::strip_path_prefix(&mut request, "/api/v1");
438
439 assert_eq!(request.uri().path(), "/users");
440 }
441
442 #[test]
443 fn test_strip_path_prefix_root() {
444 let mut request = Request::builder().uri("/api").body(empty_body()).unwrap();
445
446 MessageHandler::strip_path_prefix(&mut request, "/api");
447
448 assert_eq!(request.uri().path(), "/");
449 }
450
451 #[test]
452 fn test_h2_connect_protocol_detected() {
453 let mut request = Request::builder()
456 .method(Method::CONNECT)
457 .version(Version::HTTP_2)
458 .uri("/chat")
459 .body(empty_body())
460 .unwrap();
461 request
462 .extensions_mut()
463 .insert(hyper::ext::Protocol::from_static("websocket"));
464
465 assert_eq!(
466 MessageHandler::h2_connect_protocol(&request).as_deref(),
467 Some("websocket")
468 );
469 assert!(MessageHandler::upgrade_protocol(&request).is_none());
471 }
472
473 #[test]
474 fn test_h2_connect_without_protocol_is_a_plain_connect() {
475 let request = Request::builder()
476 .method(Method::CONNECT)
477 .version(Version::HTTP_2)
478 .uri("/chat")
479 .body(empty_body())
480 .unwrap();
481
482 assert!(MessageHandler::h2_connect_protocol(&request).is_none());
483 }
484
485 #[test]
486 fn test_h1_connect_is_not_extended_connect() {
487 let mut request = Request::builder()
489 .method(Method::CONNECT)
490 .version(Version::HTTP_11)
491 .uri("/chat")
492 .body(empty_body())
493 .unwrap();
494 request
495 .extensions_mut()
496 .insert(hyper::ext::Protocol::from_static("websocket"));
497
498 assert!(MessageHandler::h2_connect_protocol(&request).is_none());
499 }
500
501 #[test]
502 fn test_upgrade_protocol_detected() {
503 let request = Request::builder()
504 .uri("/chat")
505 .header("host", "a")
506 .header("upgrade", "websocket")
507 .header("connection", "Upgrade")
508 .body(empty_body())
509 .unwrap();
510
511 assert_eq!(
512 MessageHandler::upgrade_protocol(&request).as_deref(),
513 Some("websocket")
514 );
515 }
516
517 #[test]
518 fn test_upgrade_needs_connection_token() {
519 let request = Request::builder()
521 .uri("/chat")
522 .header("host", "a")
523 .header("upgrade", "websocket")
524 .body(empty_body())
525 .unwrap();
526
527 assert!(MessageHandler::upgrade_protocol(&request).is_none());
528 }
529
530 #[test]
531 fn test_upgrade_token_found_among_others() {
532 let request = Request::builder()
533 .uri("/chat")
534 .header("host", "a")
535 .header("upgrade", "WebSocket")
536 .header("connection", "keep-alive, UPGRADE")
537 .body(empty_body())
538 .unwrap();
539
540 assert_eq!(
541 MessageHandler::upgrade_protocol(&request).as_deref(),
542 Some("websocket")
543 );
544 }
545
546 #[test]
547 fn test_no_upgrade_without_upgrade_header() {
548 let request = Request::builder()
549 .uri("/chat")
550 .header("host", "a")
551 .header("connection", "upgrade")
552 .body(empty_body())
553 .unwrap();
554
555 assert!(MessageHandler::upgrade_protocol(&request).is_none());
556 }
557
558 #[test]
559 fn test_http2_never_upgrades() {
560 let request = Request::builder()
562 .uri("/chat")
563 .version(Version::HTTP_2)
564 .header("upgrade", "websocket")
565 .header("connection", "Upgrade")
566 .body(empty_body())
567 .unwrap();
568
569 assert!(MessageHandler::upgrade_protocol(&request).is_none());
570 }
571
572 #[test]
573 fn test_restore_upgrade_headers_normalises_connection() {
574 let mut request = Request::builder()
576 .uri("/chat")
577 .header("host", "a")
578 .header("upgrade", "websocket")
579 .header("connection", "Upgrade, X-Sneaky")
580 .header("x-sneaky", "leaked")
581 .header("x-legit", "keep-me")
582 .body(empty_body())
583 .unwrap();
584
585 let protocol = MessageHandler::upgrade_protocol(&request).unwrap();
586 MessageHandler::strip_hop_by_hop_headers(&mut request);
587 MessageHandler::restore_upgrade_headers(&mut request, &protocol);
588
589 assert_eq!(request.headers().get("upgrade").unwrap(), "websocket");
590 assert_eq!(request.headers().get("connection").unwrap(), "upgrade");
591 assert!(!request.headers().contains_key("x-sneaky"));
592 assert_eq!(request.headers().get("x-legit").unwrap(), "keep-me");
593 }
594
595 #[test]
596 fn test_ambiguous_framing_detected() {
597 let request = Request::builder()
601 .uri("/test")
602 .header("host", "a")
603 .header("content-length", "6")
604 .header("transfer-encoding", "chunked")
605 .body(empty_body())
606 .unwrap();
607
608 assert!(MessageHandler::has_ambiguous_framing(&request));
609 }
610
611 #[test]
612 fn test_content_length_alone_is_not_ambiguous() {
613 let request = Request::builder()
614 .uri("/test")
615 .header("host", "a")
616 .header("content-length", "6")
617 .body(empty_body())
618 .unwrap();
619
620 assert!(!MessageHandler::has_ambiguous_framing(&request));
621 }
622
623 #[test]
624 fn test_transfer_encoding_alone_is_not_ambiguous() {
625 let request = Request::builder()
626 .uri("/test")
627 .header("host", "a")
628 .header("transfer-encoding", "chunked")
629 .body(empty_body())
630 .unwrap();
631
632 assert!(!MessageHandler::has_ambiguous_framing(&request));
633 }
634
635 #[test]
636 fn test_strips_each_hop_by_hop_header() {
637 for name in HOP_BY_HOP_HEADERS {
639 let mut request = Request::builder()
640 .uri("/test")
641 .header("host", "example.com")
642 .header(*name, "some-value")
643 .body(empty_body())
644 .unwrap();
645
646 MessageHandler::strip_hop_by_hop_headers(&mut request);
647
648 assert!(
649 !request.headers().contains_key(*name),
650 "hop-by-hop header {} was forwarded",
651 name
652 );
653 assert_eq!(request.headers().get("host").unwrap(), "example.com");
655 }
656 }
657
658 #[test]
659 fn test_strips_headers_named_in_connection() {
660 let mut request = Request::builder()
662 .uri("/test")
663 .header("host", "example.com")
664 .header("connection", "keep-alive, X-Hop")
665 .header("x-hop", "SHOULD-BE-STRIPPED")
666 .body(empty_body())
667 .unwrap();
668
669 MessageHandler::strip_hop_by_hop_headers(&mut request);
670
671 assert!(!request.headers().contains_key("connection"));
672 assert!(!request.headers().contains_key("x-hop"));
673 assert_eq!(request.headers().get("host").unwrap(), "example.com");
674 }
675
676 #[test]
677 fn test_connection_tokens_are_case_insensitive_and_trimmed() {
678 let mut request = Request::builder()
679 .uri("/test")
680 .header("connection", " X-UPPER ,\tx-lower,,")
681 .header("x-upper", "a")
682 .header("x-lower", "b")
683 .body(empty_body())
684 .unwrap();
685
686 MessageHandler::strip_hop_by_hop_headers(&mut request);
687
688 assert!(!request.headers().contains_key("x-upper"));
689 assert!(!request.headers().contains_key("x-lower"));
690 }
691
692 #[test]
693 fn test_connection_token_cannot_strip_host_or_content_length() {
694 let mut request = Request::builder()
697 .uri("/test")
698 .header("host", "example.com")
699 .header("content-length", "0")
700 .header("connection", "host, content-length")
701 .body(empty_body())
702 .unwrap();
703
704 MessageHandler::strip_hop_by_hop_headers(&mut request);
705
706 assert!(!request.headers().contains_key("connection"));
707 assert_eq!(request.headers().get("host").unwrap(), "example.com");
708 assert_eq!(request.headers().get("content-length").unwrap(), "0");
709 }
710
711 #[test]
712 fn test_connection_token_naming_forwarded_header_does_not_suppress_ours() {
713 let mut request = Request::builder()
716 .uri("/test")
717 .header("host", "example.com")
718 .header("connection", "x-forwarded-for")
719 .header("x-forwarded-for", "1.2.3.4")
720 .body(empty_body())
721 .unwrap();
722
723 MessageHandler::strip_hop_by_hop_headers(&mut request);
724 let client_addr: SocketAddr = "192.168.1.100:12345".parse().unwrap();
725 MessageHandler::add_forwarding_headers(&mut request, client_addr, "http");
726
727 assert_eq!(
728 request.headers().get("x-forwarded-for").unwrap(),
729 "192.168.1.100"
730 );
731 }
732
733 #[test]
734 fn test_strip_is_a_no_op_without_hop_by_hop_headers() {
735 let mut request = Request::builder()
736 .uri("/test")
737 .header("host", "example.com")
738 .header("accept", "*/*")
739 .body(empty_body())
740 .unwrap();
741
742 MessageHandler::strip_hop_by_hop_headers(&mut request);
743
744 assert_eq!(request.headers().len(), 2);
745 }
746
747 #[test]
748 fn test_forwarding_headers() {
749 let mut request = Request::builder()
750 .uri("/test")
751 .header("host", "example.com")
752 .body(empty_body())
753 .unwrap();
754
755 let client_addr: SocketAddr = "192.168.1.100:12345".parse().unwrap();
756 MessageHandler::add_forwarding_headers(&mut request, client_addr, "https");
757
758 assert_eq!(
759 request.headers().get("x-forwarded-for").unwrap(),
760 "192.168.1.100"
761 );
762 assert_eq!(request.headers().get("x-forwarded-proto").unwrap(), "https");
763 assert_eq!(
764 request.headers().get("x-forwarded-host").unwrap(),
765 "example.com"
766 );
767 }
768}