diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 1743d58c6234ee..39465f2a89e5cd 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -8060,7 +8060,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "run", - "--allow-net=deno.land,deno.land:80,::,127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080", + "--allow-net=deno.land,deno.land:80,[::],127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080", "script.ts" ]); assert_eq!( @@ -8073,7 +8073,7 @@ mod tests { allow_net: Some(svec![ "deno.land", "deno.land:80", - "::", + "[::]", "127.0.0.1", "[::1]", "1.2.3.4:5678", @@ -8095,7 +8095,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "run", - "--deny-net=deno.land,deno.land:80,::,127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080", + "--deny-net=deno.land,deno.land:80,[::],127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080", "script.ts" ]); assert_eq!( @@ -8108,7 +8108,7 @@ mod tests { deny_net: Some(svec![ "deno.land", "deno.land:80", - "::", + "[::]", "127.0.0.1", "[::1]", "1.2.3.4:5678", diff --git a/cli/args/flags_net.rs b/cli/args/flags_net.rs index 2ea4670563ba05..ba441fc93eadbc 100644 --- a/cli/args/flags_net.rs +++ b/cli/args/flags_net.rs @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_core::url::Url; +use deno_runtime::deno_permissions::NetDescriptor; use std::net::IpAddr; use std::str::FromStr; @@ -42,21 +43,17 @@ pub fn validator(host_and_port: &str) -> Result { /// `127.0.0.1:port` and `localhost:port`. pub fn parse(paths: Vec) -> clap::error::Result> { let mut out: Vec = vec![]; - for host_and_port in paths.iter() { - if Url::parse(&format!("internal://{host_and_port}")).is_ok() - || host_and_port.parse::().is_ok() - { - out.push(host_and_port.to_owned()) - } else if let Ok(port) = host_and_port.parse::() { + for host_and_port in paths.into_iter() { + if let Ok(port) = host_and_port.parse::() { // we got bare port, let's add default hosts for host in ["0.0.0.0", "127.0.0.1", "localhost"].iter() { out.push(format!("{}:{}", host, port.0)); } } else { - return Err(clap::Error::raw( - clap::error::ErrorKind::InvalidValue, - format!("Bad host:port pair: {host_and_port}"), - )); + host_and_port.parse::().map_err(|e| { + clap::Error::raw(clap::error::ErrorKind::InvalidValue, format!("{e:?}")) + })?; + out.push(host_and_port) } } Ok(out) @@ -174,10 +171,8 @@ mod tests { #[test] fn parse_net_args_ipv6() { - let entries = - svec!["::", "::1", "[::1]", "[::]:5678", "[::1]:5678", "::cafe"]; - let expected = - svec!["::", "::1", "[::1]", "[::]:5678", "[::1]:5678", "::cafe"]; + let entries = svec!["[::1]", "[::]:5678", "[::1]:5678"]; + let expected = svec!["[::1]", "[::]:5678", "[::1]:5678"]; let actual = parse(entries).unwrap(); assert_eq!(actual, expected); } @@ -190,12 +185,36 @@ mod tests { #[test] fn parse_net_args_ipv6_error2() { - let entries = svec!["0123:4567:890a:bcde:fg::"]; + let entries = svec!["::1"]; assert!(parse(entries).is_err()); } #[test] fn parse_net_args_ipv6_error3() { + let entries = svec!["::"]; + assert!(parse(entries).is_err()); + } + + #[test] + fn parse_net_args_ipv6_error4() { + let entries = svec!["::cafe"]; + assert!(parse(entries).is_err()); + } + + #[test] + fn parse_net_args_ipv6_error5() { + let entries = svec!["1::1"]; + assert!(parse(entries).is_err()); + } + + #[test] + fn parse_net_args_ipv6_error6() { + let entries = svec!["0123:4567:890a:bcde:fg::"]; + assert!(parse(entries).is_err()); + } + + #[test] + fn parse_net_args_ipv6_error7() { let entries = svec!["[::q]:8080"]; assert!(parse(entries).is_err()); } diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs index a961fd3ea70439..c15e7d0135c782 100644 --- a/runtime/ops/permissions.rs +++ b/runtime/ops/permissions.rs @@ -4,10 +4,8 @@ use ::deno_permissions::parse_sys_kind; use ::deno_permissions::PermissionState; use ::deno_permissions::PermissionsContainer; use deno_core::error::custom_error; -use deno_core::error::uri_error; use deno_core::error::AnyError; use deno_core::op2; -use deno_core::url; use deno_core::OpState; use serde::Deserialize; use serde::Serialize; @@ -65,7 +63,7 @@ pub fn op_query_permission( "net" => permissions.net.query( match args.host.as_deref() { None => None, - Some(h) => Some(parse_host(h)?), + Some(h) => Some(h.parse()?), } .as_ref(), ), @@ -100,7 +98,7 @@ pub fn op_revoke_permission( "net" => permissions.net.revoke( match args.host.as_deref() { None => None, - Some(h) => Some(parse_host(h)?), + Some(h) => Some(h.parse()?), } .as_ref(), ), @@ -135,7 +133,7 @@ pub fn op_request_permission( "net" => permissions.net.request( match args.host.as_deref() { None => None, - Some(h) => Some(parse_host(h)?), + Some(h) => Some(h.parse()?), } .as_ref(), ), @@ -155,13 +153,3 @@ pub fn op_request_permission( }; Ok(PermissionStatus::from(perm)) } - -fn parse_host(host_str: &str) -> Result<(String, Option), AnyError> { - let url = url::Url::parse(&format!("http://{host_str}/")) - .map_err(|_| uri_error("Invalid host"))?; - if url.path() != "/" { - return Err(uri_error("Invalid host")); - } - let hostname = url.host_str().unwrap(); - Ok((hostname.to_string(), url.port())) -} diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index 4579eba1a36ed0..db3da99b7b28fa 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -16,7 +16,6 @@ use deno_core::url; use deno_core::url::Url; use deno_core::ModuleSpecifier; use deno_terminal::colors; -use fqdn::fqdn; use fqdn::FQDN; use once_cell::sync::Lazy; use std::borrow::Cow; @@ -25,6 +24,8 @@ use std::ffi::OsStr; use std::fmt; use std::fmt::Debug; use std::hash::Hash; +use std::net::IpAddr; +use std::net::Ipv6Addr; use std::path::Path; use std::path::PathBuf; use std::str::FromStr; @@ -691,14 +692,39 @@ impl Descriptor for WriteDescriptor { } #[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub struct NetDescriptor(pub FQDN, pub Option); +pub enum Host { + Fqdn(FQDN), + Ip(IpAddr), +} + +impl FromStr for Host { + type Err = AnyError; -impl NetDescriptor { - fn new>(host: &&(T, Option)) -> Self { - NetDescriptor(fqdn!(host.0.as_ref()), host.1) + fn from_str(s: &str) -> Result { + if s.starts_with('[') && s.ends_with(']') { + let ip = s[1..s.len() - 1] + .parse::() + .map_err(|_| uri_error(format!("invalid IPv6 address: '{s}'")))?; + return Ok(Host::Ip(IpAddr::V6(ip))); + } + let (s, has_trailing_dot) = + s.strip_suffix('.').map_or((s, false), |s| (s, true)); + if let Ok(ip) = s.parse::() { + if has_trailing_dot { + return Err(uri_error(format!("invalid host: '{s}'"))); + } + Ok(Host::Ip(ip)) + } else { + let fqdn = + FQDN::from_str(s).with_context(|| format!("invalid host: '{s}'"))?; + Ok(Host::Fqdn(fqdn)) + } } } +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct NetDescriptor(pub Host, pub Option); + impl Descriptor for NetDescriptor { type Arg = String; @@ -731,26 +757,66 @@ impl Descriptor for NetDescriptor { impl FromStr for NetDescriptor { type Err = AnyError; - fn from_str(s: &str) -> Result { - // Set the scheme to `unknown` to parse the URL, as we really don't know - // what the scheme is. We only using Url::parse to parse the host and port - // and don't care about the scheme. - let url = url::Url::parse(&format!("unknown://{s}"))?; - let hostname = url - .host_str() - .ok_or(url::ParseError::EmptyHost)? - .to_string(); + fn from_str(hostname: &str) -> Result { + // If this is a IPv6 address enclosed in square brackets, parse it as such. + if hostname.starts_with('[') { + if let Some((ip, after)) = hostname.split_once(']') { + let ip = ip[1..].parse::().map_err(|_| { + uri_error(format!("invalid IPv6 address in '{hostname}': '{ip}'")) + })?; + let port = if let Some(port) = after.strip_prefix(':') { + let port = port.parse::().map_err(|_| { + uri_error(format!("invalid port in '{hostname}': '{port}'")) + })?; + Some(port) + } else if after.is_empty() { + None + } else { + return Err(uri_error(format!("invalid host: '{hostname}'"))); + }; + return Ok(NetDescriptor(Host::Ip(IpAddr::V6(ip)), port)); + } else { + return Err(uri_error(format!("invalid host: '{hostname}'"))); + } + } + + // Otherwise it is an IPv4 address or a hostname with an optional port. + let (host, port) = hostname.split_once(':').unwrap_or((hostname, "")); + let host = host.parse::()?; + + let port = if port.is_empty() { + None + } else { + let port = port.parse::().map_err(|_| { + // If the user forgot to enclose an IPv6 address in square brackets, we + // should give them a hint. There are always at least two colons in an + // IPv6 address, so this heuristic finds likely a bare IPv6 address. + if port.contains(':') { + uri_error(format!( + "ipv6 addresses must be enclosed in square brackets: '{hostname}'" + )) + } else { + uri_error(format!("invalid port in '{hostname}': '{port}'")) + } + })?; + Some(port) + }; - Ok(NetDescriptor(fqdn!(&hostname), url.port())) + Ok(NetDescriptor(host, port)) } } impl fmt::Display for NetDescriptor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&match self.1 { - None => self.0.to_string(), - Some(port) => format!("{}:{}", self.0, port), - }) + match &self.0 { + Host::Fqdn(fqdn) => write!(f, "{fqdn}"), + Host::Ip(IpAddr::V4(ip)) => write!(f, "{ip}"), + Host::Ip(IpAddr::V6(ip)) => write!(f, "[{ip}]"), + }?; + if let Some(port) = self.1 { + write!(f, ":{}", port)?; + } + Ok(()) } } @@ -1105,37 +1171,25 @@ impl UnaryPermission { } impl UnaryPermission { - pub fn query>( - &self, - host: Option<&(T, Option)>, - ) -> PermissionState { - self.query_desc( - host.map(|h| NetDescriptor::new(&h)).as_ref(), - AllowPartial::TreatAsPartialGranted, - ) + pub fn query(&self, host: Option<&NetDescriptor>) -> PermissionState { + self.query_desc(host, AllowPartial::TreatAsPartialGranted) } - pub fn request>( - &mut self, - host: Option<&(T, Option)>, - ) -> PermissionState { - self.request_desc(host.map(|h| NetDescriptor::new(&h)).as_ref(), || None) + pub fn request(&mut self, host: Option<&NetDescriptor>) -> PermissionState { + self.request_desc(host, || None) } - pub fn revoke>( - &mut self, - host: Option<&(T, Option)>, - ) -> PermissionState { - self.revoke_desc(host.map(|h| NetDescriptor::new(&h)).as_ref()) + pub fn revoke(&mut self, host: Option<&NetDescriptor>) -> PermissionState { + self.revoke_desc(host) } - pub fn check>( + pub fn check( &mut self, - host: &(T, Option), + host: &NetDescriptor, api_name: Option<&str>, ) -> Result<(), AnyError> { skip_check_if_is_permission_fully_granted!(self); - self.check_desc(Some(&NetDescriptor::new(&host)), false, api_name, || None) + self.check_desc(Some(host), false, api_name, || None) } pub fn check_url( @@ -1144,17 +1198,14 @@ impl UnaryPermission { api_name: Option<&str>, ) -> Result<(), AnyError> { skip_check_if_is_permission_fully_granted!(self); - let hostname = url + let host = url .host_str() - .ok_or_else(|| uri_error("Missing host"))? - .to_string(); - let host = &(&hostname, url.port_or_known_default()); - let display_host = match url.port() { - None => hostname.clone(), - Some(port) => format!("{hostname}:{port}"), - }; - self.check_desc(Some(&NetDescriptor::new(&host)), false, api_name, || { - Some(format!("\"{}\"", display_host)) + .ok_or_else(|| type_error(format!("Missing host in url: '{}'", url)))?; + let host = host.parse::()?; + let port = url.port_or_known_default(); + let descriptor = NetDescriptor(host, port); + self.check_desc(Some(&descriptor), false, api_name, || { + Some(format!("\"{descriptor}\"")) }) } @@ -1780,7 +1831,9 @@ impl PermissionsContainer { host: &(T, Option), api_name: &str, ) -> Result<(), AnyError> { - self.0.lock().net.check(host, Some(api_name)) + let hostname = host.0.as_ref().parse::()?; + let descriptor = NetDescriptor(hostname, host.1); + self.0.lock().net.check(&descriptor, Some(api_name)) } #[inline(always)] @@ -2207,7 +2260,9 @@ pub fn create_child_permissions( mod tests { use super::*; use deno_core::serde_json::json; + use fqdn::fqdn; use prompter::tests::*; + use std::net::Ipv4Addr; // Creates vector of strings, Vec macro_rules! svec { @@ -2361,12 +2416,12 @@ mod tests { ]; for (host, port, is_ok) in domain_tests { + let host = host.parse().unwrap(); + let descriptor = NetDescriptor(host, Some(port)); assert_eq!( is_ok, - perms.net.check(&(host, Some(port)), None).is_ok(), - "{}:{}", - host, - port, + perms.net.check(&descriptor, None).is_ok(), + "{descriptor}", ); } } @@ -2403,7 +2458,9 @@ mod tests { ]; for (host, port) in domain_tests { - assert!(perms.net.check(&(host, Some(port)), None).is_ok()); + let host = host.parse().unwrap(); + let descriptor = NetDescriptor(host, Some(port)); + assert!(perms.net.check(&descriptor, None).is_err()); } } @@ -2439,7 +2496,9 @@ mod tests { ]; for (host, port) in domain_tests { - assert!(perms.net.check(&(host, Some(port)), None).is_err()); + let host = host.parse().unwrap(); + let descriptor = NetDescriptor(host, Some(port)); + assert!(perms.net.check(&descriptor, None).is_err()); } } @@ -2714,15 +2773,15 @@ mod tests { assert_eq!(perms4.ffi.query(Some(Path::new("/foo"))), PermissionState::Denied); assert_eq!(perms4.ffi.query(Some(Path::new("/foo/bar"))), PermissionState::Denied); assert_eq!(perms4.ffi.query(Some(Path::new("/bar"))), PermissionState::Granted); - assert_eq!(perms1.net.query::<&str>(None), PermissionState::Granted); - assert_eq!(perms1.net.query(Some(&("127.0.0.1", None))), PermissionState::Granted); - assert_eq!(perms2.net.query::<&str>(None), PermissionState::Prompt); - assert_eq!(perms2.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); - assert_eq!(perms3.net.query::<&str>(None), PermissionState::Prompt); - assert_eq!(perms3.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Denied); - assert_eq!(perms4.net.query::<&str>(None), PermissionState::GrantedPartial); - assert_eq!(perms4.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Denied); - assert_eq!(perms4.net.query(Some(&("192.168.0.1", Some(8000)))), PermissionState::Granted); + assert_eq!(perms1.net.query(None), PermissionState::Granted); + assert_eq!(perms1.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), None))), PermissionState::Granted); + assert_eq!(perms2.net.query(None), PermissionState::Prompt); + assert_eq!(perms2.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Granted); + assert_eq!(perms3.net.query(None), PermissionState::Prompt); + assert_eq!(perms3.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Denied); + assert_eq!(perms4.net.query(None), PermissionState::GrantedPartial); + assert_eq!(perms4.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Denied); + assert_eq!(perms4.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Granted); assert_eq!(perms1.env.query(None), PermissionState::Granted); assert_eq!(perms1.env.query(Some("HOME")), PermissionState::Granted); assert_eq!(perms2.env.query(None), PermissionState::Prompt); @@ -2780,9 +2839,9 @@ mod tests { prompt_value.set(true); assert_eq!(perms.ffi.request(None), PermissionState::Denied); prompt_value.set(true); - assert_eq!(perms.net.request(Some(&("127.0.0.1", None))), PermissionState::Granted); + assert_eq!(perms.net.request(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), None))), PermissionState::Granted); prompt_value.set(false); - assert_eq!(perms.net.request(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); + assert_eq!(perms.net.request(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Granted); prompt_value.set(true); assert_eq!(perms.env.request(Some("HOME")), PermissionState::Granted); assert_eq!(perms.env.query(None), PermissionState::Prompt); @@ -2851,9 +2910,9 @@ mod tests { assert_eq!(perms.ffi.revoke(Some(Path::new("/foo/bar"))), PermissionState::Prompt); assert_eq!(perms.ffi.query(Some(Path::new("/foo"))), PermissionState::Prompt); assert_eq!(perms.ffi.query(Some(Path::new("/foo/baz"))), PermissionState::Granted); - assert_eq!(perms.net.revoke(Some(&("127.0.0.1", Some(9000)))), PermissionState::Prompt); - assert_eq!(perms.net.query(Some(&("127.0.0.1", None))), PermissionState::Prompt); - assert_eq!(perms.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); + assert_eq!(perms.net.revoke(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(9000)))), PermissionState::Prompt); + assert_eq!(perms.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), None))), PermissionState::Prompt); + assert_eq!(perms.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Granted); assert_eq!(perms.env.revoke(Some("HOME")), PermissionState::Prompt); assert_eq!(perms.env.revoke(Some("hostname")), PermissionState::Prompt); assert_eq!(perms.run.revoke(Some("deno")), PermissionState::Prompt); @@ -2886,13 +2945,43 @@ mod tests { assert!(perms.ffi.check(Path::new("/bar"), None).is_err()); prompt_value.set(true); - assert!(perms.net.check(&("127.0.0.1", Some(8000)), None).is_ok()); + assert!(perms + .net + .check( + &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)), + None + ) + .is_ok()); prompt_value.set(false); - assert!(perms.net.check(&("127.0.0.1", Some(8000)), None).is_ok()); - assert!(perms.net.check(&("127.0.0.1", Some(8001)), None).is_err()); - assert!(perms.net.check(&("127.0.0.1", None), None).is_err()); - assert!(perms.net.check(&("deno.land", Some(8000)), None).is_err()); - assert!(perms.net.check(&("deno.land", None), None).is_err()); + assert!(perms + .net + .check( + &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)), + None + ) + .is_ok()); + assert!(perms + .net + .check( + &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8001)), + None + ) + .is_err()); + assert!(perms + .net + .check(&NetDescriptor("127.0.0.1".parse().unwrap(), None), None) + .is_err()); + assert!(perms + .net + .check( + &NetDescriptor("deno.land".parse().unwrap(), Some(8000)), + None + ) + .is_err()); + assert!(perms + .net + .check(&NetDescriptor("deno.land".parse().unwrap(), None), None) + .is_err()); prompt_value.set(true); assert!(perms.run.check("cat", None).is_ok()); @@ -2946,14 +3035,50 @@ mod tests { assert!(perms.ffi.check(Path::new("/bar"), None).is_ok()); prompt_value.set(false); - assert!(perms.net.check(&("127.0.0.1", Some(8000)), None).is_err()); + assert!(perms + .net + .check( + &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)), + None + ) + .is_err()); prompt_value.set(true); - assert!(perms.net.check(&("127.0.0.1", Some(8000)), None).is_err()); - assert!(perms.net.check(&("127.0.0.1", Some(8001)), None).is_ok()); - assert!(perms.net.check(&("deno.land", Some(8000)), None).is_ok()); + assert!(perms + .net + .check( + &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)), + None + ) + .is_err()); + assert!(perms + .net + .check( + &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8001)), + None + ) + .is_ok()); + assert!(perms + .net + .check( + &NetDescriptor("deno.land".parse().unwrap(), Some(8000)), + None + ) + .is_ok()); prompt_value.set(false); - assert!(perms.net.check(&("127.0.0.1", Some(8001)), None).is_ok()); - assert!(perms.net.check(&("deno.land", Some(8000)), None).is_ok()); + assert!(perms + .net + .check( + &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8001)), + None + ) + .is_ok()); + assert!(perms + .net + .check( + &NetDescriptor("deno.land".parse().unwrap(), Some(8001)), + None + ) + .is_ok()); prompt_value.set(false); assert!(perms.run.check("cat", None).is_err()); @@ -3042,10 +3167,28 @@ mod tests { ..Permissions::none_without_prompt() }; - perms.net.check(&("allowed.domain.", None), None).unwrap(); - perms.net.check(&("1.1.1.1.", None), None).unwrap(); - assert!(perms.net.check(&("denied.domain.", None), None).is_err()); - assert!(perms.net.check(&("2.2.2.2.", None), None).is_err()); + perms + .net + .check( + &NetDescriptor("allowed.domain.".parse().unwrap(), None), + None, + ) + .unwrap(); + perms + .net + .check(&NetDescriptor("1.1.1.1".parse().unwrap(), None), None) + .unwrap(); + assert!(perms + .net + .check( + &NetDescriptor("denied.domain.".parse().unwrap(), None), + None + ) + .is_err()); + assert!(perms + .net + .check(&NetDescriptor("2.2.2.2".parse().unwrap(), None), None) + .is_err()); } #[test] @@ -3331,4 +3474,101 @@ mod tests { ) .is_err()); } + + #[test] + fn test_host_parse() { + let hosts = &[ + ("deno.land", Some(Host::Fqdn(fqdn!("deno.land")))), + ("deno.land.", Some(Host::Fqdn(fqdn!("deno.land")))), + ( + "1.1.1.1", + Some(Host::Ip(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)))), + ), + ( + "::1", + Some(Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)))), + ), + ( + "[::1]", + Some(Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)))), + ), + ("[::1", None), + ("::1]", None), + ("deno. land", None), + ("1. 1.1.1", None), + ("1.1.1.1.", None), + ("1::1.", None), + ("deno.land.", Some(Host::Fqdn(fqdn!("deno.land")))), + (".deno.land", None), + ( + "::ffff:1.1.1.1", + Some(Host::Ip(IpAddr::V6(Ipv6Addr::new( + 0, 0, 0, 0, 0, 0xffff, 0x0101, 0x0101, + )))), + ), + ]; + + for (host_str, expected) in hosts { + assert_eq!(host_str.parse::().ok(), *expected, "{host_str}"); + } + } + + #[test] + fn test_net_descriptor_parse() { + let cases = &[ + ( + "deno.land", + Some(NetDescriptor(Host::Fqdn(fqdn!("deno.land")), None)), + ), + ( + "deno.land:8000", + Some(NetDescriptor(Host::Fqdn(fqdn!("deno.land")), Some(8000))), + ), + ("deno.land:", None), + ("deno.land:a", None), + ("deno. land:a", None), + ("deno.land.: a", None), + ( + "1.1.1.1", + Some(NetDescriptor( + Host::Ip(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1))), + None, + )), + ), + ("1.1.1.1.", None), + ( + "1.1.1.1:8000", + Some(NetDescriptor( + Host::Ip(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1))), + Some(8000), + )), + ), + ("::", None), + (":::80", None), + ("::80", None), + ( + "[::]", + Some(NetDescriptor( + Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0))), + None, + )), + ), + ("[::1", None), + ("::1]", None), + ("::1]", None), + ("[::1]:", None), + ("[::1]:a", None), + ( + "[::1]:443", + Some(NetDescriptor( + Host::Ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))), + Some(443), + )), + ), + ]; + + for (input, expected) in cases { + assert_eq!(input.parse::().ok(), *expected, "{input}"); + } + } }