Skip to main content

postrust_proxy/vendored/
handler.rs

1//! Vendored message handler from rpxy-lib: message_handler/*.rs
2//!
3//! This module handles request/response manipulation:
4//! - X-Forwarded-* header handling
5//! - Host header rewriting
6//! - Request parsing
7
8use 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
17/// Standard X-Forwarded headers.
18pub 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
27/// Headers that are hop-by-hop and must never reach an upstream.
28///
29/// RFC 9110 section 7.6.1. `Transfer-Encoding` belongs here even though it
30/// describes the body: the server side of hyper decodes the inbound body and
31/// the client side frames it again, so the inbound value describes a wire
32/// format the upstream never sees. Passing it through is what lets a client
33/// disagree with the proxy about where one request ends and the next begins.
34const 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
45/// Headers a `Connection` token may not remove.
46///
47/// Naming these in `Connection` is not a legitimate use of the field, and
48/// honouring it would hand a client a way to strip framing or identity
49/// headers on their way through the proxy.
50const CONNECTION_TOKEN_EXEMPT: &[&str] = &["host", "content-length"];
51
52/// Message handler for request/response manipulation.
53pub struct MessageHandler;
54
55/// Drop a `:port` from an authority, leaving the host.
56///
57/// Bracket-aware: the colons inside `[::1]` are part of the address, so a naive
58/// `split(':').next()` would return `[` for every IPv6 client.
59fn 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    /// The protocol a request is asking to upgrade to, if it is a valid
74    /// upgrade request.
75    ///
76    /// Requires both `Upgrade: <proto>` and an `upgrade` token in `Connection`,
77    /// per RFC 9110 section 7.8 -- `Upgrade` alone is not a request to switch.
78    /// HTTP/2 is excluded deliberately: it carries no `Upgrade` header and
79    /// negotiates extended CONNECT instead (RFC 8441), which is not implemented.
80    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    /// The protocol an HTTP/2 extended CONNECT is asking for (RFC 8441).
107    ///
108    /// HTTP/2 has no `Upgrade`; a WebSocket is opened with `:method = CONNECT`
109    /// plus a `:protocol` pseudo-header, which hyper surfaces as a
110    /// [`hyper::ext::Protocol`] extension. This is only ever offered when the
111    /// listener advertised SETTINGS_ENABLE_CONNECT_PROTOCOL.
112    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    /// Re-apply the upgrade headers that [`Self::strip_hop_by_hop_headers`] took.
123    ///
124    /// An upgrade is the one case where these headers legitimately travel to the
125    /// next hop. Only a normalised `Connection: upgrade` and the requested
126    /// protocol go back -- not whatever token list the client sent -- so the
127    /// carve-out cannot be used to smuggle other headers through.
128    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    /// Whether the request's body framing is ambiguous.
137    ///
138    /// RFC 9112 section 6.1: a message carrying both `Content-Length` and
139    /// `Transfer-Encoding` is either a smuggling attempt or a broken client.
140    /// `Transfer-Encoding` wins per the spec, but a proxy that quietly
141    /// normalises the disagreement is how a smuggling chain starts, so reject
142    /// it -- which is also how hyper's own parser already treats a duplicate
143    /// `Content-Length`.
144    ///
145    /// Must be called *before* [`Self::strip_hop_by_hop_headers`], which
146    /// removes the `Transfer-Encoding` this inspects.
147    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    /// Remove hop-by-hop headers before forwarding, per RFC 9110 section 7.6.1.
153    ///
154    /// Two sets go: the standard hop-by-hop headers, and every header named as
155    /// a token in the incoming `Connection` field. Call this *before*
156    /// [`Self::add_forwarding_headers`] -- a client that names `x-forwarded-for`
157    /// in `Connection` then strips only its own inbound value, and we set a
158    /// fresh one afterwards, rather than being able to suppress ours.
159    pub fn strip_hop_by_hop_headers(request: &mut Request<ProxyBody>) {
160        let headers = request.headers_mut();
161
162        // Read the Connection tokens up front: removing headers invalidates any
163        // borrow of the values we would otherwise still be holding.
164        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    /// Add X-Forwarded-* headers to a request.
190    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        // X-Forwarded-For: append client IP
200        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        // X-Real-IP: set if not present
212        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        // X-Forwarded-Proto
219        if let Ok(value) = HeaderValue::from_str(proto) {
220            headers.insert(headers::X_FORWARDED_PROTO.clone(), value);
221        }
222
223        // X-Forwarded-Host: preserve the original authority. HTTP/2 carries it in
224        // the :authority pseudo-header rather than Host, which reaches us on the
225        // URI, so fall back to that before giving up.
226        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    /// Rewrite the Host header for upstream.
236    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    /// Strip path prefix from request URI.
243    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    /// Apply route-specific header modifications.
267    pub fn apply_route_headers(request: &mut Request<ProxyBody>, route: &Route) {
268        let headers = request.headers_mut();
269
270        // Add headers
271        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        // Remove headers
281        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    /// Create a synthetic error response.
289    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    /// Create a 502 Bad Gateway response.
303    pub fn bad_request(message: &str) -> Response<ProxyBody> {
304        Self::error_response(StatusCode::BAD_REQUEST, message)
305    }
306
307    /// Create a 502 Bad Gateway response.
308    pub fn bad_gateway(message: &str) -> Response<ProxyBody> {
309        Self::error_response(StatusCode::BAD_GATEWAY, message)
310    }
311
312    /// Create a 503 Service Unavailable response.
313    pub fn service_unavailable(message: &str) -> Response<ProxyBody> {
314        Self::error_response(StatusCode::SERVICE_UNAVAILABLE, message)
315    }
316
317    /// The host a request is for, without its port.
318    ///
319    /// **Not just the `Host` header.** HTTP/2 has no `Host`: RFC 9113 section
320    /// 8.3.1 replaces it with the `:authority` pseudo-header, which reaches us
321    /// on the URI. Reading only the header therefore yields an empty host for
322    /// every h2 and h2c request, and a route with a `match.host` matches
323    /// nothing -- confirmed against a running proxy, where the same
324    /// host-matched route answered 200 over HTTP/1.1 and 404 over HTTP/2.
325    ///
326    /// `add_forwarding_headers` already had this fallback for
327    /// `x-forwarded-host`; route selection did not.
328    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    /// Create a 504 Gateway Timeout response.
343    ///
344    /// Takes a message so the body can say how long the request waited. A bare
345    /// "Gateway Timeout" leaves the caller unable to tell a route configured
346    /// with one second from an upstream that is genuinely wedged.
347    pub fn gateway_timeout(message: &str) -> Response<ProxyBody> {
348        Self::error_response(StatusCode::GATEWAY_TIMEOUT, message)
349    }
350
351    /// Create a 429 Too Many Requests response.
352    pub fn too_many_requests() -> Response<ProxyBody> {
353        Self::error_response(StatusCode::TOO_MANY_REQUESTS, "Too Many Requests")
354    }
355
356    /// Create a 404 Not Found response.
357    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        // This is every HTTP/2 request: RFC 9113 section 8.3.1 replaces Host
383        // with :authority, which reaches us on the URI. Reading only the header
384        // made every host-matched route 404 over h2 -- confirmed against a
385        // running proxy before this was fixed.
386        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        // `split(':').next()` would return "[" here, so every IPv6 client would
411        // be matched against a host of "[".
412        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        // RFC 8441: :method CONNECT plus the :protocol pseudo-header, which
454        // hyper surfaces as an extension.
455        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        // The HTTP/1.1 detector must not claim it.
470        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        // Extended CONNECT is an HTTP/2 mechanism; the h1 spelling is Upgrade.
488        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        // Upgrade alone is not a request to switch protocols (RFC 9110 s7.8).
520        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        // HTTP/2 negotiates extended CONNECT (RFC 8441), not Upgrade.
561        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        // The carve-out must put back only `upgrade`, never the client's list.
575        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        // Regression: stripping Transfer-Encoding leaves a stale Content-Length
598        // describing a body hyper has already re-framed, so this must be
599        // rejected up front rather than forwarded.
600        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        // One assertion per header in the RFC 9110 section 7.6.1 set.
638        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            // Stripping must not take unrelated headers with it.
654            assert_eq!(request.headers().get("host").unwrap(), "example.com");
655        }
656    }
657
658    #[test]
659    fn test_strips_headers_named_in_connection() {
660        // The live case found by the HTTP Garden probe.
661        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        // Naming these in Connection is not legitimate; honouring it would let a
695        // client strip framing or identity headers on the way through.
696        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        // strip runs before add_forwarding_headers, so the client's inbound value
714        // goes and ours replaces it -- no suppression, no spoofing.
715        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}