summaryrefslogtreecommitdiff
path: root/cli/resolve_addr.rs
blob: b783444d86dfa71ab2d6c0e0f76355a6e4747a17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::deno_error;
use deno::ErrBox;
use futures::Async;
use futures::Future;
use futures::Poll;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;

/// Go-style network address parsing. Returns a future.
/// Examples:
/// "192.0.2.1:25"
/// ":80"
/// "[2001:db8::1]:80"
/// "198.51.100.1:80"
/// "deno.land:443"
pub fn resolve_addr(address: &str) -> ResolveAddrFuture {
  ResolveAddrFuture {
    address: address.to_string(),
  }
}

pub struct ResolveAddrFuture {
  address: String,
}

impl Future for ResolveAddrFuture {
  type Item = SocketAddr;
  type Error = ErrBox;

  fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
    // The implementation of this is not actually async at the moment,
    // however we intend to use async DNS resolution in the future and
    // so we expose this as a future instead of Result.
    match split(&self.address) {
      None => Err(deno_error::invalid_address_syntax()),
      Some(addr_port_pair) => {
        // I absolutely despise the .to_socket_addrs() API.
        let r = addr_port_pair.to_socket_addrs().map_err(ErrBox::from);

        r.and_then(|mut iter| match iter.next() {
          Some(a) => Ok(Async::Ready(a)),
          None => panic!("There should be at least one result"),
        })
      }
    }
  }
}

fn split(address: &str) -> Option<(&str, u16)> {
  address.rfind(':').and_then(|i| {
    let (a, p) = address.split_at(i);
    // Default to localhost if given just the port. Example: ":80"
    let addr = if !a.is_empty() { a } else { "0.0.0.0" };
    // If this looks like an ipv6 IP address. Example: "[2001:db8::1]"
    // Then we remove the brackets.
    let addr = if addr.starts_with('[') && addr.ends_with(']') {
      let l = addr.len() - 1;
      addr.get(1..l).unwrap()
    } else {
      addr
    };

    let p = p.trim_start_matches(':');
    match p.parse::<u16>() {
      Err(_) => None,
      Ok(port) => Some((addr, port)),
    }
  })
}

#[cfg(test)]
mod tests {
  use super::*;
  use std::net::Ipv4Addr;
  use std::net::Ipv6Addr;
  use std::net::SocketAddrV4;
  use std::net::SocketAddrV6;

  #[test]
  fn split1() {
    assert_eq!(split("127.0.0.1:80"), Some(("127.0.0.1", 80)));
  }

  #[test]
  fn split2() {
    assert_eq!(split(":80"), Some(("0.0.0.0", 80)));
  }

  #[test]
  fn split3() {
    assert_eq!(split("no colon"), None);
  }

  #[test]
  fn split4() {
    assert_eq!(split("deno.land:443"), Some(("deno.land", 443)));
  }

  #[test]
  fn split5() {
    assert_eq!(split("[2001:db8::1]:8080"), Some(("2001:db8::1", 8080)));
  }

  #[test]
  fn resolve_addr1() {
    let expected =
      SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80));
    let actual = resolve_addr("127.0.0.1:80").wait().unwrap();
    assert_eq!(actual, expected);
  }

  #[test]
  fn resolve_addr3() {
    let expected =
      SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25));
    let actual = resolve_addr("192.0.2.1:25").wait().unwrap();
    assert_eq!(actual, expected);
  }

  #[test]
  fn resolve_addr_ipv6() {
    let expected = SocketAddr::V6(SocketAddrV6::new(
      Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
      8080,
      0,
      0,
    ));
    let actual = resolve_addr("[2001:db8::1]:8080").wait().unwrap();
    assert_eq!(actual, expected);
  }
}