1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

fix(permissions): handle ipv6 addresses correctly (#24397)

Also don't panic on invalid domain names and addresses.

Extracted with cleanups up from #24080

Co-authored-by: Yazan AbdAl-Rahman <yazan.abdalrahman@exalt.ps>
This commit is contained in:
Luca Casonato 2024-07-05 23:45:06 +01:00 committed by GitHub
parent 80df9aec1d
commit 74ac29bae6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 415 additions and 138 deletions

View file

@ -7859,7 +7859,7 @@ mod tests {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
"deno", "deno",
"run", "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" "script.ts"
]); ]);
assert_eq!( assert_eq!(
@ -7871,7 +7871,7 @@ mod tests {
unsafely_ignore_certificate_errors: Some(svec![ unsafely_ignore_certificate_errors: Some(svec![
"deno.land", "deno.land",
"localhost", "localhost",
"::", "[::]",
"127.0.0.1", "127.0.0.1",
"[::1]", "[::1]",
"1.2.3.4" "1.2.3.4"
@ -7887,7 +7887,7 @@ mod tests {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
"deno", "deno",
"repl", "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!( assert_eq!(
r.unwrap(), r.unwrap(),
Flags { Flags {
@ -7899,7 +7899,7 @@ mod tests {
unsafely_ignore_certificate_errors: Some(svec![ unsafely_ignore_certificate_errors: Some(svec![
"deno.land", "deno.land",
"localhost", "localhost",
"::", "[::]",
"127.0.0.1", "127.0.0.1",
"[::1]", "[::1]",
"1.2.3.4" "1.2.3.4"
@ -8091,7 +8091,7 @@ mod tests {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
"deno", "deno",
"run", "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" "script.ts"
]); ]);
assert_eq!( assert_eq!(
@ -8104,7 +8104,7 @@ mod tests {
allow_net: Some(svec![ allow_net: Some(svec![
"deno.land", "deno.land",
"deno.land:80", "deno.land:80",
"::", "[::]",
"127.0.0.1", "127.0.0.1",
"[::1]", "[::1]",
"1.2.3.4:5678", "1.2.3.4:5678",
@ -8126,7 +8126,7 @@ mod tests {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
"deno", "deno",
"run", "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" "script.ts"
]); ]);
assert_eq!( assert_eq!(
@ -8139,7 +8139,7 @@ mod tests {
deny_net: Some(svec![ deny_net: Some(svec![
"deno.land", "deno.land",
"deno.land:80", "deno.land:80",
"::", "[::]",
"127.0.0.1", "127.0.0.1",
"[::1]", "[::1]",
"1.2.3.4:5678", "1.2.3.4:5678",

View file

@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::url::Url; use deno_core::url::Url;
use deno_runtime::deno_permissions::NetDescriptor;
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
@ -42,21 +43,17 @@ pub fn validator(host_and_port: &str) -> Result<String, String> {
/// `127.0.0.1:port` and `localhost:port`. /// `127.0.0.1:port` and `localhost:port`.
pub fn parse(paths: Vec<String>) -> clap::error::Result<Vec<String>> { pub fn parse(paths: Vec<String>) -> clap::error::Result<Vec<String>> {
let mut out: Vec<String> = vec![]; let mut out: Vec<String> = vec![];
for host_and_port in paths.iter() { for host_and_port in paths.into_iter() {
if Url::parse(&format!("internal://{host_and_port}")).is_ok() if let Ok(port) = host_and_port.parse::<BarePort>() {
|| host_and_port.parse::<IpAddr>().is_ok()
{
out.push(host_and_port.to_owned())
} else if let Ok(port) = host_and_port.parse::<BarePort>() {
// we got bare port, let's add default hosts // we got bare port, let's add default hosts
for host in ["0.0.0.0", "127.0.0.1", "localhost"].iter() { for host in ["0.0.0.0", "127.0.0.1", "localhost"].iter() {
out.push(format!("{}:{}", host, port.0)); out.push(format!("{}:{}", host, port.0));
} }
} else { } else {
return Err(clap::Error::raw( host_and_port.parse::<NetDescriptor>().map_err(|e| {
clap::error::ErrorKind::InvalidValue, clap::Error::raw(clap::error::ErrorKind::InvalidValue, format!("{e:?}"))
format!("Bad host:port pair: {host_and_port}"), })?;
)); out.push(host_and_port)
} }
} }
Ok(out) Ok(out)
@ -121,8 +118,8 @@ mod tests {
let entries = svec![ let entries = svec![
"deno.land", "deno.land",
"deno.land:80", "deno.land:80",
"::", "[::]",
"::1", "[::1]",
"127.0.0.1", "127.0.0.1",
"[::1]", "[::1]",
"1.2.3.4:5678", "1.2.3.4:5678",
@ -142,8 +139,8 @@ mod tests {
let expected = svec![ let expected = svec![
"deno.land", "deno.land",
"deno.land:80", "deno.land:80",
"::", "[::]",
"::1", "[::1]",
"127.0.0.1", "127.0.0.1",
"[::1]", "[::1]",
"1.2.3.4:5678", "1.2.3.4:5678",
@ -174,10 +171,8 @@ mod tests {
#[test] #[test]
fn parse_net_args_ipv6() { fn parse_net_args_ipv6() {
let entries = let entries = svec!["[::1]", "[::]:5678", "[::1]:5678"];
svec!["::", "::1", "[::1]", "[::]:5678", "[::1]:5678", "::cafe"]; let expected = svec!["[::1]", "[::]:5678", "[::1]:5678"];
let expected =
svec!["::", "::1", "[::1]", "[::]:5678", "[::1]:5678", "::cafe"];
let actual = parse(entries).unwrap(); let actual = parse(entries).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -190,12 +185,36 @@ mod tests {
#[test] #[test]
fn parse_net_args_ipv6_error2() { fn parse_net_args_ipv6_error2() {
let entries = svec!["0123:4567:890a:bcde:fg::"]; let entries = svec!["::1"];
assert!(parse(entries).is_err()); assert!(parse(entries).is_err());
} }
#[test] #[test]
fn parse_net_args_ipv6_error3() { 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"]; let entries = svec!["[::q]:8080"];
assert!(parse(entries).is_err()); assert!(parse(entries).is_err());
} }

View file

@ -4,10 +4,8 @@ use ::deno_permissions::parse_sys_kind;
use ::deno_permissions::PermissionState; use ::deno_permissions::PermissionState;
use ::deno_permissions::PermissionsContainer; use ::deno_permissions::PermissionsContainer;
use deno_core::error::custom_error; use deno_core::error::custom_error;
use deno_core::error::uri_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::op2; use deno_core::op2;
use deno_core::url;
use deno_core::OpState; use deno_core::OpState;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@ -65,7 +63,7 @@ pub fn op_query_permission(
"net" => permissions.net.query( "net" => permissions.net.query(
match args.host.as_deref() { match args.host.as_deref() {
None => None, None => None,
Some(h) => Some(parse_host(h)?), Some(h) => Some(h.parse()?),
} }
.as_ref(), .as_ref(),
), ),
@ -100,7 +98,7 @@ pub fn op_revoke_permission(
"net" => permissions.net.revoke( "net" => permissions.net.revoke(
match args.host.as_deref() { match args.host.as_deref() {
None => None, None => None,
Some(h) => Some(parse_host(h)?), Some(h) => Some(h.parse()?),
} }
.as_ref(), .as_ref(),
), ),
@ -135,7 +133,7 @@ pub fn op_request_permission(
"net" => permissions.net.request( "net" => permissions.net.request(
match args.host.as_deref() { match args.host.as_deref() {
None => None, None => None,
Some(h) => Some(parse_host(h)?), Some(h) => Some(h.parse()?),
} }
.as_ref(), .as_ref(),
), ),
@ -155,13 +153,3 @@ pub fn op_request_permission(
}; };
Ok(PermissionStatus::from(perm)) Ok(PermissionStatus::from(perm))
} }
fn parse_host(host_str: &str) -> Result<(String, Option<u16>), 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()))
}

View file

@ -16,7 +16,6 @@ use deno_core::url;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_terminal::colors; use deno_terminal::colors;
use fqdn::fqdn;
use fqdn::FQDN; use fqdn::FQDN;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::borrow::Cow; use std::borrow::Cow;
@ -25,6 +24,8 @@ use std::ffi::OsStr;
use std::fmt; use std::fmt;
use std::fmt::Debug; use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use std::net::IpAddr;
use std::net::Ipv6Addr;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
@ -691,13 +692,48 @@ impl Descriptor for WriteDescriptor {
} }
#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct NetDescriptor(pub FQDN, pub Option<u16>); pub enum Host {
Fqdn(FQDN),
Ip(IpAddr),
}
impl NetDescriptor { impl FromStr for Host {
fn new<T: AsRef<str>>(host: &&(T, Option<u16>)) -> Self { type Err = AnyError;
NetDescriptor(fqdn!(host.0.as_ref()), host.1)
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with('[') && s.ends_with(']') {
let ip = s[1..s.len() - 1]
.parse::<Ipv6Addr>()
.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::<IpAddr>() {
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<u16>);
impl Descriptor for NetDescriptor { impl Descriptor for NetDescriptor {
type Arg = String; type Arg = String;
@ -731,26 +767,72 @@ impl Descriptor for NetDescriptor {
impl FromStr for NetDescriptor { impl FromStr for NetDescriptor {
type Err = AnyError; type Err = AnyError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(hostname: &str) -> Result<Self, Self::Err> {
// Set the scheme to `unknown` to parse the URL, as we really don't know // If this is a IPv6 address enclosed in square brackets, parse it as such.
// what the scheme is. We only using Url::parse to parse the host and port if hostname.starts_with('[') {
// and don't care about the scheme. if let Some((ip, after)) = hostname.split_once(']') {
let url = url::Url::parse(&format!("unknown://{s}"))?; let ip = ip[1..].parse::<Ipv6Addr>().map_err(|_| {
let hostname = url uri_error(format!("invalid IPv6 address in '{hostname}': '{ip}'"))
.host_str() })?;
.ok_or(url::ParseError::EmptyHost)? let port = if let Some(port) = after.strip_prefix(':') {
.to_string(); let port = port.parse::<u16>().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::<Host>()?;
let port = if port.is_empty() {
None
} else {
let port = port.parse::<u16>().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 { impl fmt::Display for NetDescriptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&match self.1 { match &self.0 {
None => self.0.to_string(), Host::Fqdn(fqdn) => write!(f, "{fqdn}"),
Some(port) => format!("{}:{}", self.0, port), 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<WriteDescriptor> {
} }
impl UnaryPermission<NetDescriptor> { impl UnaryPermission<NetDescriptor> {
pub fn query<T: AsRef<str>>( pub fn query(&self, host: Option<&NetDescriptor>) -> PermissionState {
&self, self.query_desc(host, AllowPartial::TreatAsPartialGranted)
host: Option<&(T, Option<u16>)>,
) -> PermissionState {
self.query_desc(
host.map(|h| NetDescriptor::new(&h)).as_ref(),
AllowPartial::TreatAsPartialGranted,
)
} }
pub fn request<T: AsRef<str>>( pub fn request(&mut self, host: Option<&NetDescriptor>) -> PermissionState {
&mut self, self.request_desc(host, || None)
host: Option<&(T, Option<u16>)>,
) -> PermissionState {
self.request_desc(host.map(|h| NetDescriptor::new(&h)).as_ref(), || None)
} }
pub fn revoke<T: AsRef<str>>( pub fn revoke(&mut self, host: Option<&NetDescriptor>) -> PermissionState {
&mut self, self.revoke_desc(host)
host: Option<&(T, Option<u16>)>,
) -> PermissionState {
self.revoke_desc(host.map(|h| NetDescriptor::new(&h)).as_ref())
} }
pub fn check<T: AsRef<str>>( pub fn check(
&mut self, &mut self,
host: &(T, Option<u16>), host: &NetDescriptor,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self); 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( pub fn check_url(
@ -1146,17 +1216,14 @@ impl UnaryPermission<NetDescriptor> {
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
skip_check_if_is_permission_fully_granted!(self); skip_check_if_is_permission_fully_granted!(self);
let hostname = url let host = url
.host_str() .host_str()
.ok_or_else(|| uri_error("Missing host"))? .ok_or_else(|| type_error(format!("Missing host in url: '{}'", url)))?;
.to_string(); let host = host.parse::<Host>()?;
let host = &(&hostname, url.port_or_known_default()); let port = url.port_or_known_default();
let display_host = match url.port() { let descriptor = NetDescriptor(host, port);
None => hostname.clone(), self.check_desc(Some(&descriptor), false, api_name, || {
Some(port) => format!("{hostname}:{port}"), Some(format!("\"{descriptor}\""))
};
self.check_desc(Some(&NetDescriptor::new(&host)), false, api_name, || {
Some(format!("\"{}\"", display_host))
}) })
} }
@ -1782,7 +1849,9 @@ impl PermissionsContainer {
host: &(T, Option<u16>), host: &(T, Option<u16>),
api_name: &str, api_name: &str,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
self.0.lock().net.check(host, Some(api_name)) let hostname = host.0.as_ref().parse::<Host>()?;
let descriptor = NetDescriptor(hostname, host.1);
self.0.lock().net.check(&descriptor, Some(api_name))
} }
#[inline(always)] #[inline(always)]
@ -2209,7 +2278,9 @@ pub fn create_child_permissions(
mod tests { mod tests {
use super::*; use super::*;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use fqdn::fqdn;
use prompter::tests::*; use prompter::tests::*;
use std::net::Ipv4Addr;
// Creates vector of strings, Vec<String> // Creates vector of strings, Vec<String>
macro_rules! svec { macro_rules! svec {
@ -2363,12 +2434,12 @@ mod tests {
]; ];
for (host, port, is_ok) in domain_tests { for (host, port, is_ok) in domain_tests {
let host = host.parse().unwrap();
let descriptor = NetDescriptor(host, Some(port));
assert_eq!( assert_eq!(
is_ok, is_ok,
perms.net.check(&(host, Some(port)), None).is_ok(), perms.net.check(&descriptor, None).is_ok(),
"{}:{}", "{descriptor}",
host,
port,
); );
} }
} }
@ -2404,8 +2475,13 @@ mod tests {
("192.168.0.1", 0), ("192.168.0.1", 0),
]; ];
for (host, port) in domain_tests { for (host_str, port) in domain_tests {
assert!(perms.net.check(&(host, Some(port)), None).is_ok()); 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), ("192.168.0.1", 0),
]; ];
for (host, port) in domain_tests { for (host_str, port) in domain_tests {
assert!(perms.net.check(&(host, Some(port)), None).is_err()); 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"))), PermissionState::Denied);
assert_eq!(perms4.ffi.query(Some(Path::new("/foo/bar"))), 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!(perms4.ffi.query(Some(Path::new("/bar"))), PermissionState::Granted);
assert_eq!(perms1.net.query::<&str>(None), PermissionState::Granted); assert_eq!(perms1.net.query(None), PermissionState::Granted);
assert_eq!(perms1.net.query(Some(&("127.0.0.1", None))), PermissionState::Granted); assert_eq!(perms1.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), None))), PermissionState::Granted);
assert_eq!(perms2.net.query::<&str>(None), PermissionState::Prompt); assert_eq!(perms2.net.query(None), PermissionState::Prompt);
assert_eq!(perms2.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); assert_eq!(perms2.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Granted);
assert_eq!(perms3.net.query::<&str>(None), PermissionState::Prompt); assert_eq!(perms3.net.query(None), PermissionState::Prompt);
assert_eq!(perms3.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Denied); assert_eq!(perms3.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Denied);
assert_eq!(perms4.net.query::<&str>(None), PermissionState::GrantedPartial); assert_eq!(perms4.net.query(None), PermissionState::GrantedPartial);
assert_eq!(perms4.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Denied); assert_eq!(perms4.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)))), PermissionState::Denied);
assert_eq!(perms4.net.query(Some(&("192.168.0.1", Some(8000)))), PermissionState::Granted); 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(None), PermissionState::Granted);
assert_eq!(perms1.env.query(Some("HOME")), PermissionState::Granted); assert_eq!(perms1.env.query(Some("HOME")), PermissionState::Granted);
assert_eq!(perms2.env.query(None), PermissionState::Prompt); assert_eq!(perms2.env.query(None), PermissionState::Prompt);
@ -2782,9 +2863,9 @@ mod tests {
prompt_value.set(true); prompt_value.set(true);
assert_eq!(perms.ffi.request(None), PermissionState::Denied); assert_eq!(perms.ffi.request(None), PermissionState::Denied);
prompt_value.set(true); 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); 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); prompt_value.set(true);
assert_eq!(perms.env.request(Some("HOME")), PermissionState::Granted); assert_eq!(perms.env.request(Some("HOME")), PermissionState::Granted);
assert_eq!(perms.env.query(None), PermissionState::Prompt); 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.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"))), PermissionState::Prompt);
assert_eq!(perms.ffi.query(Some(Path::new("/foo/baz"))), PermissionState::Granted); 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.revoke(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), Some(9000)))), PermissionState::Prompt);
assert_eq!(perms.net.query(Some(&("127.0.0.1", None))), PermissionState::Prompt); assert_eq!(perms.net.query(Some(&NetDescriptor("127.0.0.1".parse().unwrap(), None))), PermissionState::Prompt);
assert_eq!(perms.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); 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("HOME")), PermissionState::Prompt);
assert_eq!(perms.env.revoke(Some("hostname")), PermissionState::Prompt); assert_eq!(perms.env.revoke(Some("hostname")), PermissionState::Prompt);
assert_eq!(perms.run.revoke(Some("deno")), 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()); assert!(perms.ffi.check(Path::new("/bar"), None).is_err());
prompt_value.set(true); 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); prompt_value.set(false);
assert!(perms.net.check(&("127.0.0.1", Some(8000)), None).is_ok()); assert!(perms
assert!(perms.net.check(&("127.0.0.1", Some(8001)), None).is_err()); .net
assert!(perms.net.check(&("127.0.0.1", None), None).is_err()); .check(
assert!(perms.net.check(&("deno.land", Some(8000)), None).is_err()); &NetDescriptor("127.0.0.1".parse().unwrap(), Some(8000)),
assert!(perms.net.check(&("deno.land", None), None).is_err()); 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); prompt_value.set(true);
assert!(perms.run.check("cat", None).is_ok()); assert!(perms.run.check("cat", None).is_ok());
@ -2948,14 +3059,50 @@ mod tests {
assert!(perms.ffi.check(Path::new("/bar"), None).is_ok()); assert!(perms.ffi.check(Path::new("/bar"), None).is_ok());
prompt_value.set(false); 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); prompt_value.set(true);
assert!(perms.net.check(&("127.0.0.1", Some(8000)), None).is_err()); assert!(perms
assert!(perms.net.check(&("127.0.0.1", Some(8001)), None).is_ok()); .net
assert!(perms.net.check(&("deno.land", Some(8000)), None).is_ok()); .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); prompt_value.set(false);
assert!(perms.net.check(&("127.0.0.1", Some(8001)), None).is_ok()); assert!(perms
assert!(perms.net.check(&("deno.land", Some(8000)), None).is_ok()); .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); prompt_value.set(false);
assert!(perms.run.check("cat", None).is_err()); assert!(perms.run.check("cat", None).is_err());
@ -3044,10 +3191,28 @@ mod tests {
..Permissions::none_without_prompt() ..Permissions::none_without_prompt()
}; };
perms.net.check(&("allowed.domain.", None), None).unwrap(); perms
perms.net.check(&("1.1.1.1.", None), None).unwrap(); .net
assert!(perms.net.check(&("denied.domain.", None), None).is_err()); .check(
assert!(perms.net.check(&("2.2.2.2.", None), None).is_err()); &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] #[test]
@ -3333,4 +3498,109 @@ mod tests {
) )
.is_err()); .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::<Host>().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::<NetDescriptor>().ok(), *expected, "'{input}'");
}
}
} }

View file

@ -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 at blob:null/[WILDCARD]:1:8
await import(URL.createObjectURL(blob)); await import(URL.createObjectURL(blob));
^ ^

View file

@ -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 at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8
await import(`data:application/javascript;base64,${btoa(code)}`); await import(`data:application/javascript;base64,${btoa(code)}`);
^ ^

View file

@ -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 at http://localhost:4545/dynamic_import/static_remote.ts:2:8
await import( await import(
^ ^

View file

@ -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 at blob:null/[WILDCARD]:1:8
error: Uncaught (in promise) Error: Unhandled error in child worker. error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl[WILDCARD] at Worker.#pollControl[WILDCARD]

View file

@ -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 at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8
error: Uncaught (in promise) Error: Unhandled error in child worker. error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl[WILDCARD] at Worker.#pollControl[WILDCARD]

View file

@ -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"); await import("" + "https://example.com/some/file.ts");
^ ^
at async http://localhost:4545/workers/dynamic_remote.ts:2:1 at async http://localhost:4545/workers/dynamic_remote.ts:2:1

View file

@ -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 at http://localhost:4545/workers/static_remote.ts:2:8
error: Uncaught (in promise) Error: Unhandled error in child worker. error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl [WILDCARD] at Worker.#pollControl [WILDCARD]