hostsfile: support Windows (#79)

Windows has some peculiarities for example it only allows one hostname per line
and the file's location depends on an environment variable. Although in most
cases just using C:\Windows\ for %WinDir% would probably work.

Note that editing the hosts file on Windows will require running with elevated
privileges ("Run as Administrator") and in some cases also antivirus may block
access to the file.
pull/88/head
Johann150 2021-05-19 17:23:56 +02:00 committed by GitHub
parent 08b975e847
commit 7d3529f600
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 30 additions and 3 deletions

View File

@ -70,6 +70,14 @@ impl std::error::Error for Error {
/// 1.1.1.1 cloudflare-dns apnic-dns /// 1.1.1.1 cloudflare-dns apnic-dns
/// # DO NOT EDIT dns END /// # DO NOT EDIT dns END
/// ``` /// ```
///
/// On Windows the host file format is slightly different in this case:
/// ```text
/// # DO NOT EDIT dns BEGIN
/// 1.1.1.1 cloudflare-dns
/// 1.1.1.1 apnic-dns
/// # DO NOT EDIT dns END
/// ```
pub struct HostsBuilder { pub struct HostsBuilder {
tag: String, tag: String,
hostname_map: HashMap<IpAddr, Vec<String>>, hostname_map: HashMap<IpAddr, Vec<String>>,
@ -106,11 +114,19 @@ impl HostsBuilder {
} }
/// Inserts a new section to the system's default hosts file. If there is a section with the /// Inserts a new section to the system's default hosts file. If there is a section with the
/// same tag name already, it will be replaced with the new list instead. Only Unix systems are /// same tag name already, it will be replaced with the new list instead.
/// supported now.
pub fn write(&self) -> Result<()> { pub fn write(&self) -> Result<()> {
let hosts_file = if cfg!(unix) { let hosts_file = if cfg!(unix) {
PathBuf::from("/etc/hosts") PathBuf::from("/etc/hosts")
} else if cfg!(windows) {
PathBuf::from(
// according to https://support.microsoft.com/en-us/topic/how-to-reset-the-hosts-file-back-to-the-default-c2a43f9d-e176-c6f3-e4ef-3500277a6dae
// the location depends on the environment variable %WinDir%.
format!(
"{}\\System32\\Drivers\\Etc\\hosts",
std::env::var("WinDir")?
),
)
} else { } else {
return Err(Box::new(Error("unsupported operating system.".to_owned()))); return Err(Box::new(Error("unsupported operating system.".to_owned())));
}; };
@ -127,6 +143,9 @@ impl HostsBuilder {
/// Inserts a new section to the specified hosts file. If there is a section with the same tag /// Inserts a new section to the specified hosts file. If there is a section with the same tag
/// name already, it will be replaced with the new list instead. /// name already, it will be replaced with the new list instead.
///
/// On Windows, the format of one hostname per line will be used, all other systems will use
/// the same format as Unix and Unix-like systems (i.e. allow multiple hostnames per line).
pub fn write_to<P: AsRef<Path>>(&self, hosts_path: P) -> Result<()> { pub fn write_to<P: AsRef<Path>>(&self, hosts_path: P) -> Result<()> {
let hosts_path = hosts_path.as_ref(); let hosts_path = hosts_path.as_ref();
let begin_marker = format!("# DO NOT EDIT {} BEGIN", &self.tag); let begin_marker = format!("# DO NOT EDIT {} BEGIN", &self.tag);
@ -185,8 +204,16 @@ impl HostsBuilder {
if !self.hostname_map.is_empty() { if !self.hostname_map.is_empty() {
writeln!(&mut file, "{}", begin_marker)?; writeln!(&mut file, "{}", begin_marker)?;
for (ip, hostnames) in &self.hostname_map { for (ip, hostnames) in &self.hostname_map {
if cfg!(windows) {
// windows only allows one hostname per line
for hostname in hostnames {
writeln!(&mut file, "{} {}", ip, hostname)?;
}
} else {
// assume the same format as Unix
writeln!(&mut file, "{} {}", ip, hostnames.join(" "))?; writeln!(&mut file, "{} {}", ip, hostnames.join(" "))?;
} }
}
writeln!(&mut file, "{}", end_marker)?; writeln!(&mut file, "{}", end_marker)?;
} }
for line in &lines[insert..] { for line in &lines[insert..] {