1use crate::StreamPrefs;
5use crate::err::ErrorDetail;
6use std::fmt::Display;
7use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
8use std::str::FromStr;
9use thiserror::Error;
10use tor_basic_utils::StrExt;
11use tor_error::{ErrorKind, HasKind};
12
13#[cfg(feature = "onion-service-client")]
14use tor_hscrypto::pk::{HSID_ONION_SUFFIX, HsId};
15
16#[cfg(not(feature = "onion-service-client"))]
18pub(crate) mod hs_dummy {
19 use super::*;
20 use tor_error::internal;
21 use void::Void;
22
23 #[derive(Debug, Clone)]
25 pub(crate) struct HsId(pub(crate) Void);
26
27 impl PartialEq for HsId {
28 fn eq(&self, _other: &Self) -> bool {
29 void::unreachable(self.0)
30 }
31 }
32 impl Eq for HsId {}
33
34 pub(crate) const HSID_ONION_SUFFIX: &str = ".onion";
36
37 impl FromStr for HsId {
39 type Err = ErrorDetail;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 if !s.ends_with(HSID_ONION_SUFFIX) {
43 return Err(internal!("non-.onion passed to dummy HsId::from_str").into());
44 }
45
46 Err(ErrorDetail::OnionAddressNotSupported)
47 }
48 }
49}
50#[cfg(not(feature = "onion-service-client"))]
51use hs_dummy::*;
52
53pub trait IntoTorAddr {
78 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError>;
81}
82
83pub trait DangerouslyIntoTorAddr {
95 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError>;
101}
102
103#[derive(Debug, Clone, Eq, PartialEq)]
160pub struct TorAddr {
161 host: Host,
163 port: u16,
165}
166
167#[derive(Debug, PartialEq, Eq)]
173pub(crate) enum StreamInstructions {
174 Exit {
176 hostname: String,
178 port: u16,
180 },
181 Hs {
185 hsid: HsId,
187 hostname: String,
189 port: u16,
191 },
192}
193
194#[derive(PartialEq, Eq, Debug)]
196pub(crate) enum ResolveInstructions {
197 Exit(String),
199 Return(Vec<IpAddr>),
201}
202
203impl TorAddr {
204 fn new(host: Host, port: u16) -> Result<Self, TorAddrError> {
207 if port == 0 {
208 Err(TorAddrError::BadPort)
209 } else {
210 Ok(TorAddr { host, port })
211 }
212 }
213
214 pub fn from<A: IntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
217 addr.into_tor_addr()
218 }
219 pub fn dangerously_from<A: DangerouslyIntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
226 addr.into_tor_addr_dangerously()
227 }
228
229 pub fn is_ip_address(&self) -> bool {
231 matches!(&self.host, Host::Ip(_))
232 }
233
234 pub fn as_ip_address(&self) -> Option<&IpAddr> {
236 match &self.host {
237 Host::Ip(a) => Some(a),
238 _ => None,
239 }
240 }
241
242 pub(crate) fn into_stream_instructions(
244 self,
245 cfg: &crate::config::ClientAddrConfig,
246 prefs: &StreamPrefs,
247 ) -> Result<StreamInstructions, ErrorDetail> {
248 self.enforce_config(cfg, prefs)?;
249
250 let port = self.port;
251 Ok(match self.host {
252 Host::Hostname(hostname) => StreamInstructions::Exit { hostname, port },
253 Host::Ip(ip) => StreamInstructions::Exit {
254 hostname: ip.to_string(),
255 port,
256 },
257 Host::Onion(onion) => {
258 let rhs = onion
260 .rmatch_indices('.')
261 .nth(1)
262 .map(|(i, _)| i + 1)
263 .unwrap_or(0);
264 let rhs = onion
265 .get(rhs..)
266 .expect("character index was not a valid index!?");
267 let hsid = rhs.parse()?;
268 StreamInstructions::Hs {
269 hsid,
270 port,
271 hostname: onion,
272 }
273 }
274 })
275 }
276
277 pub(crate) fn into_resolve_instructions(
279 self,
280 cfg: &crate::config::ClientAddrConfig,
281 prefs: &StreamPrefs,
282 ) -> Result<ResolveInstructions, ErrorDetail> {
283 let enforce_config_result = self.enforce_config(cfg, prefs);
288
289 let instructions = (move || {
292 Ok(match self.host {
293 Host::Hostname(hostname) => ResolveInstructions::Exit(hostname),
294 Host::Ip(ip) => ResolveInstructions::Return(vec![ip]),
295 Host::Onion(_) => return Err(ErrorDetail::OnionAddressResolveRequest),
296 })
297 })()?;
298
299 let () = enforce_config_result?;
300
301 Ok(instructions)
302 }
303
304 fn is_globally_reachable_unicast(&self) -> bool {
311 self.host.is_globally_reachable_unicast()
312 }
313
314 fn enforce_config(
317 &self,
318 cfg: &crate::config::ClientAddrConfig,
319 #[allow(unused_variables)] prefs: &StreamPrefs,
321 ) -> Result<(), ErrorDetail> {
322 let allow_local_addrs = prefs
323 .connect_to_local_addrs
324 .as_bool()
325 .unwrap_or(cfg.allow_local_addrs);
326
327 if !allow_local_addrs && !self.is_globally_reachable_unicast() {
328 return Err(ErrorDetail::LocalAddress);
329 }
330
331 if let Host::Hostname(addr) = &self.host {
332 if !is_valid_hostname(addr) {
333 return Err(ErrorDetail::InvalidHostname);
335 }
336 if addr.ends_with_ignore_ascii_case(HSID_ONION_SUFFIX) {
337 return Err(ErrorDetail::OnionAddressNotSupported);
339 }
340 }
341
342 if let Host::Onion(_name) = &self.host {
343 cfg_if::cfg_if! {
344 if #[cfg(feature = "onion-service-client")] {
345 if !prefs.connect_to_onion_services.as_bool().unwrap_or(cfg.allow_onion_addrs) {
346 return Err(ErrorDetail::OnionAddressDisabled);
347 }
348 } else {
349 return Err(ErrorDetail::OnionAddressNotSupported);
350 }
351 }
352 }
353
354 Ok(())
355 }
356}
357
358impl std::fmt::Display for TorAddr {
359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360 match self.host {
361 Host::Ip(IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.port),
362 _ => write!(f, "{}:{}", self.host, self.port),
363 }
364 }
365}
366
367#[derive(Debug, Error, Clone, Eq, PartialEq)]
372#[non_exhaustive]
373pub enum TorAddrError {
374 #[error("String can never be a valid hostname")]
376 InvalidHostname,
377 #[error("No port found in string")]
379 NoPort,
380 #[error("Could not parse port")]
382 BadPort,
383}
384
385impl HasKind for TorAddrError {
386 fn kind(&self) -> ErrorKind {
387 use ErrorKind as EK;
388 use TorAddrError as TAE;
389
390 match self {
391 TAE::InvalidHostname => EK::InvalidStreamTarget,
392 TAE::NoPort => EK::InvalidStreamTarget,
393 TAE::BadPort => EK::InvalidStreamTarget,
394 }
395 }
396}
397
398#[derive(Clone, Debug, Eq, PartialEq)]
408enum Host {
409 Hostname(String),
421 Ip(IpAddr),
423 Onion(String),
428}
429
430impl FromStr for Host {
431 type Err = TorAddrError;
432 fn from_str(s: &str) -> Result<Host, TorAddrError> {
433 if s.ends_with_ignore_ascii_case(".onion") && is_valid_hostname(s) {
434 Ok(Host::Onion(s.to_owned()))
435 } else if let Ok(ip_addr) = s.parse() {
436 Ok(Host::Ip(ip_addr))
437 } else if is_valid_hostname(s) {
438 Ok(Host::Hostname(s.to_owned()))
444 } else {
445 Err(TorAddrError::InvalidHostname)
446 }
447 }
448}
449
450impl Host {
451 fn is_globally_reachable_unicast(&self) -> bool {
454 match self {
455 Host::Hostname(name) => !name.eq_ignore_ascii_case("localhost"),
456 Host::Ip(ip) => is_globally_reachable_unicast(*ip),
457 Host::Onion(_) => true,
458 }
459 }
460}
461
462pub(crate) fn is_globally_reachable_unicast(addr: IpAddr) -> bool {
504 let addr = addr.to_canonical();
506 !(match addr {
516 IpAddr::V4(v4) => {
517 v4.is_loopback() || v4.is_private() || v4.is_unspecified() || v4.is_link_local() || is_shared(v4) || v4.is_broadcast() || v4.is_multicast() }
525 IpAddr::V6(v6) => {
526 v6.is_loopback() || v6.is_unspecified() || v6.is_unique_local() || v6.is_unicast_link_local() || v6.is_multicast() }
532 })
533}
534fn is_shared(addr: Ipv4Addr) -> bool {
542 addr.octets()[0] == 100 && (addr.octets()[1] & 0b1100_0000 == 0b0100_0000)
543}
544
545impl std::fmt::Display for Host {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 match self {
548 Host::Hostname(s) => Display::fmt(s, f),
549 Host::Ip(ip) => Display::fmt(ip, f),
550 Host::Onion(onion) => Display::fmt(onion, f),
551 }
552 }
553}
554
555impl IntoTorAddr for TorAddr {
556 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
557 Ok(self)
558 }
559}
560
561impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
562 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
563 self.clone().into_tor_addr()
564 }
565}
566
567impl IntoTorAddr for &str {
568 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
569 if let Ok(sa) = SocketAddr::from_str(self) {
570 TorAddr::new(Host::Ip(sa.ip()), sa.port())
571 } else {
572 let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
573 let host = host.parse()?;
574 let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
575 TorAddr::new(host, port)
576 }
577 }
578}
579
580impl IntoTorAddr for String {
581 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
582 self.as_str().into_tor_addr()
583 }
584}
585
586impl FromStr for TorAddr {
587 type Err = TorAddrError;
588 fn from_str(s: &str) -> Result<Self, TorAddrError> {
589 s.into_tor_addr()
590 }
591}
592
593impl IntoTorAddr for (&str, u16) {
594 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
595 let (host, port) = self;
596 let host = host.parse()?;
597 TorAddr::new(host, port)
598 }
599}
600
601impl IntoTorAddr for (String, u16) {
602 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
603 let (host, port) = self;
604 (host.as_str(), port).into_tor_addr()
605 }
606}
607
608impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
609 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
610 self.clone().into_tor_addr_dangerously()
611 }
612}
613
614impl DangerouslyIntoTorAddr for (IpAddr, u16) {
615 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
616 let (addr, port) = self;
617 TorAddr::new(Host::Ip(addr), port)
618 }
619}
620
621impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
622 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
623 let (addr, port) = self;
624 TorAddr::new(Host::Ip(addr.into()), port)
625 }
626}
627
628impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
629 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
630 let (addr, port) = self;
631 TorAddr::new(Host::Ip(addr.into()), port)
632 }
633}
634
635impl DangerouslyIntoTorAddr for SocketAddr {
636 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
637 let (addr, port) = (self.ip(), self.port());
638 (addr, port).into_tor_addr_dangerously()
639 }
640}
641
642impl DangerouslyIntoTorAddr for SocketAddrV4 {
643 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
644 let (addr, port) = (self.ip(), self.port());
645 (*addr, port).into_tor_addr_dangerously()
646 }
647}
648
649impl DangerouslyIntoTorAddr for SocketAddrV6 {
650 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
651 let (addr, port) = (self.ip(), self.port());
652 (*addr, port).into_tor_addr_dangerously()
653 }
654}
655
656fn is_valid_hostname(hostname: &str) -> bool {
660 hostname_validator::is_valid(hostname)
661}
662
663#[cfg(test)]
664mod test {
665 #![allow(clippy::bool_assert_comparison)]
667 #![allow(clippy::clone_on_copy)]
668 #![allow(clippy::dbg_macro)]
669 #![allow(clippy::mixed_attributes_style)]
670 #![allow(clippy::print_stderr)]
671 #![allow(clippy::print_stdout)]
672 #![allow(clippy::single_char_pattern)]
673 #![allow(clippy::unwrap_used)]
674 #![allow(clippy::unchecked_time_subtraction)]
675 #![allow(clippy::useless_vec)]
676 #![allow(clippy::needless_pass_by_value)]
677 #![allow(clippy::string_slice)] use super::*;
680
681 #[test]
682 fn test_error_kind() {
683 use tor_error::ErrorKind as EK;
684
685 assert_eq!(
686 TorAddrError::InvalidHostname.kind(),
687 EK::InvalidStreamTarget
688 );
689 assert_eq!(TorAddrError::NoPort.kind(), EK::InvalidStreamTarget);
690 assert_eq!(TorAddrError::BadPort.kind(), EK::InvalidStreamTarget);
691 }
692
693 fn mk_stream_prefs() -> StreamPrefs {
695 let prefs = crate::StreamPrefs::default();
696
697 #[cfg(feature = "onion-service-client")]
698 let prefs = {
699 let mut prefs = prefs;
700 prefs.connect_to_onion_services(tor_config::BoolOrAuto::Explicit(true));
701 prefs
702 };
703
704 prefs
705 }
706
707 #[test]
708 fn validate_hostname() {
709 assert!(is_valid_hostname("torproject.org"));
711 assert!(is_valid_hostname("Tor-Project.org"));
712 assert!(is_valid_hostname("example.onion"));
713 assert!(is_valid_hostname("some.example.onion"));
714
715 assert!(!is_valid_hostname("-torproject.org"));
717 assert!(!is_valid_hostname("_torproject.org"));
718 assert!(!is_valid_hostname("tor_project1.org"));
719 assert!(!is_valid_hostname("iwanna$money.org"));
720 }
721
722 #[test]
723 fn validate_addr() {
724 use crate::err::ErrorDetail;
725 fn val<A: IntoTorAddr>(addr: A) -> Result<TorAddr, ErrorDetail> {
726 let toraddr = addr.into_tor_addr()?;
727 toraddr.enforce_config(&Default::default(), &mk_stream_prefs())?;
728 Ok(toraddr)
729 }
730
731 assert!(val("[2001:db8::42]:20").is_ok());
732 assert!(val(("2001:db8::42", 20)).is_ok());
733 assert!(val(("198.151.100.42", 443)).is_ok());
734 assert!(val("198.151.100.42:443").is_ok());
735 assert!(val("www.torproject.org:443").is_ok());
736 assert!(val(("www.torproject.org", 443)).is_ok());
737
738 #[cfg(feature = "onion-service-client")]
740 {
741 assert!(val("example.onion:80").is_ok());
742 assert!(val(("example.onion", 80)).is_ok());
743
744 match val("eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443") {
745 Ok(TorAddr {
746 host: Host::Onion(_),
747 ..
748 }) => {}
749 x => panic!("{x:?}"),
750 }
751 }
752
753 assert!(matches!(
754 val("-foobar.net:443"),
755 Err(ErrorDetail::InvalidHostname)
756 ));
757 assert!(matches!(
758 val("www.torproject.org"),
759 Err(ErrorDetail::Address(TorAddrError::NoPort))
760 ));
761
762 assert!(matches!(
763 val("192.168.0.1:80"),
764 Err(ErrorDetail::LocalAddress)
765 ));
766 assert!(matches!(
767 val(TorAddr::new(Host::Hostname("foo@bar".to_owned()), 553).unwrap()),
768 Err(ErrorDetail::InvalidHostname)
769 ));
770 assert!(matches!(
771 val(TorAddr::new(Host::Hostname("foo.onion".to_owned()), 80).unwrap()),
772 Err(ErrorDetail::OnionAddressNotSupported)
773 ));
774 }
775
776 #[test]
777 fn local_addrs() {
778 fn is_local_hostname(s: &str) -> bool {
779 let h: Host = s.parse().unwrap();
780 !h.is_globally_reachable_unicast()
781 }
782
783 assert!(is_local_hostname("localhost"));
784 assert!(is_local_hostname("loCALHOST"));
785 assert!(is_local_hostname("127.0.0.1"));
786 assert!(is_local_hostname("::1"));
787 assert!(is_local_hostname("::ffff:127.0.0.1"));
788 assert!(is_local_hostname("192.168.0.1"));
789 assert!(is_local_hostname("0.0.0.0"));
790 assert!(is_local_hostname("::"));
791 assert!(is_local_hostname("100.64.0.1"));
792 assert!(is_local_hostname("fc00::"));
793 assert!(is_local_hostname("fe80::"));
794 assert!(is_local_hostname("255.255.255.255"));
795 assert!(is_local_hostname("224.1.1.1"));
796 assert!(is_local_hostname("ff00::"));
797
798 assert!(!is_local_hostname("www.example.com"));
799 }
800
801 #[test]
802 fn is_ip_address() {
803 fn ip(s: &str) -> bool {
804 TorAddr::from(s).unwrap().is_ip_address()
805 }
806
807 assert!(ip("192.168.0.1:80"));
808 assert!(ip("[::1]:80"));
809 assert!(ip("[2001:db8::42]:65535"));
810 assert!(!ip("example.com:80"));
811 assert!(!ip("example.onion:80"));
812 }
813
814 #[test]
815 fn stream_instructions() {
816 use StreamInstructions as SI;
817
818 fn sap(s: &str) -> Result<StreamInstructions, ErrorDetail> {
819 TorAddr::from(s)
820 .unwrap()
821 .into_stream_instructions(&Default::default(), &mk_stream_prefs())
822 }
823
824 assert_eq!(
825 sap("[2001:db8::42]:9001").unwrap(),
826 SI::Exit {
827 hostname: "2001:db8::42".to_owned(),
828 port: 9001
829 },
830 );
831 assert_eq!(
832 sap("example.com:80").unwrap(),
833 SI::Exit {
834 hostname: "example.com".to_owned(),
835 port: 80
836 },
837 );
838
839 {
840 let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
841 let onion = format!("sss1234.www.{}.onion", b32);
842 let got = sap(&format!("{}:443", onion));
843
844 #[cfg(feature = "onion-service-client")]
845 assert_eq!(
846 got.unwrap(),
847 SI::Hs {
848 hsid: format!("{}.onion", b32).parse().unwrap(),
849 hostname: onion,
850 port: 443,
851 }
852 );
853
854 #[cfg(not(feature = "onion-service-client"))]
855 assert!(matches!(got, Err(ErrorDetail::OnionAddressNotSupported)));
856 }
857 }
858
859 #[test]
860 fn resolve_instructions() {
861 use ResolveInstructions as RI;
862
863 fn sap(s: &str, prefs: &StreamPrefs) -> Result<ResolveInstructions, ErrorDetail> {
864 TorAddr::from(s)
865 .unwrap()
866 .into_resolve_instructions(&Default::default(), prefs)
867 }
868
869 let default_prefs = Default::default();
870 assert_eq!(
871 sap("[2001:db8::42]:9001", &default_prefs).unwrap(),
872 RI::Return(vec!["2001:db8::42".parse().unwrap()]),
873 );
874 assert_eq!(
875 sap("example.com:80", &default_prefs).unwrap(),
876 RI::Exit("example.com".to_owned()),
877 );
878 assert!(matches!(
879 sap("example.onion:80", &default_prefs),
880 Err(ErrorDetail::OnionAddressResolveRequest),
881 ));
882 assert!(matches!(
883 sap("localhost:80", &default_prefs),
884 Err(ErrorDetail::LocalAddress),
885 ));
886 assert!(matches!(
887 sap("127.0.0.1:80", &default_prefs),
888 Err(ErrorDetail::LocalAddress),
889 ));
890
891 let mut connect_to_local_addrs = StreamPrefs::new();
892 connect_to_local_addrs.connect_to_local_addrs(tor_config::BoolOrAuto::Explicit(true));
893
894 assert_eq!(
895 sap("localhost:80", &connect_to_local_addrs).unwrap(),
896 RI::Exit("localhost".to_owned()),
897 );
898 assert_eq!(
899 sap("127.0.0.1:80", &connect_to_local_addrs).unwrap(),
900 RI::Return(vec!["127.0.0.1".parse().unwrap()]),
901 );
902 }
903
904 #[test]
905 fn bad_ports() {
906 assert_eq!(
907 TorAddr::from("www.example.com:squirrel"),
908 Err(TorAddrError::BadPort)
909 );
910 assert_eq!(
911 TorAddr::from("www.example.com:0"),
912 Err(TorAddrError::BadPort)
913 );
914 }
915
916 #[test]
917 fn prefs_onion_services() {
918 use crate::err::ErrorDetailDiscriminants;
919 use ErrorDetailDiscriminants as EDD;
920 use ErrorKind as EK;
921 use tor_error::{ErrorKind, HasKind as _};
922
923 #[allow(clippy::redundant_closure)] let prefs_def = || StreamPrefs::default();
925
926 let addr: TorAddr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443"
927 .parse()
928 .unwrap();
929
930 fn map(
931 got: Result<impl Sized, ErrorDetail>,
932 ) -> Result<(), (ErrorDetailDiscriminants, ErrorKind)> {
933 got.map(|_| ())
934 .map_err(|e| (ErrorDetailDiscriminants::from(&e), e.kind()))
935 }
936
937 let check_stream = |prefs, expected| {
938 let got = addr
939 .clone()
940 .into_stream_instructions(&Default::default(), &prefs);
941 assert_eq!(map(got), expected, "{prefs:?}");
942 };
943 let check_resolve = |prefs| {
944 let got = addr
945 .clone()
946 .into_resolve_instructions(&Default::default(), &prefs);
947 let expected = Err((EDD::OnionAddressResolveRequest, EK::NotImplemented));
950 assert_eq!(map(got), expected, "{prefs:?}");
951 };
952
953 cfg_if::cfg_if! {
954 if #[cfg(feature = "onion-service-client")] {
955 use tor_config::BoolOrAuto as B;
956 let prefs_of = |yn| {
957 let mut prefs = StreamPrefs::default();
958 prefs.connect_to_onion_services(yn);
959 prefs
960 };
961 check_stream(prefs_def(), Ok(()));
962 check_stream(prefs_of(B::Auto), Ok(()));
963 check_stream(prefs_of(B::Explicit(true)), Ok(()));
964 check_stream(prefs_of(B::Explicit(false)), Err((EDD::OnionAddressDisabled, EK::ForbiddenStreamTarget)));
965
966 check_resolve(prefs_def());
967 check_resolve(prefs_of(B::Auto));
968 check_resolve(prefs_of(B::Explicit(true)));
969 check_resolve(prefs_of(B::Explicit(false)));
970 } else {
971 check_stream(prefs_def(), Err((EDD::OnionAddressNotSupported, EK::FeatureDisabled)));
972
973 check_resolve(prefs_def());
974 }
975 }
976 }
977
978 #[test]
979 fn convert_safe() {
980 fn check<A: IntoTorAddr>(a: A, s: &str) {
981 let a1 = TorAddr::from(a).unwrap();
982 let a2 = s.parse().unwrap();
983 assert_eq!(a1, a2);
984 assert_eq!(&a1.to_string(), s);
985 }
986
987 check(("www.example.com", 8000), "www.example.com:8000");
988 check(
989 TorAddr::from(("www.example.com", 8000)).unwrap(),
990 "www.example.com:8000",
991 );
992 check(
993 TorAddr::from(("www.example.com", 8000)).unwrap(),
994 "www.example.com:8000",
995 );
996 let addr = "[2001:db8::0042]:9001".to_owned();
997 check(&addr, "[2001:db8::42]:9001");
998 check(addr, "[2001:db8::42]:9001");
999 check(("2001:db8::0042".to_owned(), 9001), "[2001:db8::42]:9001");
1000 check(("example.onion", 80), "example.onion:80");
1001 }
1002
1003 #[test]
1004 fn convert_dangerous() {
1005 fn check<A: DangerouslyIntoTorAddr>(a: A, s: &str) {
1006 let a1 = TorAddr::dangerously_from(a).unwrap();
1007 let a2 = TorAddr::from(s).unwrap();
1008 assert_eq!(a1, a2);
1009 assert_eq!(&a1.to_string(), s);
1010 }
1011
1012 let ip: IpAddr = "203.0.133.6".parse().unwrap();
1013 let ip4: Ipv4Addr = "203.0.133.7".parse().unwrap();
1014 let ip6: Ipv6Addr = "2001:db8::42".parse().unwrap();
1015 let sa: SocketAddr = "203.0.133.8:80".parse().unwrap();
1016 let sa4: SocketAddrV4 = "203.0.133.8:81".parse().unwrap();
1017 let sa6: SocketAddrV6 = "[2001:db8::43]:82".parse().unwrap();
1018
1019 #[allow(clippy::needless_borrow)]
1021 #[allow(clippy::needless_borrows_for_generic_args)]
1022 check(&(ip, 443), "203.0.133.6:443");
1023 check((ip, 443), "203.0.133.6:443");
1024 check((ip4, 444), "203.0.133.7:444");
1025 check((ip6, 445), "[2001:db8::42]:445");
1026 check(sa, "203.0.133.8:80");
1027 check(sa4, "203.0.133.8:81");
1028 check(sa6, "[2001:db8::43]:82");
1029 }
1030}