diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 4d0465caab..710dbc0a64 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -7859,7 +7859,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "run", - "--unsafely-ignore-certificate-errors=deno.land,localhost,::,127.0.0.1,[::1],1.2.3.4", + "--unsafely-ignore-certificate-errors=deno.land,localhost,[::],127.0.0.1,[::1],1.2.3.4", "script.ts" ]); assert_eq!( @@ -7871,7 +7871,7 @@ mod tests { unsafely_ignore_certificate_errors: Some(svec![ "deno.land", "localhost", - "::", + "[::]", "127.0.0.1", "[::1]", "1.2.3.4" @@ -7887,7 +7887,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "repl", - "--unsafely-ignore-certificate-errors=deno.land,localhost,::,127.0.0.1,[::1],1.2.3.4"]); + "--unsafely-ignore-certificate-errors=deno.land,localhost,[::],127.0.0.1,[::1],1.2.3.4"]); assert_eq!( r.unwrap(), Flags { @@ -7899,7 +7899,7 @@ mod tests { unsafely_ignore_certificate_errors: Some(svec![ "deno.land", "localhost", - "::", + "[::]", "127.0.0.1", "[::1]", "1.2.3.4" @@ -8091,7 +8091,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!( @@ -8104,7 +8104,7 @@ mod tests { allow_net: Some(svec![ "deno.land", "deno.land:80", - "::", + "[::]", "127.0.0.1", "[::1]", "1.2.3.4:5678", @@ -8126,7 +8126,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!( @@ -8139,7 +8139,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 2ea4670563..57cf8d527c 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) @@ -121,8 +118,8 @@ mod tests { let entries = svec![ "deno.land", "deno.land:80", - "::", - "::1", + "[::]", + "[::1]", "127.0.0.1", "[::1]", "1.2.3.4:5678", @@ -142,8 +139,8 @@ mod tests { let expected = svec![ "deno.land", "deno.land:80", - "::", - "::1", + "[::]", + "[::1]", "127.0.0.1", "[::1]", "1.2.3.4:5678", @@ -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 a961fd3ea7..c15e7d0135 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 3b3f68a530..a2245316b7 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,49 @@ 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 NetDescriptor { - fn new>(host: &&(T, Option)) -> Self { - NetDescriptor(fqdn!(host.0.as_ref()), host.1) +impl FromStr for Host { + type Err = AnyError; + + 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 (without_trailing_dot, has_trailing_dot) = + s.strip_suffix('.').map_or((s, false), |s| (s, true)); + if let Ok(ip) = without_trailing_dot.parse::() { + if has_trailing_dot { + return Err(uri_error(format!( + "invalid host: '{without_trailing_dot}'" + ))); + } + Ok(Host::Ip(ip)) + } else { + let lower = if s.chars().all(|c| c.is_ascii_lowercase()) { + Cow::Borrowed(s) + } else { + Cow::Owned(s.to_ascii_lowercase()) + }; + let fqdn = FQDN::from_str(&lower) + .with_context(|| format!("invalid host: '{s}'"))?; + if fqdn.is_root() { + return Err(uri_error(format!("invalid empty 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 +767,72 @@ 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}'"))); + } + } - Ok(NetDescriptor(fqdn!(&hostname), url.port())) + // Otherwise it is an IPv4 address or a FQDN with an optional port. + let (host, port) = match hostname.split_once(':') { + Some((_, "")) => { + return Err(uri_error(format!("invalid empty port in '{hostname}'"))); + } + Some((host, port)) => (host, port), + None => (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(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(()) } } @@ -1107,37 +1189,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( @@ -1146,17 +1216,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}\"")) }) } @@ -1782,7 +1849,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)] @@ -2209,7 +2278,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 { @@ -2363,12 +2434,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}", ); } } @@ -2404,8 +2475,13 @@ mod tests { ("192.168.0.1", 0), ]; - for (host, port) in domain_tests { - assert!(perms.net.check(&(host, Some(port)), None).is_ok()); + for (host_str, port) in domain_tests { + let host = host_str.parse().unwrap(); + let descriptor = NetDescriptor(host, Some(port)); + assert!( + perms.net.check(&descriptor, None).is_ok(), + "expected {host_str}:{port} to pass" + ); } } @@ -2440,8 +2516,13 @@ mod tests { ("192.168.0.1", 0), ]; - for (host, port) in domain_tests { - assert!(perms.net.check(&(host, Some(port)), None).is_err()); + for (host_str, port) in domain_tests { + let host = host_str.parse().unwrap(); + let descriptor = NetDescriptor(host, Some(port)); + assert!( + perms.net.check(&descriptor, None).is_err(), + "expected {host_str}:{port} to fail" + ); } } @@ -2716,15 +2797,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("192.168.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); @@ -2782,9 +2863,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); @@ -2853,9 +2934,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); @@ -2888,13 +2969,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()); @@ -2948,14 +3059,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(8000)), + None + ) + .is_ok()); prompt_value.set(false); assert!(perms.run.check("cat", None).is_err()); @@ -3044,10 +3191,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] @@ -3333,4 +3498,109 @@ 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")))), + ("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", + 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..", 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), + )), + ), + ("", None), + ("deno.land..", None), + ]; + + for (input, expected) in cases { + assert_eq!(input.parse::().ok(), *expected, "'{input}'"); + } + } } diff --git a/tests/testdata/dynamic_import/permissions_blob_remote.ts.out b/tests/testdata/dynamic_import/permissions_blob_remote.ts.out index a00c02d72f..f436a5eb84 100644 --- a/tests/testdata/dynamic_import/permissions_blob_remote.ts.out +++ b/tests/testdata/dynamic_import/permissions_blob_remote.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag +error: Uncaught (in promise) TypeError: Requires net access to "example.com:443", run again with the --allow-net flag at blob:null/[WILDCARD]:1:8 await import(URL.createObjectURL(blob)); ^ diff --git a/tests/testdata/dynamic_import/permissions_data_remote.ts.out b/tests/testdata/dynamic_import/permissions_data_remote.ts.out index cb2a7ccf79..00248e2778 100644 --- a/tests/testdata/dynamic_import/permissions_data_remote.ts.out +++ b/tests/testdata/dynamic_import/permissions_data_remote.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag +error: Uncaught (in promise) TypeError: Requires net access to "example.com:443", run again with the --allow-net flag at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8 await import(`data:application/javascript;base64,${btoa(code)}`); ^ diff --git a/tests/testdata/dynamic_import/permissions_remote_remote.ts.out b/tests/testdata/dynamic_import/permissions_remote_remote.ts.out index bd88dd4d98..0e8b0fc1f1 100644 --- a/tests/testdata/dynamic_import/permissions_remote_remote.ts.out +++ b/tests/testdata/dynamic_import/permissions_remote_remote.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag +error: Uncaught (in promise) TypeError: Requires net access to "example.com:443", run again with the --allow-net flag at http://localhost:4545/dynamic_import/static_remote.ts:2:8 await import( ^ diff --git a/tests/testdata/workers/permissions_blob_remote.ts.out b/tests/testdata/workers/permissions_blob_remote.ts.out index 618f552dc1..6dc4f0f5fa 100644 --- a/tests/testdata/workers/permissions_blob_remote.ts.out +++ b/tests/testdata/workers/permissions_blob_remote.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag +error: Uncaught (in worker "") Requires net access to "example.com:443", run again with the --allow-net flag at blob:null/[WILDCARD]:1:8 error: Uncaught (in promise) Error: Unhandled error in child worker. at Worker.#pollControl[WILDCARD] diff --git a/tests/testdata/workers/permissions_data_remote.ts.out b/tests/testdata/workers/permissions_data_remote.ts.out index 3f7c8cb634..e7af110bb5 100644 --- a/tests/testdata/workers/permissions_data_remote.ts.out +++ b/tests/testdata/workers/permissions_data_remote.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag +error: Uncaught (in worker "") Requires net access to "example.com:443", run again with the --allow-net flag at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8 error: Uncaught (in promise) Error: Unhandled error in child worker. at Worker.#pollControl[WILDCARD] diff --git a/tests/testdata/workers/permissions_dynamic_remote.ts.out b/tests/testdata/workers/permissions_dynamic_remote.ts.out index 91f3cc6d5b..4fb2c2234f 100644 --- a/tests/testdata/workers/permissions_dynamic_remote.ts.out +++ b/tests/testdata/workers/permissions_dynamic_remote.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in worker "") (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag +error: Uncaught (in worker "") (in promise) TypeError: Requires net access to "example.com:443", run again with the --allow-net flag await import("" + "https://example.com/some/file.ts"); ^ at async http://localhost:4545/workers/dynamic_remote.ts:2:1 diff --git a/tests/testdata/workers/permissions_remote_remote.ts.out b/tests/testdata/workers/permissions_remote_remote.ts.out index bb065740aa..a095f1938b 100644 --- a/tests/testdata/workers/permissions_remote_remote.ts.out +++ b/tests/testdata/workers/permissions_remote_remote.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag +error: Uncaught (in worker "") Requires net access to "example.com:443", run again with the --allow-net flag at http://localhost:4545/workers/static_remote.ts:2:8 error: Uncaught (in promise) Error: Unhandled error in child worker. at Worker.#pollControl [WILDCARD]