app.rs (9027B)
1 use core::convert; 2 use rpz::dom::{ 3 Adblock, DomainOnly, FirefoxDomainErr, Hosts, ParsedDomain, RpzAction, RpzDomain, Value, 4 Wildcard, 5 }; 6 use rpz::file::{AbsFilePath, File, Files, Kind, LocalFiles, Summary}; 7 use std::{ 8 collections::HashMap, 9 fs, 10 io::{self, Error, Write}, 11 }; 12 use superset_map::SupersetSet; 13 /// Helper that returns the `Kind` of a file. 14 pub trait Helper { 15 /// Returns the `Kind` of file. 16 fn kind() -> Kind; 17 } 18 impl Helper for Adblock<'_> { 19 fn kind() -> Kind { 20 Kind::Adblock 21 } 22 } 23 impl Helper for DomainOnly<'_> { 24 fn kind() -> Kind { 25 Kind::DomainOnly 26 } 27 } 28 impl Helper for Hosts<'_> { 29 fn kind() -> Kind { 30 Kind::Hosts 31 } 32 } 33 impl Helper for Wildcard<'_> { 34 fn kind() -> Kind { 35 Kind::Wildcard 36 } 37 } 38 /// Container of `Domain`s to block and unblock. 39 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 40 pub struct Domains<'unblock, 'block> { 41 /// `RpzDomain`s to not block. 42 unblock: SupersetSet<RpzDomain<'unblock>>, 43 /// `RpzDomain`s to block. 44 block: SupersetSet<RpzDomain<'block>>, 45 } 46 impl<'unblock, 'block> Domains<'unblock, 'block> { 47 /// Returns a reference to the `RpzDomain`s to block. 48 pub const fn block(&self) -> &SupersetSet<RpzDomain<'block>> { 49 &self.block 50 } 51 /// Returns an empty `Domains`. 52 pub const fn new() -> Self { 53 Self { 54 unblock: SupersetSet::new(), 55 block: SupersetSet::new(), 56 } 57 } 58 /// Returns `Domains` based on `LocalFiles` along 59 /// with a `Vec` of `Summary`. 60 pub fn new_with<'c: 'unblock + 'block>( 61 local: &'c LocalFiles, 62 ) -> (Self, Vec<Summary<'c, FirefoxDomainErr>>) { 63 let mut val = Self { 64 unblock: SupersetSet::new(), 65 block: SupersetSet::new(), 66 }; 67 let summaries = val.add_local_files(local); 68 (val, summaries) 69 } 70 /// Parses each line in `file` as an `RpzDomain` before 71 /// adding it to `Domains::block` iff `Domains::unblock` does not contain 72 /// a superset of it. 73 /// 74 /// All parsing errors are ignored. 75 fn add_block_file< 76 'c: 'block, 77 T: Into<RpzDomain<'block>> + ParsedDomain<'block, Error = FirefoxDomainErr> + Helper, 78 >( 79 &mut self, 80 file: &'c File, 81 summaries: &mut Vec<Summary<'c, FirefoxDomainErr>>, 82 ) { 83 let mut summary = Summary { 84 file, 85 kind: T::kind(), 86 domain_count: 0, 87 comment_count: 0, 88 blank_count: 0, 89 errors: HashMap::new(), 90 }; 91 file.data 92 .lines() 93 .fold((), |(), line| match T::parse_value(line) { 94 Ok(val) => match val { 95 Value::Domain(dom) => { 96 summary.domain_count = summary.domain_count.saturating_add(1); 97 let domain = dom.into(); 98 if !self.unblock.contains_superset(&domain) { 99 self.block.insert(domain); 100 } 101 } 102 Value::Comment(_) => { 103 summary.comment_count = summary.comment_count.saturating_add(1); 104 } 105 Value::Blank => { 106 summary.blank_count = summary.blank_count.saturating_add(1); 107 } 108 }, 109 Err(err) => { 110 let count = summary.errors.entry(err).or_insert(0); 111 *count = count.saturating_add(1); 112 } 113 }); 114 summaries.push(summary); 115 } 116 /// Parses each line in the files in `files` as an `RpzDomain` before 117 /// adding it to `Domains::block` iff `Domains::unblock` does not contain 118 /// a superset of it. 119 /// 120 /// All parsing errors are ignored. 121 pub fn add_block_files<'c: 'block>( 122 &mut self, 123 files: &'c Files, 124 summaries: &mut Vec<Summary<'c, FirefoxDomainErr>>, 125 ) { 126 /// Parses each line in the `String`s in `files` as an `RpzDomain` before 127 /// adding it to `Domains::block` iff `Domains::unblock` does not contain 128 /// a superset of it. 129 /// 130 /// All parsing errors are ignored. 131 fn add_files< 132 'unblock, 133 'block, 134 'c: 'block, 135 T: Into<RpzDomain<'block>> + ParsedDomain<'block, Error = FirefoxDomainErr> + Helper, 136 >( 137 doms: &mut Domains<'unblock, 'block>, 138 files: &'c [File], 139 summaries: &mut Vec<Summary<'c, FirefoxDomainErr>>, 140 ) { 141 files 142 .iter() 143 .fold((), |(), file| doms.add_block_file::<T>(file, summaries)); 144 } 145 add_files::<Adblock<'_>>(self, &files.adblock, summaries); 146 add_files::<DomainOnly<'_>>(self, &files.domain, summaries); 147 add_files::<Hosts<'_>>(self, &files.hosts, summaries); 148 add_files::<Wildcard<'_>>(self, &files.wildcard, summaries); 149 } 150 /// Parses each line in the files in `files` as an `RpzDomain`. 151 /// For unblock files, the domain is added to `Domains::unblock`; 152 /// for block files, the domain is added to `Domains::block` iff 153 /// `Domains::unblock` does not contain a superset of it. 154 /// 155 /// All parsing errors are ignored. 156 fn add_local_files<'c: 'unblock + 'block>( 157 &mut self, 158 files: &'c LocalFiles, 159 ) -> Vec<Summary<'c, FirefoxDomainErr>> { 160 let mut summaries = files.unblock.add_to_superset(&mut self.unblock); 161 self.add_block_files(&files.block, &mut summaries); 162 summaries 163 } 164 /// Writes all necessary unblock `RpzDomain`s as `<domain> CNAME rpz-passthru.` 165 /// followed by all block `RpzDomain`s as `<domain> CNAME .` 166 /// 167 /// When subdomains exist, `*.<domain>` is used. 168 /// 169 /// When `path` is `None`, `stdout` is written to; otherwise, 170 /// `path.unwrap().1` is written to before being renamed 171 /// to `path.unwrap().0`. 172 /// 173 /// Returns the quantity of unblock lines and block lines written 174 /// respectively. 175 /// 176 /// Note that by "necessary unblock" we mean that there exists 177 /// a proper superset of it in the blocked `RpzDomain`s; as if not, 178 /// there is no need to write it since a lack of a blocked entry 179 /// is equivalent to an unblock entry. 180 /// 181 /// # Errors 182 /// 183 /// Returns `Error` iff `writeln` or `fs::rename` do. 184 pub fn write( 185 self, 186 path: Option<(AbsFilePath<false>, AbsFilePath<false>)>, 187 ) -> Result<(usize, usize), Error> { 188 /// When `exclude.is_none()`, calls `Rpz::write_rpz_line` with 189 /// `RpzAction::Nxdomain` for each `RpzDomain` in `doms`; 190 /// otherwise calls it with `RpzAction::Passthru` 191 /// so long as `exclude` contains a proper superset of the domain. 192 /// 193 /// Returns the total number of lines written. 194 fn write_domain<W: Write>( 195 doms: &SupersetSet<RpzDomain<'_>>, 196 exclude: Option<&SupersetSet<RpzDomain<'_>>>, 197 mut writer: W, 198 ) -> Result<usize, Error> { 199 let action = exclude.map_or(RpzAction::Nxdomain, |_| RpzAction::Passthru); 200 doms.into_iter().try_fold(0usize, |count, rpz| { 201 if exclude.map_or(true, |other| other.contains_proper_superset(rpz)) { 202 rpz.write_to_rpz(action, &mut writer) 203 .map(|()| count.saturating_add(if rpz.is_subdomains() { 2 } else { 1 })) 204 } else { 205 Ok(count) 206 } 207 }) 208 } 209 path.map_or_else( 210 || { 211 let mut std = io::stdout().lock(); 212 write_domain(&self.unblock, Some(&self.block), &mut std).and_then(|unblock_count| { 213 write_domain(&self.block, None, std) 214 .map(|block_count| (unblock_count, block_count)) 215 }) 216 }, 217 |(rpz, rpz_tmp)| { 218 fs::File::options() 219 .read(false) 220 .write(true) 221 .create_new(true) 222 .open(rpz_tmp.as_path()) 223 .and_then(|file| { 224 write_domain(&self.unblock, Some(&self.block), &file).and_then( 225 |unblock_count| { 226 write_domain(&self.block, None, file).and_then(|block_count| { 227 fs::rename(rpz_tmp.as_path(), rpz) 228 .map(|()| (unblock_count, block_count)) 229 .map_err(|err| { 230 fs::remove_file(rpz_tmp) 231 .map_or_else(convert::identity, |()| err) 232 }) 233 }) 234 }, 235 ) 236 }) 237 }, 238 ) 239 } 240 }