1use std::error::Error;
25use std::fmt;
26use std::io;
27use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
28use std::str::FromStr;
29use std::sync::mpsc;
30use std::sync::Arc;
31use std::time::Duration;
32
33use crate::error::{HttpError, Phase};
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
37pub enum AddressClass {
38 Public,
39 Loopback,
40 LinkLocal,
42 Private,
44 Reserved,
47 Unspecified,
48 Multicast,
49}
50
51pub fn classify_ip(ip: IpAddr) -> AddressClass {
53 match ip {
54 IpAddr::V4(v4) => classify_v4(v4),
55 IpAddr::V6(v6) => classify_v6(v6),
56 }
57}
58
59fn classify_v4(ip: Ipv4Addr) -> AddressClass {
60 let [a, b, c, _] = ip.octets();
61 if a == 127 {
62 AddressClass::Loopback
63 } else if a == 0 {
64 AddressClass::Unspecified
65 } else if a == 169 && b == 254 {
66 AddressClass::LinkLocal
67 } else if a == 10
68 || (a == 172 && (16..=31).contains(&b))
69 || (a == 192 && b == 168)
70 || (a == 100 && (64..=127).contains(&b))
71 {
72 AddressClass::Private
73 } else if (224..=239).contains(&a) {
74 AddressClass::Multicast
75 } else if a >= 240
76 || (a == 192 && b == 0 && c == 0)
77 || (a == 192 && b == 0 && c == 2)
78 || (a == 198 && (b == 18 || b == 19))
79 || (a == 198 && b == 51 && c == 100)
80 || (a == 203 && b == 0 && c == 113)
81 {
82 AddressClass::Reserved
83 } else {
84 AddressClass::Public
85 }
86}
87
88fn v4_from_segments(hi: u16, lo: u16) -> Ipv4Addr {
89 Ipv4Addr::new((hi >> 8) as u8, hi as u8, (lo >> 8) as u8, lo as u8)
90}
91
92fn v4_from_octets(o: &[u8; 16], idx: [usize; 4]) -> Ipv4Addr {
93 Ipv4Addr::new(o[idx[0]], o[idx[1]], o[idx[2]], o[idx[3]])
94}
95
96pub fn embedded_v4s(ip: IpAddr) -> Vec<Ipv4Addr> {
102 let IpAddr::V6(v6) = ip else {
103 return Vec::new();
104 };
105 if v6.is_loopback() || v6.is_unspecified() {
106 return Vec::new();
107 }
108 let s = v6.segments();
109 if let Some(v4) = v6.to_ipv4_mapped() {
110 return vec![v4];
111 }
112 if s[..6] == [0, 0, 0, 0, 0, 0] || s[..6] == [0x64, 0xff9b, 0, 0, 0, 0] {
113 return vec![v4_from_segments(s[6], s[7])];
114 }
115 if s[0] == 0x2002 {
116 return vec![v4_from_segments(s[1], s[2])];
117 }
118 if s[0] == 0x64 && s[1] == 0xff9b && s[2] == 1 {
119 let o = v6.octets();
120 return vec![
121 v4_from_octets(&o, [12, 13, 14, 15]),
122 v4_from_octets(&o, [9, 10, 11, 12]),
123 v4_from_octets(&o, [7, 9, 10, 11]),
124 v4_from_octets(&o, [6, 7, 9, 10]),
125 ];
126 }
127 Vec::new()
128}
129
130pub fn embedded_v4(ip: IpAddr) -> Option<Ipv4Addr> {
133 let all = embedded_v4s(ip);
134 all.iter()
135 .copied()
136 .find(|v4| classify_v4(*v4) != AddressClass::Public)
137 .or_else(|| all.first().copied())
138}
139
140fn classify_v6(v6: Ipv6Addr) -> AddressClass {
141 let s = v6.segments();
142 if v6.is_loopback() {
143 return AddressClass::Loopback;
144 }
145 if v6.is_unspecified() {
146 return AddressClass::Unspecified;
147 }
148 let embedded = embedded_v4s(IpAddr::V6(v6));
149 if !embedded.is_empty() {
150 return embedded
151 .into_iter()
152 .map(classify_v4)
153 .find(|c| *c != AddressClass::Public)
154 .unwrap_or(AddressClass::Public);
155 }
156 if v6.is_multicast() {
157 AddressClass::Multicast
158 } else if s[0] & 0xffc0 == 0xfe80 {
159 AddressClass::LinkLocal
160 } else if s[0] & 0xfe00 == 0xfc00 || s[0] & 0xffc0 == 0xfec0 {
161 AddressClass::Private
162 } else if (s[0] == 0x2001 && s[1] == 0x0db8) || (s[0] == 0x2001 && s[1] == 0) {
163 AddressClass::Reserved
164 } else {
165 AddressClass::Public
166 }
167}
168
169fn hard_block(ip: IpAddr) -> Option<&'static str> {
175 fn one(ip: IpAddr) -> Option<&'static str> {
176 if classify_ip(ip) == AddressClass::LinkLocal {
177 return Some("is link-local (cloud metadata range)");
178 }
179 match ip {
180 IpAddr::V4(v4)
181 if [[100, 100, 100, 200], [168, 63, 129, 16], [192, 0, 0, 192]]
182 .contains(&v4.octets()) =>
183 {
184 Some("is a cloud metadata address")
185 }
186 IpAddr::V6(v6) if v6.segments()[0] == 0xfd00 && v6.segments()[1] == 0x0ec2 => {
187 Some("is in the AWS IPv6 metadata block fd00:ec2::/32")
188 }
189 _ => None,
190 }
191 }
192 one(ip).or_else(|| {
193 embedded_v4s(ip)
194 .into_iter()
195 .find_map(|v4| one(IpAddr::V4(v4)))
196 })
197}
198
199#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
201pub enum HostIpKind {
202 Ipv4DottedQuad,
204 Ipv4Legacy,
207 Ipv6,
209}
210
211#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
213pub struct ParsedHostIp {
214 pub addr: IpAddr,
215 pub kind: HostIpKind,
216 pub is_canonical: bool,
219}
220
221pub fn parse_host_ip(host: &str) -> Option<ParsedHostIp> {
245 let trimmed = host.trim_matches(['[', ']']);
246 if let Ok(ip) = trimmed.parse::<IpAddr>() {
247 let (kind, is_canonical) = match ip {
248 IpAddr::V4(_) => (HostIpKind::Ipv4DottedQuad, true),
249 IpAddr::V6(_) => (HostIpKind::Ipv6, true),
250 };
251 return Some(ParsedHostIp {
252 addr: ip,
253 kind,
254 is_canonical,
255 });
256 }
257 let (body, trailing_dot) = match trimmed.strip_suffix('.') {
258 Some(b) => (b, true),
259 None => (trimmed, false),
260 };
261 let v4 = parse_legacy_ipv4(body)?;
262 let canonical = !trailing_dot && body.eq_ignore_ascii_case(&v4.to_string());
263 Some(ParsedHostIp {
264 addr: IpAddr::V4(v4),
265 kind: if canonical {
266 HostIpKind::Ipv4DottedQuad
267 } else {
268 HostIpKind::Ipv4Legacy
269 },
270 is_canonical: canonical,
271 })
272}
273
274pub fn parse_legacy_ipv4(host: &str) -> Option<Ipv4Addr> {
278 let parts: Vec<&str> = host.split('.').collect();
279 if parts.is_empty() || parts.len() > 4 {
280 return None;
281 }
282 let mut values = Vec::with_capacity(parts.len());
283 for part in &parts {
284 let (digits, radix) =
285 if let Some(hex) = part.strip_prefix("0x").or_else(|| part.strip_prefix("0X")) {
286 (hex, 16)
287 } else if part.len() > 1 && part.starts_with('0') {
288 (&part[1..], 8)
289 } else {
290 (*part, 10)
291 };
292 if digits.is_empty() || !digits.chars().all(|c| c.is_digit(radix)) {
293 return None;
294 }
295 values.push(u64::from_str_radix(digits, radix).ok()?);
296 }
297 let (last, init) = values.split_last()?;
298 if init.iter().any(|v| *v > 255) {
299 return None;
300 }
301 let tail_bytes = 4 - init.len();
302 if *last >= 1u64 << (8 * tail_bytes) {
303 return None;
304 }
305 let mut addr: u32 = 0;
306 for v in init {
307 addr = (addr << 8) | *v as u32;
308 }
309 addr = addr.checked_shl(8 * tail_bytes as u32).unwrap_or(0) | *last as u32;
310 Some(Ipv4Addr::from(addr))
311}
312
313fn is_metadata_host(host: &str) -> bool {
315 matches!(
316 host,
317 "metadata" | "metadata.google.internal" | "metadata.goog" | "instance-data"
318 ) || host.ends_with(".metadata.google.internal")
319}
320
321pub(crate) fn normalize_host(host: &str) -> String {
323 let h = host.trim_matches(['[', ']']).to_ascii_lowercase();
324 h.strip_suffix('.').map(str::to_string).unwrap_or(h)
325}
326
327#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
329pub struct IpNet {
330 addr: IpAddr,
331 prefix: u8,
332}
333
334#[derive(Clone, Debug, Eq, PartialEq)]
336pub struct ParseIpNetError(pub String);
337
338impl fmt::Display for ParseIpNetError {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 write!(f, "invalid CIDR range: {}", self.0)
341 }
342}
343impl Error for ParseIpNetError {}
344
345impl IpNet {
346 pub fn new(addr: IpAddr, prefix: u8) -> Result<Self, ParseIpNetError> {
348 let max = if addr.is_ipv4() { 32 } else { 128 };
349 if prefix > max {
350 return Err(ParseIpNetError(format!("prefix {prefix} exceeds {max}")));
351 }
352 Ok(Self { addr, prefix })
353 }
354
355 pub fn addr(&self) -> IpAddr {
356 self.addr
357 }
358
359 pub fn prefix(&self) -> u8 {
360 self.prefix
361 }
362
363 pub fn contains(&self, ip: &IpAddr) -> bool {
366 match (self.addr, ip) {
367 (IpAddr::V4(net), IpAddr::V4(ip)) => {
368 let mask = if self.prefix == 0 {
369 0
370 } else {
371 u32::MAX << (32 - u32::from(self.prefix))
372 };
373 u32::from(net) & mask == u32::from(*ip) & mask
374 }
375 (IpAddr::V6(net), IpAddr::V6(ip)) => {
376 let mask = if self.prefix == 0 {
377 0
378 } else {
379 u128::MAX << (128 - u32::from(self.prefix))
380 };
381 u128::from(net) & mask == u128::from(*ip) & mask
382 }
383 _ => false,
384 }
385 }
386}
387
388impl FromStr for IpNet {
389 type Err = ParseIpNetError;
390
391 fn from_str(s: &str) -> Result<Self, Self::Err> {
393 let (addr, prefix) = match s.split_once('/') {
394 Some((a, p)) => (a, Some(p)),
395 None => (s, None),
396 };
397 let addr: IpAddr = addr
398 .trim()
399 .parse()
400 .map_err(|_| ParseIpNetError(s.to_string()))?;
401 let prefix = match prefix {
402 Some(p) => p
403 .trim()
404 .parse::<u8>()
405 .map_err(|_| ParseIpNetError(s.to_string()))?,
406 None => {
407 if addr.is_ipv4() {
408 32
409 } else {
410 128
411 }
412 }
413 };
414 Self::new(addr, prefix)
415 }
416}
417
418impl fmt::Display for IpNet {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 write!(f, "{}/{}", self.addr, self.prefix)
421 }
422}
423
424#[derive(Clone, Debug, Eq, PartialEq)]
426pub struct AddressDenial {
427 pub host: String,
428 pub addr: Option<IpAddr>,
430 pub class: Option<AddressClass>,
431 pub reason: String,
432}
433
434impl fmt::Display for AddressDenial {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 write!(
437 f,
438 "address denied for host '{}': {}",
439 self.host, self.reason
440 )
441 }
442}
443impl Error for AddressDenial {}
444
445type DenyHook = Arc<dyn Fn(&IpAddr) -> Option<String> + Send + Sync>;
446type AllowHook = Arc<dyn Fn(&IpAddr) -> bool + Send + Sync>;
447type ResolverHook = Arc<dyn Fn(&str) -> Vec<IpAddr> + Send + Sync>;
448
449#[derive(Clone)]
450enum Mode {
451 PublicWeb,
452 FirstParty(Vec<String>),
453}
454
455#[derive(Clone)]
456struct AllowRule {
457 hosts: Vec<String>,
458 pred: AllowHook,
459}
460
461#[derive(Clone)]
468pub struct AddressPolicy {
469 mode: Mode,
470 deny_ranges: Vec<IpNet>,
471 deny_if: Vec<DenyHook>,
472 allow: Vec<AllowRule>,
473 private_ok: Vec<String>,
475 allow_downgrade: bool,
477 allow_legacy_ipv4: bool,
479 resolver: Option<ResolverHook>,
480}
481
482impl Default for AddressPolicy {
483 fn default() -> Self {
484 Self::public_web()
485 }
486}
487
488impl fmt::Debug for AddressPolicy {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 let mut d = f.debug_struct("AddressPolicy");
491 match &self.mode {
492 Mode::PublicWeb => d.field("mode", &"public-web"),
493 Mode::FirstParty(hosts) => d.field("mode", &format_args!("first-party {hosts:?}")),
494 };
495 d.field("deny_ranges", &self.deny_ranges)
496 .field("deny_if", &self.deny_if.len())
497 .field(
498 "allow_if_hosts",
499 &self
500 .allow
501 .iter()
502 .flat_map(|r| r.hosts.iter())
503 .collect::<Vec<_>>(),
504 )
505 .field("private_resolution_hosts", &self.private_ok)
506 .field("allow_insecure_redirects", &self.allow_downgrade)
507 .field("allow_legacy_ipv4", &self.allow_legacy_ipv4)
508 .field("custom_resolver", &self.resolver.is_some())
509 .finish()
510 }
511}
512
513impl AddressPolicy {
514 pub fn public_web() -> Self {
516 Self {
517 mode: Mode::PublicWeb,
518 deny_ranges: Vec::new(),
519 deny_if: Vec::new(),
520 allow: Vec::new(),
521 private_ok: Vec::new(),
522 allow_downgrade: false,
523 allow_legacy_ipv4: false,
524 resolver: None,
525 }
526 }
527
528 pub fn first_party(hosts: Vec<String>) -> Self {
535 Self {
536 mode: Mode::FirstParty(hosts.iter().map(|h| normalize_host(h)).collect()),
537 ..Self::public_web()
538 }
539 }
540
541 pub fn is_first_party(&self) -> bool {
543 matches!(self.mode, Mode::FirstParty(_))
544 }
545
546 pub fn allow_legacy_ipv4(mut self) -> Self {
553 self.allow_legacy_ipv4 = true;
554 self
555 }
556
557 pub fn allow_insecure_redirects(mut self) -> Self {
560 self.allow_downgrade = true;
561 self
562 }
563
564 pub fn allows_redirect(&self, from_scheme: &str, to_scheme: &str) -> bool {
568 self.allow_downgrade
569 || !from_scheme.eq_ignore_ascii_case("https")
570 || !to_scheme.eq_ignore_ascii_case("http")
571 }
572
573 pub(crate) fn check_redirect_scheme(
575 &self,
576 from_scheme: &str,
577 to_scheme: &str,
578 host: &str,
579 ) -> Result<(), AddressDenial> {
580 if self.allows_redirect(from_scheme, to_scheme) {
581 return Ok(());
582 }
583 Err(denial(
584 &normalize_host(host),
585 None,
586 None,
587 "redirect from HTTPS to HTTP refused",
588 ))
589 }
590
591 pub fn allow_private_resolution(mut self, host: impl Into<String>) -> Self {
596 self.private_ok.push(normalize_host(&host.into()));
597 self
598 }
599
600 pub fn deny_ranges(mut self, ranges: Vec<IpNet>) -> Self {
603 self.deny_ranges.extend(ranges);
604 self
605 }
606
607 pub fn deny_if<F>(mut self, f: F) -> Self
611 where
612 F: Fn(&IpAddr) -> Option<String> + Send + Sync + 'static,
613 {
614 self.deny_if.push(Arc::new(f));
615 self
616 }
617
618 pub fn allow_if<F>(mut self, hosts: Vec<String>, pred: F) -> Self
623 where
624 F: Fn(&IpAddr) -> bool + Send + Sync + 'static,
625 {
626 self.allow.push(AllowRule {
627 hosts: hosts.iter().map(|h| normalize_host(h)).collect(),
628 pred: Arc::new(pred),
629 });
630 self
631 }
632
633 pub fn with_resolver<F>(mut self, f: F) -> Self
636 where
637 F: Fn(&str) -> Vec<IpAddr> + Send + Sync + 'static,
638 {
639 self.resolver = Some(Arc::new(f));
640 self
641 }
642
643 pub fn resolve_vetted(&self, host: &str, port: u16) -> Result<Vec<SocketAddr>, HttpError> {
659 self.admit("https", host)?;
660 let host = normalize_host(host);
661 let addrs = match self.fixed_addrs(&host) {
662 Some(addrs) => addrs,
663 None => match self.lookup_blocking(&host, port, Some(Duration::from_secs(10))) {
664 Ok(addrs) => addrs,
665 Err(e) if e.kind() == io::ErrorKind::TimedOut => {
666 return Err(HttpError::timeout("name lookup", Phase::Connect))
667 }
668 Err(_) => return Err(HttpError::transport("host not found", Phase::Connect)),
669 },
670 };
671 let vetted = self.vet(&host, addrs)?;
672 Ok(vetted
673 .into_iter()
674 .map(|ip| SocketAddr::new(ip, port))
675 .collect())
676 }
677
678 fn host_listed(&self, host: &str) -> bool {
680 self.allow
681 .iter()
682 .any(|rule| rule.hosts.iter().any(|h| h == host))
683 }
684
685 pub(crate) fn vet_via_proxy(&self, host: &str) -> Result<(), AddressDenial> {
691 let host = normalize_host(host);
692 if let Some(addrs) = self.fixed_addrs(&host) {
693 return self.vet(&host, addrs).map(|_| ());
694 }
695 if matches!(self.mode, Mode::FirstParty(_)) || self.host_listed(&host) {
696 return Ok(());
697 }
698 let reason = if host.ends_with(".local") {
699 Some("mDNS name is refused through a proxy")
700 } else if host.ends_with(".internal") {
701 Some("internal name is refused through a proxy")
702 } else if !host.contains('.') {
703 Some("single-label name is refused through a proxy")
704 } else {
705 None
706 };
707 match reason {
708 Some(r) => Err(denial(
709 &host,
710 None,
711 Some(AddressClass::Private),
712 format!("{r} (through a proxy only names and literals can be checked)"),
713 )),
714 None => Ok(()),
715 }
716 }
717
718 pub(crate) fn admit(&self, scheme: &str, host: &str) -> Result<(), AddressDenial> {
724 if !self.allow_legacy_ipv4 {
725 if let Some(parsed) = parse_host_ip(host) {
726 if !parsed.is_canonical {
727 return Err(denial(
728 &normalize_host(host),
729 Some(parsed.addr),
730 Some(classify_ip(parsed.addr)),
731 format!(
732 "non-canonical IPv4 spelling is refused (it reads as {})",
733 parsed.addr
734 ),
735 ));
736 }
737 }
738 }
739 let host = normalize_host(host);
740 if is_metadata_host(&host) {
741 return Err(denial(
742 &host,
743 None,
744 Some(AddressClass::LinkLocal),
745 "host is a cloud metadata endpoint",
746 ));
747 }
748 if let Mode::FirstParty(hosts) = &self.mode {
749 if !scheme.eq_ignore_ascii_case("https") {
750 return Err(denial(
751 &host,
752 None,
753 None,
754 "first-party client allows HTTPS only",
755 ));
756 }
757 if !hosts.contains(&host) {
758 return Err(denial(
759 &host,
760 None,
761 None,
762 "host is not in the first-party host list",
763 ));
764 }
765 }
766 Ok(())
767 }
768
769 pub(crate) fn fixed_addrs(&self, host: &str) -> Option<Vec<IpAddr>> {
771 let host = normalize_host(host);
772 if let Some(parsed) = parse_host_ip(&host) {
773 return Some(vec![parsed.addr]);
774 }
775 if host == "localhost" || host.ends_with(".localhost") {
776 return Some(vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]);
777 }
778 None
779 }
780
781 pub(crate) fn lookup_blocking(
784 &self,
785 host: &str,
786 port: u16,
787 limit: Option<Duration>,
788 ) -> io::Result<Vec<IpAddr>> {
789 let host = normalize_host(host);
790 let hook = self.resolver.clone();
791 let run = move || -> io::Result<Vec<IpAddr>> {
792 match hook {
793 Some(hook) => Ok(hook(&host)),
794 None => Ok((host, port).to_socket_addrs()?.map(|a| a.ip()).collect()),
795 }
796 };
797 match limit {
798 None => run(),
799 Some(limit) => {
800 let (tx, rx) = mpsc::sync_channel(1);
801 std::thread::spawn(move || tx.send(run()).ok());
802 match rx.recv_timeout(limit) {
803 Ok(r) => r,
804 Err(_) => Err(io::Error::new(
805 io::ErrorKind::TimedOut,
806 "name lookup timed out",
807 )),
808 }
809 }
810 }
811 }
812
813 #[cfg(feature = "async")]
815 pub(crate) fn resolver_hook(&self) -> Option<ResolverHook> {
816 self.resolver.clone()
817 }
818
819 pub(crate) fn vet(&self, host: &str, addrs: Vec<IpAddr>) -> Result<Vec<IpAddr>, AddressDenial> {
822 self.vet_with(host, addrs)
823 }
824
825 fn vet_with(&self, host: &str, addrs: Vec<IpAddr>) -> Result<Vec<IpAddr>, AddressDenial> {
826 let host = normalize_host(host);
827 if addrs.is_empty() {
828 return Err(denial(
829 &host,
830 None,
831 None,
832 "host did not resolve to any address",
833 ));
834 }
835 let literal = parse_host_ip(&host).is_some();
836 for &ip in &addrs {
837 let class = classify_ip(ip);
838 if let Some(why) = hard_block(ip) {
839 return Err(denial(
840 &host,
841 Some(ip),
842 Some(class),
843 format!("{} {why}", subject(literal, ip)),
844 ));
845 }
846 let forms: Vec<IpAddr> = std::iter::once(ip)
847 .chain(embedded_v4s(ip).into_iter().map(IpAddr::V4))
848 .collect();
849 for form in forms.iter() {
850 if let Some(net) = self.deny_ranges.iter().find(|n| n.contains(form)) {
851 return Err(denial(
852 &host,
853 Some(ip),
854 Some(class),
855 format!("{} is in denied range {net}", subject(literal, ip)),
856 ));
857 }
858 for hook in &self.deny_if {
859 if let Some(text) = hook(form) {
860 return Err(denial(&host, Some(ip), Some(class), text));
861 }
862 }
863 }
864 let private_ok =
865 matches!(self.mode, Mode::FirstParty(_)) && self.private_ok.contains(&host);
866 if class != AddressClass::Public && !private_ok && !self.allowed(&host, &ip) {
867 let reason = if literal {
868 format!("address {ip} is {class:?}")
869 } else {
870 format!("host resolves to {class:?} address {ip}")
871 };
872 return Err(denial(&host, Some(ip), Some(class), reason));
873 }
874 }
875 Ok(addrs)
876 }
877
878 fn allowed(&self, host: &str, ip: &IpAddr) -> bool {
879 self.allow
880 .iter()
881 .any(|rule| rule.hosts.iter().any(|h| h == host) && (rule.pred)(ip))
882 }
883}
884
885fn subject(literal: bool, ip: IpAddr) -> String {
886 if literal {
887 format!("address {ip}")
888 } else {
889 format!("resolved address {ip}")
890 }
891}
892
893fn denial(
894 host: &str,
895 addr: Option<IpAddr>,
896 class: Option<AddressClass>,
897 reason: impl Into<String>,
898) -> AddressDenial {
899 AddressDenial {
900 host: host.to_string(),
901 addr,
902 class,
903 reason: reason.into(),
904 }
905}
906
907pub(crate) fn denial_in_chain(err: &(dyn Error + 'static)) -> Option<AddressDenial> {
909 let mut current: Option<&(dyn Error + 'static)> = Some(err);
910 while let Some(e) = current {
911 if let Some(d) = e.downcast_ref::<AddressDenial>() {
912 return Some(d.clone());
913 }
914 current = e.source();
915 }
916 None
917}
918
919thread_local! {
920 static VIA_PROXY: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
922}
923
924pub(crate) struct ViaProxy(bool);
929
930impl ViaProxy {
931 pub(crate) fn enter(on: bool) -> Self {
932 Self(VIA_PROXY.with(|c| c.replace(on)))
933 }
934}
935
936impl Drop for ViaProxy {
937 fn drop(&mut self) {
938 VIA_PROXY.with(|c| c.set(self.0));
939 }
940}
941
942const MAX_RESOLVED_ADDRS: usize = 16;
944
945#[derive(Debug)]
950pub(crate) struct VettingResolver {
951 pub(crate) policy: Arc<AddressPolicy>,
952 pub(crate) proxy: Option<(String, u16)>,
956 pub(crate) lookup_limit: Duration,
958}
959
960impl ureq::unversioned::resolver::Resolver for VettingResolver {
961 fn resolve(
962 &self,
963 uri: &ureq::http::Uri,
964 _config: &ureq::config::Config,
965 timeout: ureq::unversioned::transport::NextTimeout,
966 ) -> Result<ureq::unversioned::resolver::ResolvedSocketAddrs, ureq::Error> {
967 let scheme = uri.scheme_str().unwrap_or("").to_ascii_lowercase();
968 let default_port = match scheme.as_str() {
969 "http" => 80,
970 "https" => 443,
971 _ => return Err(ureq::Error::BadUri("unsupported scheme".into())),
972 };
973 let raw_host = uri
974 .host()
975 .ok_or_else(|| ureq::Error::BadUri("missing host".into()))?;
976 let host = normalize_host(raw_host);
977 let port = uri.port_u16().unwrap_or(default_port);
978 let is_proxy = VIA_PROXY.with(|c| c.get())
979 && self
980 .proxy
981 .as_ref()
982 .is_some_and(|(h, p)| *h == host && *p == port);
983 if !is_proxy && !matches!(scheme.as_str(), "http" | "https") {
984 return Err(ureq::Error::BadUri("unsupported scheme".into()));
985 }
986 if !is_proxy {
987 self.policy.admit(&scheme, raw_host).map_err(to_ureq)?;
988 }
989 let addrs = match self.policy.fixed_addrs(&host) {
990 Some(addrs) => addrs,
991 None => {
992 let limit = if timeout.after.is_not_happening() {
993 self.lookup_limit
994 } else {
995 (*timeout.after).min(self.lookup_limit)
996 };
997 match self.policy.lookup_blocking(&host, port, Some(limit)) {
998 Ok(addrs) => addrs,
999 Err(e) if e.kind() == io::ErrorKind::TimedOut => {
1000 return Err(ureq::Error::Timeout(timeout.reason))
1001 }
1002 Err(_) => return Err(ureq::Error::HostNotFound),
1003 }
1004 }
1005 };
1006 let vetted = if is_proxy {
1007 if addrs.is_empty() {
1008 return Err(ureq::Error::HostNotFound);
1009 }
1010 addrs
1011 } else {
1012 self.policy.vet(&host, addrs).map_err(to_ureq)?
1013 };
1014 let mut out = ureq::unversioned::resolver::Resolver::empty(self);
1015 for ip in vetted.into_iter().take(MAX_RESOLVED_ADDRS) {
1016 out.push(std::net::SocketAddr::new(ip, port));
1017 }
1018 Ok(out)
1019 }
1020}
1021
1022fn to_ureq(d: AddressDenial) -> ureq::Error {
1023 ureq::Error::Io(io::Error::new(io::ErrorKind::PermissionDenied, d))
1024}
1025
1026#[cfg(test)]
1027mod tests {
1028 use super::*;
1029
1030 fn ip(s: &str) -> IpAddr {
1031 s.parse().unwrap()
1032 }
1033
1034 #[test]
1035 fn ipnet_parses_and_matches() {
1036 let net: IpNet = "10.0.0.0/8".parse().unwrap();
1037 assert!(net.contains(&ip("10.255.0.1")));
1038 assert!(!net.contains(&ip("11.0.0.1")));
1039 assert!(!net.contains(&ip("::1")));
1040 let v6: IpNet = "fd00::/8".parse().unwrap();
1041 assert!(v6.contains(&ip("fd12::1")));
1042 assert!(!v6.contains(&ip("fe80::1")));
1043 let host: IpNet = "203.0.113.9".parse().unwrap();
1044 assert_eq!(host.prefix(), 32);
1045 assert!(host.contains(&ip("203.0.113.9")) && !host.contains(&ip("203.0.113.8")));
1046 assert!("10.0.0.0/33".parse::<IpNet>().is_err());
1047 assert!("nonsense".parse::<IpNet>().is_err());
1048 let all: IpNet = "0.0.0.0/0".parse().unwrap();
1049 assert!(all.contains(&ip("8.8.8.8")));
1050 }
1051
1052 #[test]
1053 fn embedded_forms_extract_the_v4() {
1054 assert_eq!(
1055 embedded_v4(ip("::ffff:10.1.2.3")),
1056 Some("10.1.2.3".parse().unwrap())
1057 );
1058 assert_eq!(
1059 embedded_v4(ip("::10.1.2.3")),
1060 Some("10.1.2.3".parse().unwrap())
1061 );
1062 assert_eq!(
1063 embedded_v4(ip("64:ff9b::a00:1")),
1064 Some("10.0.0.1".parse().unwrap())
1065 );
1066 assert_eq!(
1067 embedded_v4(ip("2002:0a00:0001::1")),
1068 Some("10.0.0.1".parse().unwrap())
1069 );
1070 assert_eq!(embedded_v4(ip("::1")), None);
1071 assert_eq!(embedded_v4(ip("::")), None);
1072 assert_eq!(embedded_v4(ip("2606:4700::1")), None);
1073 assert_eq!(embedded_v4(ip("1.2.3.4")), None);
1074 }
1075
1076 #[test]
1077 fn host_ip_parsing_reports_the_spelling() {
1078 let k = |h: &str| parse_host_ip(h).map(|p| (p.addr, p.kind, p.is_canonical));
1079 let v4 = |s: &str| IpAddr::V4(s.parse().unwrap());
1080 assert_eq!(
1081 k("1.1.1.1"),
1082 Some((v4("1.1.1.1"), HostIpKind::Ipv4DottedQuad, true))
1083 );
1084 for (spelling, want) in [
1085 ("127.1", "127.0.0.1"),
1086 ("10.1.2", "10.1.0.2"),
1087 ("0177.0.0.1", "127.0.0.1"),
1088 ("0x7f.0.0.1", "127.0.0.1"),
1089 ("0X7F000001", "127.0.0.1"),
1090 ("2130706433", "127.0.0.1"),
1091 ("017700000001", "127.0.0.1"),
1092 ("0251.0376.0251.0376", "169.254.169.254"),
1093 ("1.1.1.1.", "1.1.1.1"),
1094 ("0", "0.0.0.0"),
1095 ] {
1096 assert_eq!(
1097 k(spelling),
1098 Some((v4(want), HostIpKind::Ipv4Legacy, false)),
1099 "{spelling}"
1100 );
1101 }
1102 for v6 in [
1103 "::1",
1104 "[::1]",
1105 "::ffff:127.0.0.1",
1106 "[::ffff:7f00:1]",
1107 "64:ff9b::a00:1",
1108 ] {
1109 let p = parse_host_ip(v6).unwrap();
1110 assert!(p.is_canonical && p.kind == HostIpKind::Ipv6, "{v6}");
1111 }
1112 for not_ip in [
1113 "example.com",
1114 "1.2.3.4.5",
1115 "256.1.1.1",
1116 "1.2.3.256",
1117 "4294967296",
1118 "08.1",
1119 "0x",
1120 "1..2",
1121 "",
1122 ".",
1123 "1.2.3.",
1124 "a.b.c.d",
1125 "127.0.0.1x",
1126 ] {
1127 let got = parse_host_ip(not_ip);
1128 if not_ip == "1.2.3." {
1130 assert!(got.is_some());
1131 } else {
1132 assert!(got.is_none(), "{not_ip}: {got:?}");
1133 }
1134 }
1135 assert_eq!(
1136 parse_legacy_ipv4("0x7f.1"),
1137 Some("127.0.0.1".parse().unwrap())
1138 );
1139 assert_eq!(parse_legacy_ipv4("1.1.1.1."), None);
1140 assert_eq!(parse_legacy_ipv4("::1"), None);
1141 }
1142
1143 #[test]
1144 fn non_canonical_spellings_are_refused_by_default_and_allowed_on_request() {
1145 let strict = AddressPolicy::default();
1146 for h in ["0177.0.0.1", "0x7f.1", "2130706433", "127.1", "1.1.1.1."] {
1147 let d = strict.admit("http", h).unwrap_err();
1148 assert!(d.reason.contains("non-canonical"), "{h}: {d}");
1149 assert!(d.addr.is_some(), "{h}");
1150 }
1151 for h in [
1152 "1.1.1.1",
1153 "::ffff:1.1.1.1",
1154 "[::1]",
1155 "example.com",
1156 "example.com.",
1157 ] {
1158 assert!(strict.admit("http", h).is_ok(), "{h}");
1159 }
1160 let lax = AddressPolicy::default().allow_legacy_ipv4();
1161 assert!(lax.admit("http", "127.1").is_ok());
1162 assert!(lax.vet("127.1", lax.fixed_addrs("127.1").unwrap()).is_err());
1164 }
1165
1166 #[test]
1167 fn host_normalization() {
1168 assert_eq!(normalize_host("[::1]"), "::1");
1169 assert_eq!(normalize_host("Example.COM."), "example.com");
1170 }
1171
1172 #[test]
1173 fn vet_denies_empty_resolution() {
1174 let d = AddressPolicy::default()
1175 .vet("nowhere.test", vec![])
1176 .unwrap_err();
1177 assert_eq!(d.addr, None);
1178 assert!(d.reason.contains("did not resolve"), "{d}");
1179 }
1180}