use anyhow::{bail, Context, Result};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::Path;
use std::str::{FromStr, SplitAsciiWhitespace};
use tracing::{trace, warn};
use crate::filter::path;
pub struct FilterFile {
pub info: Option<FileInfo>,
pub content: FilterContent,
}
#[derive(Clone, Debug)]
pub struct FileInfo {
pub source_path: String,
pub local_path: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FilterEntry {
pub line_num: Option<usize>,
pub dest_upstream: bool,
pub dest_ipv4: Option<Ipv4Addr>,
pub dest_ipv6: Option<Ipv6Addr>,
}
pub struct FilterContent {
entries: HashMap<String, FilterEntry>,
}
impl FilterContent {
fn new() -> FilterContent {
FilterContent {
entries: HashMap::new(),
}
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn get(&self, host: &str) -> Option<&FilterEntry> {
self.entries.get(host)
}
fn add_allow_line(
&mut self,
source_info: &FileInfo,
line_num: usize,
host: &str,
) -> Result<()> {
let host = validate_host(host)?;
self.entries.insert(
host,
FilterEntry {
line_num: Some(line_num),
dest_upstream: true,
dest_ipv4: None,
dest_ipv6: None,
},
);
Ok(())
}
fn add_block_line(
&mut self,
source_info: &FileInfo,
line_num: usize,
host: &str,
) -> Result<()> {
let host = validate_host(host)?;
self.entries.insert(
host,
FilterEntry {
line_num: Some(line_num),
dest_upstream: false,
dest_ipv4: None,
dest_ipv6: None,
},
);
Ok(())
}
fn add_block_hardcoded(&mut self, host: &str) {
self.entries.insert(
host.to_string(),
FilterEntry {
line_num: None,
dest_upstream: false,
dest_ipv4: None,
dest_ipv6: None,
},
);
}
fn add_override_ipv4_line(
&mut self,
source_info: &FileInfo,
line_num: usize,
host: &str,
dest: Ipv4Addr,
) -> Result<()> {
let host = validate_host(host)?;
let initial_val = FilterEntry {
line_num: Some(line_num),
dest_upstream: false,
dest_ipv4: Some(dest),
dest_ipv6: None,
};
let map_val = self.entries.entry(host).or_insert(initial_val);
map_val.line_num = Some(line_num);
map_val.dest_ipv4 = Some(dest);
Ok(())
}
fn add_override_ipv6_line(
&mut self,
source_info: &FileInfo,
line_num: usize,
host: &str,
dest: Ipv6Addr,
) -> Result<()> {
let host = validate_host(host)?;
let initial_val = FilterEntry {
line_num: Some(line_num),
dest_upstream: false,
dest_ipv4: None,
dest_ipv6: Some(dest),
};
let map_val = self.entries.entry(host).or_insert(initial_val);
map_val.line_num = Some(line_num);
map_val.dest_ipv6 = Some(dest);
Ok(())
}
}
pub fn block_hardcoded(block_names: &[&str]) -> Result<FilterFile> {
let mut content = FilterContent::new();
for name in block_names {
content.add_block_hardcoded(name);
}
Ok(FilterFile {
info: None,
content,
})
}
pub fn read_override(info: &FileInfo) -> Result<FilterFile> {
let path_str = info.local_path.clone();
let path = Path::new(&path_str);
let file = File::open(path).with_context(|| format!("Failed to open file {:?}", info))?;
if path::is_zstd_extension(path) {
read_override_imp(info, zstd::stream::Decoder::new(file)?)
} else {
read_override_imp(info, file)
}
}
fn read_override_imp<T: Read>(info: &FileInfo, file: T) -> Result<FilterFile> {
let mut reader = BufReader::new(file);
let mut buf = String::new();
let mut line_num = 0;
let mut content = FilterContent::new();
loop {
line_num += 1;
let len = reader
.read_line(&mut buf)
.with_context(|| format!("Failed to read file {:?}", info))?;
if len == 0 {
return Ok(FilterFile {
info: Some(info.clone()),
content,
});
} else {
match handle_override_line(&buf, line_num, &mut content, &info) {
Ok(()) => {}
Err(e) => warn!("Failed to parse {:?} line {}: {}", info, line_num, e),
};
buf.clear();
}
}
}
fn handle_override_line(
line: &str,
line_num: usize,
out: &mut FilterContent,
info: &FileInfo,
) -> Result<()> {
let mut words = tokenize(line);
if let Some(first) = words.next() {
if let Some(second) = words.next() {
if let Some(_) = first.find(':') {
let ipv6_dest = Ipv6Addr::from_str(first)
.with_context(|| format!("Failed to parse IPv6 address: {}", first))?;
out.add_override_ipv6_line(&info, line_num, second, ipv6_dest)?;
for word in words {
out.add_override_ipv6_line(&info, line_num, word, ipv6_dest)?;
}
Ok(())
} else {
let ipv4_dest = Ipv4Addr::from_str(first)
.with_context(|| format!("Failed to parse IPv4 address: {}", first))?;
out.add_override_ipv4_line(&info, line_num, second, ipv4_dest)?;
for word in words {
out.add_override_ipv4_line(&info, line_num, word, ipv4_dest)?;
}
Ok(())
}
} else {
bail!("Unexpected block-style entry in override rule: {}", first);
}
} else {
Ok(())
}
}
pub fn read_block(info: &FileInfo) -> Result<FilterFile> {
let path_str = info.local_path.clone();
let path = Path::new(&path_str);
let file = File::open(path).with_context(|| format!("Failed to open file {:?}", info))?;
if path::is_zstd_extension(path) {
read_block_imp(info, zstd::stream::Decoder::new(file)?)
} else {
read_block_imp(info, file)
}
}
fn read_block_imp<T: Read>(info: &FileInfo, file: T) -> Result<FilterFile> {
let mut reader = BufReader::new(file);
let mut buf = String::new();
let mut line_num = 0;
let mut content = FilterContent::new();
loop {
line_num += 1;
let len = reader
.read_line(&mut buf)
.with_context(|| format!("Failed to read file {:?}", info))?;
if len == 0 {
return Ok(FilterFile {
info: Some(info.clone()),
content,
});
} else {
match handle_block_line(&buf, line_num, &mut content, &info) {
Ok(()) => {}
Err(e) => warn!("Failed to parse {:?} line {}: {}", info, line_num, e),
};
buf.clear();
}
}
}
fn handle_block_line(
line: &str,
line_num: usize,
out: &mut FilterContent,
info: &FileInfo,
) -> Result<()> {
let mut words = tokenize(line);
if let Some(first) = words.next() {
if let Some(second) = words.next() {
out.add_block_line(&info, line_num, second)?;
for word in words {
out.add_block_line(&info, line_num, word)?;
}
Ok(())
} else {
if first.starts_with("@@") {
let first_trim = first
.trim_start_matches(|c| char::is_ascii_punctuation(&c))
.trim_end_matches(|c| char::is_ascii_punctuation(&c));
out.add_allow_line(&info, line_num, first_trim)?;
} else if first.starts_with("||") || first.starts_with("://") || first.ends_with("^") {
let mut first_trim = first;
if first_trim.ends_with("$important") {
first_trim = &first_trim[0..first.len() - 10];
}
first_trim = first_trim
.trim_start_matches(|c| char::is_ascii_punctuation(&c))
.trim_end_matches(|c| char::is_ascii_punctuation(&c));
out.add_block_line(&info, line_num, &first_trim)?;
} else {
trace!("{} UNKNOWN: {}", line_num, first);
out.add_block_line(&info, line_num, first)?;
}
Ok(())
}
} else {
Ok(())
}
}
fn tokenize(line: &str) -> SplitAsciiWhitespace {
match (line.find('!'), line.find('#')) {
(Some(comment_start_excl), Some(comment_start_hash)) => {
if comment_start_excl < comment_start_hash {
line[..comment_start_excl].split_ascii_whitespace()
} else {
line[..comment_start_hash].split_ascii_whitespace()
}
}
(None, Some(comment_start)) => line[..comment_start].split_ascii_whitespace(),
(Some(comment_start), None) => line[..comment_start].split_ascii_whitespace(),
(None, None) => line.split_ascii_whitespace(),
}
}
fn validate_host(host: &str) -> Result<String> {
if host.len() < 2 {
bail!("Invalid host of length {}: {}", host.len(), host);
}
if host.len() > 253 {
bail!("Invalid host of length {}", host.len());
}
for (idx, c) in host.char_indices() {
if idx == host.len() - 1 {
if !c.is_ascii_alphanumeric() {
bail!("Invalid host, last char must be alphanumeric: {}", host);
}
} else {
if !c.is_ascii_alphanumeric() && c != '-' && c != '_' && c != '.' {
bail!(
"Invalid host, middle chars must be alphanumeric, '-', or '.': {}",
host
);
}
}
}
Ok(host.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn comments_hash() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
true,
handle_block_line("# ignored comment", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("foo.com # ignored comment", 5, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("foo.biz# ignored comment", 6, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line(
"1.2.3.4 foo.nz foo.co.nz # ignored comment",
7,
&mut content,
&file_info
)
.is_ok()
);
assert_eq!(
true,
handle_block_line(
"||foo.geek.nz^ # ignored comment",
8,
&mut content,
&file_info
)
.is_ok()
);
let entry = content.get("foo.com").unwrap();
assert_eq!(Some(5), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.biz").unwrap();
assert_eq!(Some(6), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.nz").unwrap();
assert_eq!(Some(7), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.co.nz").unwrap();
assert_eq!(Some(7), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.geek.nz").unwrap();
assert_eq!(Some(8), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
assert_eq!(None, content.get("www.foo.com"));
assert_eq!(None, content.get("foo2.com"));
}
#[test]
fn comments_excl() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
true,
handle_block_line("! ignored comment", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("foo.com ! ignored comment", 5, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("foo.biz! ignored comment", 6, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line(
"1.2.3.4 foo.nz foo.co.nz ! ignored comment",
7,
&mut content,
&file_info
)
.is_ok()
);
assert_eq!(
true,
handle_block_line(
"||foo.geek.nz^ ! ignored comment",
8,
&mut content,
&file_info
)
.is_ok()
);
assert_eq!(5, content.entries.len());
let entry = content.get("foo.com").unwrap();
assert_eq!(Some(5), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.biz").unwrap();
assert_eq!(Some(6), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.nz").unwrap();
assert_eq!(Some(7), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.co.nz").unwrap();
assert_eq!(Some(7), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.geek.nz").unwrap();
assert_eq!(Some(8), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
assert_eq!(None, content.get("www.foo.com"));
assert_eq!(None, content.get("foo2.com"));
}
#[test]
fn bad_hosts() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
false,
handle_block_line("a", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("ab", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
false,
handle_block_line("ab?", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
false,
handle_block_line("?ab", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("ab0", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("0ab", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
false,
handle_block_line("ab_", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("_ab", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
false,
handle_block_line("ab.", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line(".ab", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("a.b", 4, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("a.b", 4, &mut content, &file_info).is_ok()
);
assert_eq!(true, handle_block_line("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 4, &mut content, &file_info).is_ok());
assert_eq!(false, handle_block_line("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 4, &mut content, &file_info).is_ok());
}
#[test]
fn block_hostname() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
true,
handle_block_line("foo.com", 5, &mut content, &file_info).is_ok()
);
assert_eq!(1, content.entries.len());
let entry = content.get("foo.com").unwrap();
assert_eq!(Some(5), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
assert_eq!(None, content.get("www.foo.com"));
assert_eq!(None, content.get("foo2.com"));
}
#[test]
fn override_hostname() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
false,
handle_override_line("foo.com", 5, &mut content, &file_info).is_ok()
);
}
#[test]
fn block_adblock() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
true,
handle_block_line("||foo.com^", 5, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("||bar.com.", 6, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("||baz.com", 7, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("://foo.net^", 8, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("://bar.net", 9, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("://*.baz.net^", 10, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line(".foo.org^", 11, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("||bar.org^$important", 12, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("||baz.org$important", 13, &mut content, &file_info).is_ok()
);
assert_eq!(9, content.entries.len());
let entry = content.get("foo.com").unwrap();
assert_eq!(Some(5), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("bar.com").unwrap();
assert_eq!(Some(6), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("baz.com").unwrap();
assert_eq!(Some(7), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.net").unwrap();
assert_eq!(Some(8), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("bar.net").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("baz.net").unwrap();
assert_eq!(Some(10), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.org").unwrap();
assert_eq!(Some(11), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("bar.org").unwrap();
assert_eq!(Some(12), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("baz.org").unwrap();
assert_eq!(Some(13), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
assert_eq!(None, content.get("www.foo.com"));
assert_eq!(None, content.get("foo2.com"));
}
#[test]
fn block_adblock_allow() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
true,
handle_block_line("@@||foo.com^|", 5, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("@@||bar.com^", 6, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("@@|baz.com^", 7, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("@@|foo.net^", 8, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("@@-bar.net^", 9, &mut content, &file_info).is_ok()
);
assert_eq!(5, content.entries.len());
let entry = content.get("foo.com").unwrap();
assert_eq!(Some(5), entry.line_num);
assert_eq!(true, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("bar.com").unwrap();
assert_eq!(Some(6), entry.line_num);
assert_eq!(true, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("baz.com").unwrap();
assert_eq!(Some(7), entry.line_num);
assert_eq!(true, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.net").unwrap();
assert_eq!(Some(8), entry.line_num);
assert_eq!(true, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("bar.net").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(true, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
assert_eq!(None, content.get("www.foo.com"));
assert_eq!(None, content.get("foo2.com"));
}
#[test]
fn override_adblock() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
false,
handle_override_line("||foo.com^", 5, &mut content, &file_info).is_ok()
);
}
#[test]
fn block_etchosts() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
true,
handle_block_line("1.2.3.4 foo.com", 5, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("::2 foo.com", 6, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("::3 foo.biz", 7, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line("1.2.3.5 foo.biz", 8, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_block_line(
"::4 foo.nz foo.co.nz foo.geek.nz",
9,
&mut content,
&file_info
)
.is_ok()
);
assert_eq!(5, content.entries.len());
let entry = content.get("foo.com").unwrap();
assert_eq!(Some(6), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.biz").unwrap();
assert_eq!(Some(8), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.nz").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.co.nz").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
let entry = content.get("foo.geek.nz").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(None, entry.dest_ipv6);
assert_eq!(None, content.get("www.foo.com"));
assert_eq!(None, content.get("foo2.com"));
}
#[test]
fn override_etchosts() {
let mut content = FilterContent::new();
let file_info = FileInfo {
source_path: "testsrc".to_string(),
local_path: "testlocal".to_string(),
};
assert_eq!(
true,
handle_override_line("1.2.3.4 foo.com", 5, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_override_line("::2 foo.com", 6, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_override_line("::3 foo.biz", 7, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_override_line("1.2.3.5 foo.biz", 8, &mut content, &file_info).is_ok()
);
assert_eq!(
true,
handle_override_line(
"::4 foo.nz foo.co.nz foo.geek.nz",
9,
&mut content,
&file_info
)
.is_ok()
);
assert_eq!(5, content.entries.len());
let entry = content.get("foo.com").unwrap();
assert_eq!(Some(6), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(
Some(Ipv4Addr::from_str("1.2.3.4").unwrap()),
entry.dest_ipv4
);
assert_eq!(Some(Ipv6Addr::from_str("::2").unwrap()), entry.dest_ipv6);
let entry = content.get("foo.biz").unwrap();
assert_eq!(Some(8), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(
Some(Ipv4Addr::from_str("1.2.3.5").unwrap()),
entry.dest_ipv4
);
assert_eq!(Some(Ipv6Addr::from_str("::3").unwrap()), entry.dest_ipv6);
let entry = content.get("foo.nz").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(Some(Ipv6Addr::from_str("::4").unwrap()), entry.dest_ipv6);
let entry = content.get("foo.co.nz").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(Some(Ipv6Addr::from_str("::4").unwrap()), entry.dest_ipv6);
let entry = content.get("foo.geek.nz").unwrap();
assert_eq!(Some(9), entry.line_num);
assert_eq!(false, entry.dest_upstream);
assert_eq!(None, entry.dest_ipv4);
assert_eq!(Some(Ipv6Addr::from_str("::4").unwrap()), entry.dest_ipv6);
assert_eq!(None, content.get("www.foo.com"));
assert_eq!(None, content.get("foo2.com"));
}
}