1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3use std::net::IpAddr;
4
5pub use error::Error;
6use http::{HeaderMap, HeaderName};
7
8type Result<T> = std::result::Result<T, Error>;
9
10pub fn cf_connecting_ip(header_map: &HeaderMap) -> Result<IpAddr> {
12 ip_from_single_header(header_map, &HeaderName::from_static("cf-connecting-ip"))
13}
14
15pub fn cloudfront_viewer_address(header_map: &HeaderMap) -> Result<IpAddr> {
17 const HEADER_NAME: HeaderName = HeaderName::from_static("cloudfront-viewer-address");
18
19 fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
20 header_value
27 .rsplit_once(':')
28 .map(|(ip, _port)| ip)
29 .ok_or_else(|| Error::MalformedHeaderValue {
30 header_name: HEADER_NAME,
31 header_value: header_value.to_owned(),
32 })?
33 .trim()
34 .parse::<IpAddr>()
35 .map_err(|_| Error::MalformedHeaderValue {
36 header_name: HEADER_NAME,
37 header_value: header_value.to_owned(),
38 })
39 }
40
41 let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
42 ip_from_header_value(header_value.0)
43}
44
45pub fn fly_client_ip(header_map: &HeaderMap) -> Result<IpAddr> {
51 ip_from_single_header(header_map, &HeaderName::from_static("fly-client-ip"))
52}
53
54#[cfg(feature = "forwarded-header")]
55pub fn rightmost_forwarded(header_map: &HeaderMap) -> Result<IpAddr> {
57 const HEADER_NAME: HeaderName = HeaderName::from_static("forwarded");
58
59 fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
60 use forwarded_header_value::{ForwardedHeaderValue, Identifier};
61
62 let stanza = ForwardedHeaderValue::from_forwarded(header_value)
63 .map_err(|_| Error::MalformedHeaderValue {
64 header_name: HEADER_NAME,
65 header_value: header_value.to_owned(),
66 })?
67 .into_iter()
68 .last()
69 .ok_or_else(|| Error::MalformedHeaderValue {
70 header_name: HEADER_NAME,
71 header_value: header_value.to_owned(),
72 })?;
73
74 let forwarded_for = stanza.forwarded_for.ok_or_else(|| Error::ForwardedNoFor {
75 header_value: header_value.to_owned(),
76 })?;
77
78 match forwarded_for {
79 Identifier::SocketAddr(a) => Ok(a.ip()),
80 Identifier::IpAddr(ip) => Ok(ip),
81 Identifier::String(_) => Err(Error::ForwardedObfuscated {
82 header_value: header_value.to_owned(),
83 }),
84 Identifier::Unknown => Err(Error::ForwardedUnknown {
85 header_value: header_value.to_owned(),
86 }),
87 }
88 }
89
90 let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
91 ip_from_header_value(header_value.0)
92}
93
94pub fn rightmost_x_forwarded_for(header_map: &HeaderMap) -> Result<IpAddr> {
97 const HEADER_NAME: HeaderName = HeaderName::from_static("x-forwarded-for");
98
99 fn ip_from_header_value(header_value: &str) -> Result<IpAddr> {
100 header_value
101 .split(',')
102 .next_back()
103 .ok_or_else(|| Error::MalformedHeaderValue {
104 header_name: HEADER_NAME,
105 header_value: header_value.to_owned(),
106 })?
107 .trim()
108 .parse::<IpAddr>()
109 .map_err(|_| Error::MalformedHeaderValue {
110 header_name: HEADER_NAME,
111 header_value: header_value.to_owned(),
112 })
113 }
114
115 let header_value = AsciiHeaderValue::of_last_header(header_map, &HEADER_NAME)?;
116 ip_from_header_value(header_value.0)
117}
118
119pub fn true_client_ip(header_map: &HeaderMap) -> Result<IpAddr> {
121 ip_from_single_header(header_map, &HeaderName::from_static("true-client-ip"))
122}
123
124pub fn x_real_ip(header_map: &HeaderMap) -> Result<IpAddr> {
126 ip_from_single_header(header_map, &HeaderName::from_static("x-real-ip"))
127}
128
129#[derive(Debug)]
131struct AsciiHeaderValue<'a>(&'a str);
132
133impl<'a> AsciiHeaderValue<'a> {
134 fn of_single_header(header_map: &'a HeaderMap, header_name: &HeaderName) -> Result<Self> {
138 let mut iter = header_map.get_all(header_name).into_iter();
139
140 let Some(header_value) = iter.next() else {
141 return Err(Error::AbsentHeader {
142 header_name: header_name.to_owned(),
143 });
144 };
145
146 if iter.next().is_some() {
147 return Err(Error::SingleHeaderRequired {
148 header_name: header_name.to_owned(),
149 });
150 }
151
152 header_value
153 .to_str()
154 .map_err(|_| Error::NonAsciiHeaderValue {
155 header_name: header_name.to_owned(),
156 })
157 .map(Self)
158 }
159
160 fn of_last_header(header_map: &'a HeaderMap, header_name: &HeaderName) -> Result<Self> {
162 header_map
163 .get_all(header_name)
164 .into_iter()
165 .next_back()
166 .ok_or_else(|| Error::AbsentHeader {
167 header_name: header_name.to_owned(),
168 })?
169 .to_str()
170 .map_err(|_| Error::NonAsciiHeaderValue {
171 header_name: header_name.to_owned(),
172 })
173 .map(Self)
174 }
175
176 fn parse_ip(&self, header_name: &HeaderName) -> Result<IpAddr> {
178 self.0
179 .trim()
180 .parse()
181 .map_err(|_| Error::MalformedHeaderValue {
182 header_name: header_name.to_owned(),
183 header_value: self.0.to_owned(),
184 })
185 }
186}
187
188fn ip_from_single_header(header_map: &HeaderMap, header_name: &HeaderName) -> Result<IpAddr> {
191 AsciiHeaderValue::of_single_header(header_map, header_name)?.parse_ip(header_name)
192}
193
194mod error {
195 use std::fmt;
196
197 use http::HeaderName;
198
199 #[derive(Debug, PartialEq)]
201 pub enum Error {
202 AbsentHeader {
204 header_name: HeaderName,
206 },
207 NonAsciiHeaderValue {
209 header_name: HeaderName,
211 },
212 MalformedHeaderValue {
214 header_name: HeaderName,
216 header_value: String,
218 },
219 SingleHeaderRequired {
226 header_name: HeaderName,
228 },
229 #[cfg(feature = "forwarded-header")]
230 ForwardedNoFor {
232 header_value: String,
234 },
235 #[cfg(feature = "forwarded-header")]
236 ForwardedObfuscated {
238 header_value: String,
240 },
241 #[cfg(feature = "forwarded-header")]
242 ForwardedUnknown {
244 header_value: String,
246 },
247 }
248
249 impl fmt::Display for Error {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 match self {
252 Self::AbsentHeader { header_name } => {
253 write!(f, "Missing required header: {header_name}")
254 }
255 Self::NonAsciiHeaderValue { header_name } => write!(
256 f,
257 "Header value contains non-ASCII characters: {header_name}",
258 ),
259 Self::MalformedHeaderValue {
260 header_name,
261 header_value,
262 } => write!(
263 f,
264 "Malformed header value for `{header_name}`: {header_value}",
265 ),
266 Self::SingleHeaderRequired { header_name } => write!(
267 f,
268 "Multiple occurrences of the header aren't allowed: {header_name}"
269 ),
270 #[cfg(feature = "forwarded-header")]
271 Self::ForwardedNoFor { header_value } => write!(
272 f,
273 "`Forwarded` header missing `for` directive: {header_value}",
274 ),
275 #[cfg(feature = "forwarded-header")]
276 Self::ForwardedObfuscated { header_value } => write!(
277 f,
278 "`Forwarded` header contains obfuscated IP: {header_value}",
279 ),
280 #[cfg(feature = "forwarded-header")]
281 Self::ForwardedUnknown { header_value } => write!(
282 f,
283 "`Forwarded` header contains unknown identifier: {header_value}",
284 ),
285 }
286 }
287 }
288
289 impl std::error::Error for Error {}
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 const VALID_IPV4: &str = "1.2.3.4";
297 const VALID_IPV6: &str = "1:23:4567:89ab:c:d:e:f";
298
299 fn headers<'a>(items: impl IntoIterator<Item = (&'a str, &'a str)>) -> HeaderMap {
300 HeaderMap::from_iter(
301 items
302 .into_iter()
303 .map(|(name, value)| (name.parse().unwrap(), value.parse().unwrap())),
304 )
305 }
306
307 #[test]
308 fn test_ascii_header_value_of_last_header() {
309 let header_name_str = "my-header";
310 let header_name = HeaderName::from_static(header_name_str);
311
312 assert_eq!(
313 AsciiHeaderValue::of_last_header(&headers([]), &header_name).unwrap_err(),
314 Error::AbsentHeader {
315 header_name: header_name.clone()
316 }
317 );
318
319 assert_eq!(
320 AsciiHeaderValue::of_last_header(&headers([(header_name_str, "ы")]), &header_name)
321 .unwrap_err(),
322 Error::NonAsciiHeaderValue {
323 header_name: header_name.clone()
324 }
325 );
326
327 assert_eq!(
328 AsciiHeaderValue::of_last_header(&headers([(header_name_str, "foo")]), &header_name)
329 .unwrap()
330 .0,
331 "foo",
332 "single valid header"
333 );
334
335 assert_eq!(
336 AsciiHeaderValue::of_last_header(
337 &headers([(header_name_str, "foo"), (header_name_str, "bar")]),
338 &header_name
339 )
340 .unwrap()
341 .0,
342 "bar",
343 "multiple valid headers"
344 );
345 }
346
347 #[test]
348 fn test_ascii_header_value_of_single_header() {
349 let header_name_str = "my-header";
350 let header_name = HeaderName::from_static(header_name_str);
351
352 assert_eq!(
353 AsciiHeaderValue::of_single_header(&headers([]), &header_name).unwrap_err(),
354 Error::AbsentHeader {
355 header_name: header_name.clone()
356 }
357 );
358
359 assert_eq!(
360 AsciiHeaderValue::of_single_header(&headers([(header_name_str, "ы")]), &header_name)
361 .unwrap_err(),
362 Error::NonAsciiHeaderValue {
363 header_name: header_name.clone()
364 }
365 );
366
367 assert_eq!(
368 AsciiHeaderValue::of_single_header(
369 &headers([(header_name_str, "foo"), (header_name_str, "bar")]),
370 &header_name
371 )
372 .unwrap_err(),
373 Error::SingleHeaderRequired {
374 header_name: header_name.clone()
375 }
376 );
377
378 assert_eq!(
379 AsciiHeaderValue::of_single_header(&headers([(header_name_str, "foo")]), &header_name)
380 .unwrap()
381 .0,
382 "foo"
383 );
384 }
385
386 #[test]
387 fn test_cf_connecting_ip() {
388 let header = "cf-connecting-ip";
389
390 assert_eq!(
391 cf_connecting_ip(&headers([])).unwrap_err(),
392 Error::AbsentHeader {
393 header_name: HeaderName::from_static(header)
394 }
395 );
396 assert_eq!(
397 cf_connecting_ip(&headers([(header, "ы")])).unwrap_err(),
398 Error::NonAsciiHeaderValue {
399 header_name: HeaderName::from_static(header)
400 }
401 );
402 assert_eq!(
403 cf_connecting_ip(&headers([(header, "foo")])).unwrap_err(),
404 Error::MalformedHeaderValue {
405 header_name: HeaderName::from_static(header),
406 header_value: "foo".into(),
407 }
408 );
409
410 assert_eq!(
411 cf_connecting_ip(&headers([(header, VALID_IPV4)])).unwrap(),
412 VALID_IPV4.parse::<IpAddr>().unwrap()
413 );
414 assert_eq!(
415 cf_connecting_ip(&headers([(header, VALID_IPV6)])).unwrap(),
416 VALID_IPV6.parse::<IpAddr>().unwrap()
417 );
418 }
419
420 #[test]
421 fn test_cloudfront_viewer_address() {
422 let header = "cloudfront-viewer-address";
423
424 assert_eq!(
425 cloudfront_viewer_address(&headers([])).unwrap_err(),
426 Error::AbsentHeader {
427 header_name: HeaderName::from_static(header)
428 }
429 );
430 assert_eq!(
431 cloudfront_viewer_address(&headers([(header, "ы")])).unwrap_err(),
432 Error::NonAsciiHeaderValue {
433 header_name: HeaderName::from_static(header)
434 }
435 );
436 assert_eq!(
437 cloudfront_viewer_address(&headers([(header, VALID_IPV4)])).unwrap_err(),
438 Error::MalformedHeaderValue {
439 header_name: HeaderName::from_static(header),
440 header_value: VALID_IPV4.into(),
441 }
442 );
443 assert_eq!(
444 cloudfront_viewer_address(&headers([(header, "foo:8000")])).unwrap_err(),
445 Error::MalformedHeaderValue {
446 header_name: HeaderName::from_static(header),
447 header_value: "foo:8000".into(),
448 }
449 );
450
451 let valid_header_value_v4 = format!("{VALID_IPV4}:8000");
452 let valid_header_value_v6 = format!("{VALID_IPV6}:8000");
453 assert_eq!(
454 cloudfront_viewer_address(&headers([(header, valid_header_value_v4.as_ref())]))
455 .unwrap(),
456 VALID_IPV4.parse::<IpAddr>().unwrap()
457 );
458 assert_eq!(
459 cloudfront_viewer_address(&headers([(header, valid_header_value_v6.as_ref())]))
460 .unwrap(),
461 VALID_IPV6.parse::<IpAddr>().unwrap()
462 );
463 }
464
465 #[test]
466 fn test_fly_client_ip() {
467 let header = "fly-client-ip";
468
469 assert_eq!(
470 fly_client_ip(&headers([])).unwrap_err(),
471 Error::AbsentHeader {
472 header_name: HeaderName::from_static(header)
473 }
474 );
475 assert_eq!(
476 fly_client_ip(&headers([(header, "ы")])).unwrap_err(),
477 Error::NonAsciiHeaderValue {
478 header_name: HeaderName::from_static(header)
479 }
480 );
481 assert_eq!(
482 fly_client_ip(&headers([(header, "foo")])).unwrap_err(),
483 Error::MalformedHeaderValue {
484 header_name: HeaderName::from_static(header),
485 header_value: "foo".into(),
486 }
487 );
488
489 assert_eq!(
490 fly_client_ip(&headers([(header, VALID_IPV4)])).unwrap(),
491 VALID_IPV4.parse::<IpAddr>().unwrap()
492 );
493 assert_eq!(
494 fly_client_ip(&headers([(header, VALID_IPV6)])).unwrap(),
495 VALID_IPV6.parse::<IpAddr>().unwrap()
496 );
497 }
498
499 #[cfg(feature = "forwarded-header")]
500 #[test]
501 fn test_rightmost_forwarded() {
502 let header = "forwarded";
503
504 assert_eq!(
505 rightmost_forwarded(&headers([])).unwrap_err(),
506 Error::AbsentHeader {
507 header_name: HeaderName::from_static(header)
508 }
509 );
510 assert_eq!(
511 rightmost_forwarded(&headers([(header, "ы")])).unwrap_err(),
512 Error::NonAsciiHeaderValue {
513 header_name: HeaderName::from_static(header)
514 }
515 );
516 assert_eq!(
517 rightmost_forwarded(&headers([(header, "foo")])).unwrap_err(),
518 Error::MalformedHeaderValue {
519 header_name: HeaderName::from_static(header),
520 header_value: "foo".into(),
521 }
522 );
523 assert_eq!(
524 rightmost_forwarded(&headers([
525 (header, format!("for={VALID_IPV4}").as_ref()),
526 (header, "proto=http"),
527 ]))
528 .unwrap_err(),
529 Error::ForwardedNoFor {
530 header_value: "proto=http".into(),
531 }
532 );
533 assert_eq!(
534 rightmost_forwarded(&headers([(header, "for=unknown")])).unwrap_err(),
535 Error::ForwardedUnknown {
536 header_value: "for=unknown".into(),
537 }
538 );
539 assert_eq!(
540 rightmost_forwarded(&headers([(header, "for=_foo")])).unwrap_err(),
541 Error::ForwardedObfuscated {
542 header_value: "for=_foo".into(),
543 }
544 );
545
546 assert_eq!(
547 rightmost_forwarded(&headers([
548 (header, "proto=http"),
549 (header, format!("for={VALID_IPV4};proto=http").as_ref()),
550 ]))
551 .unwrap(),
552 VALID_IPV4.parse::<IpAddr>().unwrap()
553 );
554 assert_eq!(
555 rightmost_forwarded(&headers([(
556 header,
557 format!("for={VALID_IPV4}:8000").as_ref()
558 ),]))
559 .unwrap(),
560 VALID_IPV4.parse::<IpAddr>().unwrap()
561 );
562
563 assert_eq!(
564 rightmost_forwarded(&headers([(header, format!("for={VALID_IPV6}").as_ref()),]))
565 .unwrap(),
566 VALID_IPV6.parse::<IpAddr>().unwrap()
567 );
568 assert_eq!(
569 rightmost_forwarded(&headers([(
570 header,
571 format!("for=[{VALID_IPV6}]:8000").as_ref()
572 ),]))
573 .unwrap(),
574 VALID_IPV6.parse::<IpAddr>().unwrap()
575 );
576 }
577
578 #[test]
579 fn test_rightmost_x_forwarded_for() {
580 let header = "x-forwarded-for";
581
582 assert_eq!(
583 rightmost_x_forwarded_for(&headers([])).unwrap_err(),
584 Error::AbsentHeader {
585 header_name: HeaderName::from_static(header)
586 }
587 );
588 assert_eq!(
589 rightmost_x_forwarded_for(&headers([(header, "ы")])).unwrap_err(),
590 Error::NonAsciiHeaderValue {
591 header_name: HeaderName::from_static(header)
592 }
593 );
594 assert_eq!(
595 rightmost_x_forwarded_for(&headers([(header, "1.2.3.4,foo")])).unwrap_err(),
596 Error::MalformedHeaderValue {
597 header_name: HeaderName::from_static(header),
598 header_value: "1.2.3.4,foo".into(),
599 }
600 );
601
602 assert_eq!(
603 rightmost_x_forwarded_for(&headers([(header, format!("foo,{VALID_IPV4}").as_ref())]))
604 .unwrap(),
605 VALID_IPV4.parse::<IpAddr>().unwrap()
606 );
607 assert_eq!(
608 rightmost_x_forwarded_for(&headers([(header, VALID_IPV6)])).unwrap(),
609 VALID_IPV6.parse::<IpAddr>().unwrap()
610 );
611 }
612
613 #[test]
614 fn test_true_client_ip() {
615 let header = "true-client-ip";
616
617 assert_eq!(
618 true_client_ip(&headers([])).unwrap_err(),
619 Error::AbsentHeader {
620 header_name: HeaderName::from_static(header)
621 }
622 );
623 assert_eq!(
624 true_client_ip(&headers([(header, "ы")])).unwrap_err(),
625 Error::NonAsciiHeaderValue {
626 header_name: HeaderName::from_static(header)
627 }
628 );
629 assert_eq!(
630 true_client_ip(&headers([(header, "foo")])).unwrap_err(),
631 Error::MalformedHeaderValue {
632 header_name: HeaderName::from_static(header),
633 header_value: "foo".into(),
634 }
635 );
636
637 assert_eq!(
638 true_client_ip(&headers([(header, VALID_IPV4)])).unwrap(),
639 VALID_IPV4.parse::<IpAddr>().unwrap()
640 );
641 assert_eq!(
642 true_client_ip(&headers([(header, VALID_IPV6)])).unwrap(),
643 VALID_IPV6.parse::<IpAddr>().unwrap()
644 );
645 }
646
647 #[test]
648 fn test_x_real_ip() {
649 let header = "x-real-ip";
650
651 assert_eq!(
652 x_real_ip(&headers([])).unwrap_err(),
653 Error::AbsentHeader {
654 header_name: HeaderName::from_static(header)
655 }
656 );
657 assert_eq!(
658 x_real_ip(&headers([(header, "ы")])).unwrap_err(),
659 Error::NonAsciiHeaderValue {
660 header_name: HeaderName::from_static(header)
661 }
662 );
663 assert_eq!(
664 x_real_ip(&headers([(header, "foo")])).unwrap_err(),
665 Error::MalformedHeaderValue {
666 header_name: HeaderName::from_static(header),
667 header_value: "foo".into(),
668 }
669 );
670
671 assert_eq!(
672 x_real_ip(&headers([(header, VALID_IPV4)])).unwrap(),
673 VALID_IPV4.parse::<IpAddr>().unwrap()
674 );
675 assert_eq!(
676 x_real_ip(&headers([(header, VALID_IPV6)])).unwrap(),
677 VALID_IPV6.parse::<IpAddr>().unwrap()
678 );
679 }
680}