Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reland "fix(cli): ipv6 parsing for --allow-net params (#6453)" #6472

Merged
merged 7 commits into from Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 33 additions & 36 deletions cli/flags.rs
@@ -1,4 +1,5 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::resolve_hosts::resolve_hosts;
ry marked this conversation as resolved.
Show resolved Hide resolved
use clap::App;
use clap::AppSettings;
use clap::Arg;
Expand Down Expand Up @@ -1053,7 +1054,7 @@ Grant permission to read from disk and listen to network:

Grant permission to read allow-listed files from disk:
deno run --allow-read=/etc https://deno.land/std/http/file_server.ts

Deno allows specifying the filename '-' to read the file from stdin.
curl https://deno.land/std/examples/welcome.ts | target/debug/deno run -",
)
Expand Down Expand Up @@ -1375,41 +1376,6 @@ pub fn resolve_urls(urls: Vec<String>) -> Vec<String> {
out
}

/// Expands "bare port" paths (eg. ":8080") into full paths with hosts. It
/// expands to such paths into 3 paths with following hosts: `0.0.0.0:port`,
/// `127.0.0.1:port` and `localhost:port`.
fn resolve_hosts(paths: Vec<String>) -> Vec<String> {
let mut out: Vec<String> = vec![];
for host_and_port in paths.iter() {
let parts = host_and_port.split(':').collect::<Vec<&str>>();

match parts.len() {
// host only
1 => {
out.push(host_and_port.to_owned());
}
// host and port (NOTE: host might be empty string)
2 => {
let host = parts[0];
let port = parts[1];

if !host.is_empty() {
out.push(host_and_port.to_owned());
continue;
}

// 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));
}
}
_ => panic!("Bad host:port pair: {}", host_and_port),
}
}

out
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -2481,6 +2447,37 @@ mod tests {
);
}

#[test]
fn allow_net_allowlist_with_ipv6_address() {
let r = flags_from_vec_safe(svec![
"deno",
"run",
"--allow-net=deno.land,deno.land:80,::,127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080",
"script.ts"
]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run {
script: "script.ts".to_string(),
},
net_allowlist: svec![
"deno.land",
"deno.land:80",
"::",
"127.0.0.1",
"[::1]",
"1.2.3.4:5678",
"0.0.0.0:5678",
"127.0.0.1:5678",
"localhost:5678",
"[::1]:8080"
],
..Flags::default()
}
);
}

