sync_resolve/
hosts.rs

1//! Implements parsing the system hosts file to produce a host table
2
3use std::fs::File;
4use std::io::{self, Read};
5use std::net::IpAddr;
6use std::path::{Path, PathBuf};
7
8/// Represents a host table, consisting of addresses mapped to names.
9#[derive(Clone, Debug)]
10pub struct HostTable {
11    /// Contained hosts
12    pub hosts: Vec<Host>,
13}
14
15impl HostTable {
16    /// Returns the address for the first host matching the given name.
17    ///
18    /// If no match is found, `None` is returned.
19    pub fn find_address(&self, name: &str) -> Option<IpAddr> {
20        self.find_host_by_name(name).map(|h| h.address)
21    }
22
23    /// Returns the canonical name for the first host matching the given
24    /// address.
25    ///
26    /// If no match is found, `None` is returned.
27    pub fn find_name(&self, addr: IpAddr) -> Option<&str> {
28        self.find_host_by_address(addr).map(|h| &h.name[..])
29    }
30
31    /// Returns the first host matching the given address.
32    ///
33    /// If no match is found, `None` is returned.
34    pub fn find_host_by_address(&self, addr: IpAddr) -> Option<&Host> {
35        self.hosts.iter().find(|h| h.address == addr)
36    }
37
38    /// Returns the first host matching the given address.
39    ///
40    /// If no match is found, `None` is returned.
41    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/// Represents a single host within a host table.
49#[derive(Clone, Debug)]
50pub struct Host {
51    /// Host address
52    pub address: IpAddr,
53    /// Canonical host name
54    pub name: String,
55    /// Host aliases
56    pub aliases: Vec<String>,
57}
58
59/// Returns the absolute path to the system hosts file.
60pub 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        // I'm not sure if this is the "correct" thing to do,
76        // but it seems like a better alternative than panicking.
77        None => PathBuf::from("C:/Windows/System32/drivers/etc/hosts"),
78    }
79}
80
81/// Loads a host table from the given filename.
82///
83/// If an error is encountered in opening the file or reading its contents
84/// or if the file is malformed, the error is returned.
85pub 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
93/// Attempts to parse a host table in the hosts file format.
94pub 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}