client_ip/
lib.rs

1#![doc = include_str!("../README.md")]
2use std::net::IpAddr;
3
4pub use error::Error;
5use http::{HeaderMap, HeaderName};
6
7type Result<T> = std::result::Result<T, Error>;
8
9/// Extracts client IP from `CF-Connecting-IP` (Cloudflare) header
10pub fn cf_connecting_ip(header_map: &HeaderMap) -> Result<IpAddr> {
11    ip_from_single_header(header_map, &HeaderName::from_static("cf-connecting-ip"))
12}
13
14/// Extracts client IP from `CloudFront-Viewer-Address` (AWS CloudFront) header
15pub fn cloudfront_viewer_address(header_map: &HeaderMap) -> Result<IpAddr> {
16    const HEADER_NAME: HeaderName = HeaderName::from_static("cloudfront-viewer-address");
17
18    fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
19        // Spec: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html#cloudfront-headers-viewer-location
20        // Note: Both IPv4 and IPv6 addresses (in the specified format) do not contain
21        //       non-ascii characters, so no need to handle percent-encoding.
22        //
23        // CloudFront does not use `[::]:12345` style notation for IPv6 (unfortunately),
24        // otherwise parsing via `SocketAddr` would be possible.
25        header_value
26            .rsplit_once(':')
27            .map(|(ip, _port)| ip)
28            .ok_or_else(|| Error::MalformedHeaderValue {
29                header_name: HEADER_NAME,
30                header_value: header_value.to_owned(),
31            })?
32            .trim()
33            .parse::<IpAddr>()
34            .map_err(|_| Error::MalformedHeaderValue {
35                header_name: HEADER_NAME,
36                header_value: header_value.to_owned(),
37            })
38    }
39
40    let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
41    ip_from_header_value(header_value.0)
42}
43
44/// Extracts client IP from `Fly-Client-IP` (Fly.io) header
45///
46/// When the extractor is run for health check path, provide required
47/// `Fly-Client-IP` header through [`services.http_checks.headers`](https://fly.io/docs/reference/configuration/#services-http_checks)
48/// or [`http_service.checks.headers`](https://fly.io/docs/reference/configuration/#services-http_checks)
49pub fn fly_client_ip(header_map: &HeaderMap) -> Result<IpAddr> {
50    ip_from_single_header(header_map, &HeaderName::from_static("fly-client-ip"))
51}
52
53#[cfg(feature = "forwarded-header")]
54/// Extracts the rightmost IP from `Forwarded` header
55pub fn rightmost_forwarded(header_map: &HeaderMap) -> Result<IpAddr> {
56    const HEADER_NAME: HeaderName = HeaderName::from_static("forwarded");
57
58    fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
59        use forwarded_header_value::{ForwardedHeaderValue, Identifier};
60
61        let stanza = ForwardedHeaderValue::from_forwarded(header_value)
62            .map_err(|_| Error::MalformedHeaderValue {
63                header_name: HEADER_NAME,
64                header_value: header_value.to_owned(),
65            })?
66            .into_iter()
67            .last()
68            .ok_or_else(|| Error::MalformedHeaderValue {
69                header_name: HEADER_NAME,
70                header_value: header_value.to_owned(),
71            })?;
72
73        let forwarded_for = stanza.forwarded_for.ok_or_else(|| Error::ForwardedNoFor {
74            header_value: header_value.to_owned(),
75        })?;
76
77        match forwarded_for {
78            Identifier::SocketAddr(a) => Ok(a.ip()),
79            Identifier::IpAddr(ip) => Ok(ip),
80            Identifier::String(_) => Err(Error::ForwardedObfuscated {
81                header_value: header_value.to_owned(),
82            }),
83            Identifier::Unknown => Err(Error::ForwardedUnknown {
84                header_value: header_value.to_owned(),
85            }),
86        }
87    }
88
89    let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
90    ip_from_header_value(header_value.0)
91}
92
93/// Extracts the rightmost IP address from the comma-separated list in the value
94/// of the last `X-Forwarded-For` header.
95pub fn rightmost_x_forwarded_for(header_map: &HeaderMap) -> Result<IpAddr> {
96    const HEADER_NAME: HeaderName = HeaderName::from_static("x-forwarded-for");
97
98    fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
99        header_value
100            .split(',')
101            .next_back()
102            .ok_or_else(|| Error::MalformedHeaderValue {
103                header_name: HEADER_NAME,
104                header_value: header_value.to_owned(),
105            })?
106            .trim()
107            .parse::<IpAddr>()
108            .map_err(|_| Error::MalformedHeaderValue {
109                header_name: HEADER_NAME,
110                header_value: header_value.to_owned(),
111            })
112    }
113
114    let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
115    ip_from_header_value(header_value.0)
116}
117
118/// Extracts client IP from `True-Client-IP` (Akamai, Cloudflare) header
119pub fn true_client_ip(header_map: &HeaderMap) -> Result<IpAddr> {
120    ip_from_single_header(header_map, &HeaderName::from_static("true-client-ip"))
121}
122
123/// Extracts client IP from `X-Envoy-External-Address` header
124pub fn x_envoy_external_address(header_map: &HeaderMap) -> Result<IpAddr> {
125    ip_from_single_header(
126        header_map,
127        &HeaderName::from_static("x-envoy-external-address"),
128    )
129}
130
131/// Extracts client IP from `X-Real-Ip` (Nginx) header
132pub fn x_real_ip(header_map: &HeaderMap) -> Result<IpAddr> {
133    ip_from_single_header(header_map, &HeaderName::from_static("x-real-ip"))
134}
135
136/// A [`http::HeaderValue`] converted to string and ensured to be valid ASCII
137#[derive(Debug)]
138struct AsciiHeaderValue<'a>(&'a str);
139
140impl<'a> AsciiHeaderValue<'a> {
141    /// Returns value of a header that must occur only once. Multiple
142    /// occurrences of the header are considered a critical proxy configuration
143    /// error.
144    fn of_single_header(header_map: &'a HeaderMap, header_name: &HeaderName) -> Result<Self> {
145        let mut iter = header_map.get_all(header_name).into_iter();
146
147        let Some(header_value) = iter.next() else {
148            return Err(Error::AbsentHeader {
149                header_name: header_name.to_owned(),
150            });
151        };
152
153        if iter.next().is_some() {
154            return Err(Error::SingleHeaderRequired {
155                header_name: header_name.to_owned(),
156            });
157        }
158
159        header_value
160            .to_str()
161            .map_err(|_| Error::NonAsciiHeaderValue {
162                header_name: header_name.to_owned(),
163            })
164            .map(Self)
165    }
166
167    /// Returns a value of the last occurring header.
168    fn of_last_header(header_map: &'a HeaderMap, header_name: &HeaderName) -> Result<Self> {
169        header_map
170            .get_all(header_name)
171            .into_iter()
172            .next_back()
173            .ok_or_else(|| Error::AbsentHeader {
174                header_name: header_name.to_owned(),
175            })?
176            .to_str()
177            .map_err(|_| Error::NonAsciiHeaderValue {
178                header_name: header_name.to_owned(),
179            })
180            .map(Self)
181    }
182
183    /// Tries to parse the whole value as an IP.
184    fn parse_ip(&self, header_name: &HeaderName) -> Result<IpAddr> {
185        self.0
186            .trim()
187            .parse()
188            .map_err(|_| Error::MalformedHeaderValue {
189                header_name: header_name.to_owned(),
190                header_value: self.0.to_owned(),
191            })
192    }
193}
194
195/// Parses an IP from a header that occurs only once. Multiple
196/// occurrences of the header are considered a proxy configuration error.
197fn ip_from_single_header(header_map: &HeaderMap, header_name: &HeaderName) -> Result<IpAddr> {
198    AsciiHeaderValue::of_single_header(header_map, header_name)?.parse_ip(header_name)
199}
200
201mod error {
202    use std::fmt;
203
204    use http::HeaderName;
205
206    /// Errors that can occur during IP extraction
207    #[derive(Debug, PartialEq)]
208    pub enum Error {
209        /// The IP-related header is missing
210        AbsentHeader {
211            /// Header name
212            header_name: HeaderName,
213        },
214        /// Header value contains not only visible ASCII characters
215        NonAsciiHeaderValue {
216            /// Header name
217            header_name: HeaderName,
218        },
219        /// Header value has an unexpected format
220        MalformedHeaderValue {
221            /// Header name
222            header_name: HeaderName,
223            /// Header value
224            header_value: String,
225        },
226        /// Multiple occurrences of a header required to occur only once found
227        ///
228        /// According to the HTTP/1.1 specification (RFC 7230, Section 3.2.2):
229        /// > A sender MUST NOT generate multiple header fields with the same
230        /// > field name in a message unless either the entire field value for
231        /// > that header field is defined as a comma-separated list ...
232        SingleHeaderRequired {
233            /// Header name
234            header_name: HeaderName,
235        },
236        #[cfg(feature = "forwarded-header")]
237        /// Forwarded header doesn't contain `for` directive
238        ForwardedNoFor {
239            /// Header value
240            header_value: String,
241        },
242        #[cfg(feature = "forwarded-header")]
243        /// RFC 7239 allows to [obfuscate IPs](https://www.rfc-editor.org/rfc/rfc7239.html#section-6.3)
244        ForwardedObfuscated {
245            /// Header value
246            header_value: String,
247        },
248        #[cfg(feature = "forwarded-header")]
249        /// RFC 7239 allows [unknown identifiers](https://www.rfc-editor.org/rfc/rfc7239.html#section-6.2)
250        ForwardedUnknown {
251            /// Header value
252            header_value: String,
253        },
254    }
255
256    impl fmt::Display for Error {
257        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258            match self {
259                Self::AbsentHeader { header_name } => {
260                    write!(f, "Missing required header: {header_name}")
261                }
262                Self::NonAsciiHeaderValue { header_name } => write!(
263                    f,
264                    "Header value contains non-ASCII characters: {header_name}",
265                ),
266                Self::MalformedHeaderValue {
267                    header_name,
268                    header_value,
269                } => write!(
270                    f,
271                    "Malformed header value for `{header_name}`: {header_value}",
272                ),
273                Self::SingleHeaderRequired { header_name } => write!(
274                    f,
275                    "Multiple occurrences of the header aren't allowed: {header_name}"
276                ),
277                #[cfg(feature = "forwarded-header")]
278                Self::ForwardedNoFor { header_value } => write!(
279                    f,
280                    "`Forwarded` header missing `for` directive: {header_value}",
281                ),
282                #[cfg(feature = "forwarded-header")]
283                Self::ForwardedObfuscated { header_value } => write!(
284                    f,
285                    "`Forwarded` header contains obfuscated IP: {header_value}",
286                ),
287                #[cfg(feature = "forwarded-header")]
288                Self::ForwardedUnknown { header_value } => write!(
289                    f,
290                    "`Forwarded` header contains unknown identifier: {header_value}",
291                ),
292            }
293        }
294    }
295
296    impl std::error::Error for Error {}
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    const VALID_IPV4: &str = "1.2.3.4";
304    const VALID_IPV6: &str = "1:23:4567:89ab:c:d:e:f";
305
306    fn headers<'a>(items: impl IntoIterator<Item = (&'a str, &'a str)>) -> HeaderMap {
307        HeaderMap::from_iter(
308            items
309                .into_iter()
310                .map(|(name, value)| (name.parse().unwrap(), value.parse().unwrap())),
311        )
312    }
313
314    #[test]
315    fn test_ascii_header_value_of_last_header() {
316        let header_name_str = "my-header";
317        let header_name = HeaderName::from_static(header_name_str);
318
319        assert_eq!(
320            AsciiHeaderValue::of_last_header(&headers([]), &header_name).unwrap_err(),
321            Error::AbsentHeader {
322                header_name: header_name.clone()
323            }
324        );
325
326        assert_eq!(
327            AsciiHeaderValue::of_last_header(&headers([(header_name_str, "ы")]), &header_name)
328                .unwrap_err(),
329            Error::NonAsciiHeaderValue {
330                header_name: header_name.clone()
331            }
332        );
333
334        assert_eq!(
335            AsciiHeaderValue::of_last_header(&headers([(header_name_str, "foo")]), &header_name)
336                .unwrap()
337                .0,
338            "foo",
339            "single valid header"
340        );
341
342        assert_eq!(
343            AsciiHeaderValue::of_last_header(
344                &headers([(header_name_str, "foo"), (header_name_str, "bar")]),
345                &header_name
346            )
347            .unwrap()
348            .0,
349            "bar",
350            "multiple valid headers"
351        );
352    }
353
354    #[test]
355    fn test_ascii_header_value_of_single_header() {
356        let header_name_str = "my-header";
357        let header_name = HeaderName::from_static(header_name_str);
358
359        assert_eq!(
360            AsciiHeaderValue::of_single_header(&headers([]), &header_name).unwrap_err(),
361            Error::AbsentHeader {
362                header_name: header_name.clone()
363            }
364        );
365
366        assert_eq!(
367            AsciiHeaderValue::of_single_header(&headers([(header_name_str, "ы")]), &header_name)
368                .unwrap_err(),
369            Error::NonAsciiHeaderValue {
370                header_name: header_name.clone()
371            }
372        );
373
374        assert_eq!(
375            AsciiHeaderValue::of_single_header(
376                &headers([(header_name_str, "foo"), (header_name_str, "bar")]),
377                &header_name
378            )
379            .unwrap_err(),
380            Error::SingleHeaderRequired {
381                header_name: header_name.clone()
382            }
383        );
384
385        assert_eq!(
386            AsciiHeaderValue::of_single_header(&headers([(header_name_str, "foo")]), &header_name)
387                .unwrap()
388                .0,
389            "foo"
390        );
391    }
392
393    #[test]
394    fn test_cf_connecting_ip() {
395        let header = "cf-connecting-ip";
396
397        assert_eq!(
398            cf_connecting_ip(&headers([])).unwrap_err(),
399            Error::AbsentHeader {
400                header_name: HeaderName::from_static(header)
401            }
402        );
403        assert_eq!(
404            cf_connecting_ip(&headers([(header, "ы")])).unwrap_err(),
405            Error::NonAsciiHeaderValue {
406                header_name: HeaderName::from_static(header)
407            }
408        );
409        assert_eq!(
410            cf_connecting_ip(&headers([(header, "foo")])).unwrap_err(),
411            Error::MalformedHeaderValue {
412                header_name: HeaderName::from_static(header),
413                header_value: "foo".into(),
414            }
415        );
416
417        assert_eq!(
418            cf_connecting_ip(&headers([(header, VALID_IPV4)])).unwrap(),
419            VALID_IPV4.parse::<IpAddr>().unwrap()
420        );
421        assert_eq!(
422            cf_connecting_ip(&headers([(header, VALID_IPV6)])).unwrap(),
423            VALID_IPV6.parse::<IpAddr>().unwrap()
424        );
425    }
426
427    #[test]
428    fn test_cloudfront_viewer_address() {
429        let header = "cloudfront-viewer-address";
430
431        assert_eq!(
432            cloudfront_viewer_address(&headers([])).unwrap_err(),
433            Error::AbsentHeader {
434                header_name: HeaderName::from_static(header)
435            }
436        );
437        assert_eq!(
438            cloudfront_viewer_address(&headers([(header, "ы")])).unwrap_err(),
439            Error::NonAsciiHeaderValue {
440                header_name: HeaderName::from_static(header)
441            }
442        );
443        assert_eq!(
444            cloudfront_viewer_address(&headers([(header, VALID_IPV4)])).unwrap_err(),
445            Error::MalformedHeaderValue {
446                header_name: HeaderName::from_static(header),
447                header_value: VALID_IPV4.into(),
448            }
449        );
450        assert_eq!(
451            cloudfront_viewer_address(&headers([(header, "foo:8000")])).unwrap_err(),
452            Error::MalformedHeaderValue {
453                header_name: HeaderName::from_static(header),
454                header_value: "foo:8000".into(),
455            }
456        );
457
458        let valid_header_value_v4 = format!("{VALID_IPV4}:8000");
459        let valid_header_value_v6 = format!("{VALID_IPV6}:8000");
460        assert_eq!(
461            cloudfront_viewer_address(&headers([(header, valid_header_value_v4.as_ref())]))
462                .unwrap(),
463            VALID_IPV4.parse::<IpAddr>().unwrap()
464        );
465        assert_eq!(
466            cloudfront_viewer_address(&headers([(header, valid_header_value_v6.as_ref())]))
467                .unwrap(),
468            VALID_IPV6.parse::<IpAddr>().unwrap()
469        );
470    }
471
472    #[test]
473    fn test_fly_client_ip() {
474        let header = "fly-client-ip";
475
476        assert_eq!(
477            fly_client_ip(&headers([])).unwrap_err(),
478            Error::AbsentHeader {
479                header_name: HeaderName::from_static(header)
480            }
481        );
482        assert_eq!(
483            fly_client_ip(&headers([(header, "ы")])).unwrap_err(),
484            Error::NonAsciiHeaderValue {
485                header_name: HeaderName::from_static(header)
486            }
487        );
488        assert_eq!(
489            fly_client_ip(&headers([(header, "foo")])).unwrap_err(),
490            Error::MalformedHeaderValue {
491                header_name: HeaderName::from_static(header),
492                header_value: "foo".into(),
493            }
494        );
495
496        assert_eq!(
497            fly_client_ip(&headers([(header, VALID_IPV4)])).unwrap(),
498            VALID_IPV4.parse::<IpAddr>().unwrap()
499        );
500        assert_eq!(
501            fly_client_ip(&headers([(header, VALID_IPV6)])).unwrap(),
502            VALID_IPV6.parse::<IpAddr>().unwrap()
503        );
504    }
505
506    #[cfg(feature = "forwarded-header")]
507    #[test]
508    fn test_rightmost_forwarded() {
509        let header = "forwarded";
510
511        assert_eq!(
512            rightmost_forwarded(&headers([])).unwrap_err(),
513            Error::AbsentHeader {
514                header_name: HeaderName::from_static(header)
515            }
516        );
517        assert_eq!(
518            rightmost_forwarded(&headers([(header, "ы")])).unwrap_err(),
519            Error::NonAsciiHeaderValue {
520                header_name: HeaderName::from_static(header)
521            }
522        );
523        assert_eq!(
524            rightmost_forwarded(&headers([(header, "foo")])).unwrap_err(),
525            Error::MalformedHeaderValue {
526                header_name: HeaderName::from_static(header),
527                header_value: "foo".into(),
528            }
529        );
530        assert_eq!(
531            rightmost_forwarded(&headers([
532                (header, format!("for={VALID_IPV4}").as_ref()),
533                (header, "proto=http"),
534            ]))
535            .unwrap_err(),
536            Error::ForwardedNoFor {
537                header_value: "proto=http".into(),
538            }
539        );
540        assert_eq!(
541            rightmost_forwarded(&headers([(header, "for=unknown")])).unwrap_err(),
542            Error::ForwardedUnknown {
543                header_value: "for=unknown".into(),
544            }
545        );
546        assert_eq!(
547            rightmost_forwarded(&headers([(header, "for=_foo")])).unwrap_err(),
548            Error::ForwardedObfuscated {
549                header_value: "for=_foo".into(),
550            }
551        );
552
553        assert_eq!(
554            rightmost_forwarded(&headers([
555                (header, "proto=http"),
556                (header, format!("for={VALID_IPV4};proto=http").as_ref()),
557            ]))
558            .unwrap(),
559            VALID_IPV4.parse::<IpAddr>().unwrap()
560        );
561        assert_eq!(
562            rightmost_forwarded(&headers([(
563                header,
564                format!("for={VALID_IPV4}:8000").as_ref()
565            ),]))
566            .unwrap(),
567            VALID_IPV4.parse::<IpAddr>().unwrap()
568        );
569
570        assert_eq!(
571            rightmost_forwarded(&headers([(header, format!("for={VALID_IPV6}").as_ref()),]))
572                .unwrap(),
573            VALID_IPV6.parse::<IpAddr>().unwrap()
574        );
575        assert_eq!(
576            rightmost_forwarded(&headers([(
577                header,
578                format!("for=[{VALID_IPV6}]:8000").as_ref()
579            ),]))
580            .unwrap(),
581            VALID_IPV6.parse::<IpAddr>().unwrap()
582        );
583    }
584
585    #[test]
586    fn test_rightmost_x_forwarded_for() {
587        let header = "x-forwarded-for";
588
589        assert_eq!(
590            rightmost_x_forwarded_for(&headers([])).unwrap_err(),
591            Error::AbsentHeader {
592                header_name: HeaderName::from_static(header)
593            }
594        );
595        assert_eq!(
596            rightmost_x_forwarded_for(&headers([(header, "ы")])).unwrap_err(),
597            Error::NonAsciiHeaderValue {
598                header_name: HeaderName::from_static(header)
599            }
600        );
601        assert_eq!(
602            rightmost_x_forwarded_for(&headers([(header, "1.2.3.4,foo")])).unwrap_err(),
603            Error::MalformedHeaderValue {
604                header_name: HeaderName::from_static(header),
605                header_value: "1.2.3.4,foo".into(),
606            }
607        );
608
609        assert_eq!(
610            rightmost_x_forwarded_for(&headers([(header, format!("foo,{VALID_IPV4}").as_ref())]))
611                .unwrap(),
612            VALID_IPV4.parse::<IpAddr>().unwrap()
613        );
614        assert_eq!(
615            rightmost_x_forwarded_for(&headers([(header, VALID_IPV6)])).unwrap(),
616            VALID_IPV6.parse::<IpAddr>().unwrap()
617        );
618    }
619
620    #[test]
621    fn test_true_client_ip() {
622        let header = "true-client-ip";
623
624        assert_eq!(
625            true_client_ip(&headers([])).unwrap_err(),
626            Error::AbsentHeader {
627                header_name: HeaderName::from_static(header)
628            }
629        );
630        assert_eq!(
631            true_client_ip(&headers([(header, "ы")])).unwrap_err(),
632            Error::NonAsciiHeaderValue {
633                header_name: HeaderName::from_static(header)
634            }
635        );
636        assert_eq!(
637            true_client_ip(&headers([(header, "foo")])).unwrap_err(),
638            Error::MalformedHeaderValue {
639                header_name: HeaderName::from_static(header),
640                header_value: "foo".into(),
641            }
642        );
643
644        assert_eq!(
645            true_client_ip(&headers([(header, VALID_IPV4)])).unwrap(),
646            VALID_IPV4.parse::<IpAddr>().unwrap()
647        );
648        assert_eq!(
649            true_client_ip(&headers([(header, VALID_IPV6)])).unwrap(),
650            VALID_IPV6.parse::<IpAddr>().unwrap()
651        );
652    }
653
654    #[test]
655    fn test_x_envoy_external_address() {
656        let header = "x-envoy-external-address";
657
658        assert_eq!(
659            x_envoy_external_address(&headers([])).unwrap_err(),
660            Error::AbsentHeader {
661                header_name: HeaderName::from_static(header)
662            }
663        );
664        assert_eq!(
665            x_envoy_external_address(&headers([(header, "ы")])).unwrap_err(),
666            Error::NonAsciiHeaderValue {
667                header_name: HeaderName::from_static(header)
668            }
669        );
670        assert_eq!(
671            x_envoy_external_address(&headers([(header, "foo")])).unwrap_err(),
672            Error::MalformedHeaderValue {
673                header_name: HeaderName::from_static(header),
674                header_value: "foo".into(),
675            }
676        );
677
678        assert_eq!(
679            x_envoy_external_address(&headers([(header, VALID_IPV4)])).unwrap(),
680            VALID_IPV4.parse::<IpAddr>().unwrap()
681        );
682        assert_eq!(
683            x_envoy_external_address(&headers([(header, VALID_IPV6)])).unwrap(),
684            VALID_IPV6.parse::<IpAddr>().unwrap()
685        );
686    }
687
688    #[test]
689    fn test_x_real_ip() {
690        let header = "x-real-ip";
691
692        assert_eq!(
693            x_real_ip(&headers([])).unwrap_err(),
694            Error::AbsentHeader {
695                header_name: HeaderName::from_static(header)
696            }
697        );
698        assert_eq!(
699            x_real_ip(&headers([(header, "ы")])).unwrap_err(),
700            Error::NonAsciiHeaderValue {
701                header_name: HeaderName::from_static(header)
702            }
703        );
704        assert_eq!(
705            x_real_ip(&headers([(header, "foo")])).unwrap_err(),
706            Error::MalformedHeaderValue {
707                header_name: HeaderName::from_static(header),
708                header_value: "foo".into(),
709            }
710        );
711
712        assert_eq!(
713            x_real_ip(&headers([(header, VALID_IPV4)])).unwrap(),
714            VALID_IPV4.parse::<IpAddr>().unwrap()
715        );
716        assert_eq!(
717            x_real_ip(&headers([(header, VALID_IPV6)])).unwrap(),
718            VALID_IPV6.parse::<IpAddr>().unwrap()
719        );
720    }
721}