1use std::fs::File;
4use std::io::{self, Read};
5use std::net::IpAddr;
6use std::path::{Path, PathBuf};
7
8#[derive(Clone, Debug)]
10pub struct HostTable {
11 pub hosts: Vec<Host>,
13}
14
15impl HostTable {
16 pub fn find_address(&self, name: &str) -> Option<IpAddr> {
20 self.find_host_by_name(name).map(|h| h.address)
21 }
22
23 pub fn find_name(&self, addr: IpAddr) -> Option<&str> {
28 self.find_host_by_address(addr).map(|h| &h.name[..])
29 }
30
31 pub fn find_host_by_address(&self, addr: IpAddr) -> Option<&Host> {
35 self.hosts.iter().find(|h| h.address == addr)
36 }
37
38 pub fn find_host_by_name(&self, name: &str) -> Option<&Host> {
42 self.hosts
43 .iter()
44 .find(|h| h.name == name || h.aliases.iter().any(|a| a == name))
45 }
46}
47
48#[derive(Clone, Debug)]
50pub struct Host {
51 pub address: IpAddr,
53 pub name: String,
55 pub aliases: Vec<String>,
57}
58
59pub fn host_file() -> PathBuf {
61 host_file_impl()
62}
63
64#[cfg(unix)]
65fn host_file_impl() -> PathBuf {
66 PathBuf::from("/etc/hosts")
67}
68
69#[cfg(windows)]
70fn host_file_impl() -> PathBuf {
71 use std::env::var_os;
72
73 match var_os("SystemRoot") {
74 Some(root) => PathBuf::from(root).join("System32/drivers/etc/hosts"),
75 None => PathBuf::from("C:/Windows/System32/drivers/etc/hosts"),
78 }
79}
80
81pub fn load_hosts(path: &Path) -> io::Result<HostTable> {
86 let mut f = File::open(path)?;
87 let mut buf = String::new();
88
89 f.read_to_string(&mut buf)?;
90 parse_host_table(&buf)
91}
92
93pub fn parse_host_table(data: &str) -> io::Result<HostTable> {
95 let mut hosts = Vec::new();
96
97 for line in data.lines() {
98 let mut line = line;
99
100 if let Some(pos) = line.find('#') {
101 line = &line[..pos];
102 }
103
104 let mut words = line.split_whitespace();
105
106 let addr_str = match words.next() {
107 Some(w) => w,
108 None => continue,
109 };
110
111 let addr = match addr_str.parse() {
112 Ok(addr) => addr,
113 Err(_) => {
114 return Err(io::Error::new(
115 io::ErrorKind::InvalidData,
116 format!("invalid address: {}", addr_str),
117 ))
118 }
119 };
120
121 let name = match words.next() {
122 Some(w) => w,
123 None => return Err(io::Error::new(io::ErrorKind::InvalidData, "missing names")),
124 };
125
126 hosts.push(Host {
127 address: addr,
128 name: name.to_owned(),
129 aliases: words.map(|s| s.to_owned()).collect(),
130 });
131 }
132
133 Ok(HostTable { hosts })
134}
135
136#[cfg(test)]
137mod test {
138 use super::parse_host_table;
139 use std::net::IpAddr;
140
141 fn ip(s: &str) -> IpAddr {
142 s.parse().unwrap()
143 }
144
145 #[test]
146 fn test_hosts() {
147 let hosts = parse_host_table(
148 "\
149# Comment line
150127.0.0.1 localhost
151::1 ip6-localhost
152
153192.168.10.1 foo foo.bar foo.local # Mid-line comment
154",
155 )
156 .unwrap();
157
158 assert_eq!(hosts.find_address("localhost"), Some(ip("127.0.0.1")));
159 assert_eq!(hosts.find_address("ip6-localhost"), Some(ip("::1")));
160 assert_eq!(hosts.find_name(ip("192.168.10.1")), Some("foo"));
161
162 assert_eq!(hosts.find_address("missing"), None);
163 assert_eq!(hosts.find_name(ip("0.0.0.0")), None);
164
165 let host = hosts.find_host_by_address(ip("192.168.10.1")).unwrap();
166
167 assert_eq!(host.address, ip("192.168.10.1"));
168 assert_eq!(host.name, "foo");
169 assert_eq!(host.aliases, ["foo.bar", "foo.local"]);
170 }
171}