#[test]
fn lock_write() {
let r = flags_from_vec_safe(svec![
Expand Down
1 change: 1 addition & 0 deletions cli/main.rs
Expand Up @@ -52,6 +52,7 @@ pub mod ops;
pub mod permissions;
mod repl;
pub mod resolve_addr;
pub mod resolve_hosts;
pub mod signal;
pub mod source_maps;
mod startup_data;
Expand Down
188 changes: 188 additions & 0 deletions cli/resolve_hosts.rs
@@ -0,0 +1,188 @@
use std::net::IpAddr;
use std::str::FromStr;
use url::Url;

#[derive(Debug, PartialEq, Eq)]
pub struct ParsePortError(String);

#[derive(Debug, PartialEq, Eq)]
pub struct BarePort(u16);

impl FromStr for BarePort {
type Err = ParsePortError;
fn from_str(s: &str) -> Result<BarePort, ParsePortError> {
if s.starts_with(':') {
match s.split_at(1).1.parse::<u16>() {
Ok(port) => Ok(BarePort(port)),
Err(e) => Err(ParsePortError(e.to_string())),
}
} else {
Err(ParsePortError(
"Bare Port doesn't start with ':'".to_string(),
))
}
}
}

/// Expands "bare port" paths (eg. ":8080") into full paths with hosts. It
/// expands to such paths into 3 paths with following hosts: `0.0.0.0:port`,
/// `127.0.0.1:port` and `localhost:port`.
pub fn resolve_hosts(paths: Vec<String>) -> Vec<String> {
ry marked this conversation as resolved.
Show resolved Hide resolved
let mut out: Vec<String> = vec![];
for host_and_port in paths.iter() {
if Url::parse(&format!("deno://{}", host_and_port)).is_ok()
|| 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
for host in ["0.0.0.0", "127.0.0.1", "localhost"].iter() {
out.push(format!("{}:{}", host, port.0));
}
} else {
panic!("Bad host:port pair: {}", host_and_port)
ry marked this conversation as resolved.
Show resolved Hide resolved
}
}
out
}

#[cfg(test)]
mod bare_port_tests {
use super::{BarePort, ParsePortError};

#[test]
fn bare_port_parsed() {
let expected = BarePort(8080);
let actual = ":8080".parse::<BarePort>();
assert_eq!(actual, Ok(expected));
}

#[test]
fn bare_port_parse_error1() {
let expected =
ParsePortError("Bare Port doesn't start with ':'".to_string());
let actual = "8080".parse::<BarePort>();
assert_eq!(actual, Err(expected));
}

#[test]
fn bare_port_parse_error2() {
let actual = ":65536".parse::<BarePort>();
assert!(actual.is_err());
}

#[test]
fn bare_port_parse_error3() {
let actual = ":14u16".parse::<BarePort>();
assert!(actual.is_err());
}

#[test]
fn bare_port_parse_error4() {
let actual = "Deno".parse::<BarePort>();
assert!(actual.is_err());
}

#[test]
fn bare_port_parse_error5() {
let actual = "deno.land:8080".parse::<BarePort>();
assert!(actual.is_err());
}
}

#[cfg(test)]
mod tests {
use super::resolve_hosts;

// Creates vector of strings, Vec<String>
macro_rules! svec {
($($x:expr),*) => (vec![$($x.to_string()),*]);
}

#[test]
fn resolve_hosts_() {
let entries = svec![
"deno.land",
"deno.land:80",
"::",
"::1",
"127.0.0.1",
"[::1]",
"1.2.3.4:5678",
"0.0.0.0:5678",
"127.0.0.1:5678",
"[::]:5678",
"[::1]:5678",
"localhost:5678",
"[::1]:8080",
"[::]:8000",
"[::1]:8000",
"localhost:8000",
"0.0.0.0:4545",
"127.0.0.1:4545",
"999.0.88.1:80"
];
let expected = svec![
"deno.land",
"deno.land:80",
"::",
"::1",
"127.0.0.1",
"[::1]",
"1.2.3.4:5678",
"0.0.0.0:5678",
"127.0.0.1:5678",
"[::]:5678",
"[::1]:5678",
"localhost:5678",
"[::1]:8080",
"[::]:8000",
"[::1]:8000",
"localhost:8000",
"0.0.0.0:4545",
"127.0.0.1:4545",
"999.0.88.1:80"
];
let actual = resolve_hosts(entries);
assert_eq!(actual, expected);
}

#[test]
fn resolve_hosts_expansion() {
let entries = svec![":8080"];
let expected = svec!["0.0.0.0:8080", "127.0.0.1:8080", "localhost:8080"];
let actual = resolve_hosts(entries);
assert_eq!(actual, expected);
}

#[test]
fn resolve_hosts_ipv6() {
let entries =
svec!["::", "::1", "[::1]", "[::]:5678", "[::1]:5678", "::cafe"];
let expected =
svec!["::", "::1", "[::1]", "[::]:5678", "[::1]:5678", "::cafe"];
let actual = resolve_hosts(entries);
assert_eq!(actual, expected);
}

#[test]
#[should_panic]
fn resolve_hosts_ipv6_error1() {
let entries = svec![":::"];
resolve_hosts(entries);
}

#[test]
#[should_panic]
fn resolve_hosts_ipv6_error2() {
let entries = svec!["0123:4567:890a:bcde:fg::"];
resolve_hosts(entries);
}

#[test]
#[should_panic]
fn resolve_hosts_ipv6_error3() {
let entries = svec!["[::q]:8080"];
resolve_hosts(entries);
}
}
12 changes: 10 additions & 2 deletions cli/tests/complex_permissions_test.ts
Expand Up @@ -14,7 +14,11 @@ const test: { [key: string]: Function } = {
},
netListen(endpoints: string[]): void {
endpoints.forEach((endpoint) => {
const [hostname, port] = endpoint.split(":");
const index = endpoint.lastIndexOf(":");
const [hostname, port] = [
endpoint.substr(0, index),
endpoint.substr(index + 1),
];
const listener = Deno.listen({
transport: "tcp",
hostname,
Expand All @@ -25,7 +29,11 @@ const test: { [key: string]: Function } = {
},
async netConnect(endpoints: string[]): Promise<void> {
for (const endpoint of endpoints) {
const [hostname, port] = endpoint.split(":");
const index = endpoint.lastIndexOf(":");
const [hostname, port] = [
endpoint.substr(0, index),
endpoint.substr(index + 1),
];
const listener = await Deno.connect({
transport: "tcp",
hostname,
Expand Down