app.rs (9050B)
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(crate) 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(crate) 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(crate) const fn block(&self) -> &SupersetSet<RpzDomain<'block>> { 49 &self.block 50 } 51 /// Returns an empty `Domains`. 52 pub(crate) 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(crate) 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(crate) 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 'block, 133 'c: 'block, 134 T: Into<RpzDomain<'block>> + ParsedDomain<'block, Error = FirefoxDomainErr> + Helper, 135 >( 136 doms: &mut Domains<'_, 'block>, 137 files: &'c [File], 138 summaries: &mut Vec<Summary<'c, FirefoxDomainErr>>, 139 ) { 140 files 141 .iter() 142 .fold((), |(), file| doms.add_block_file::<T>(file, summaries)); 143 } 144 add_files::<Adblock<'_>>(self, &files.adblock, summaries); 145 add_files::<DomainOnly<'_>>(self, &files.domain, summaries); 146 add_files::<Hosts<'_>>(self, &files.hosts, summaries); 147 add_files::<Wildcard<'_>>(self, &files.wildcard, summaries); 148 } 149 /// Parses each line in the files in `files` as an `RpzDomain`. 150 /// For unblock files, the domain is added to `Domains::unblock`; 151 /// for block files, the domain is added to `Domains::block` iff 152 /// `Domains::unblock` does not contain a superset of it. 153 /// 154 /// All parsing errors are ignored. 155 fn add_local_files<'c: 'unblock + 'block>( 156 &mut self, 157 files: &'c LocalFiles, 158 ) -> Vec<Summary<'c, FirefoxDomainErr>> { 159 let mut summaries = files.unblock.add_to_superset(&mut self.unblock); 160 self.add_block_files(&files.block, &mut summaries); 161 summaries 162 } 163 /// Writes all necessary unblock `RpzDomain`s as `<domain> CNAME rpz-passthru.` 164 /// followed by all block `RpzDomain`s as `<domain> CNAME .` 165 /// 166 /// When subdomains exist, `*.<domain>` is used. 167 /// 168 /// When `path` is `None`, `stdout` is written to; otherwise, 169 /// `path.unwrap().1` is written to before being renamed 170 /// to `path.unwrap().0`. 171 /// 172 /// Returns the quantity of unblock lines and block lines written 173 /// respectively. 174 /// 175 /// Note that by "necessary unblock" we mean that there exists 176 /// a proper superset of it in the blocked `RpzDomain`s; as if not, 177 /// there is no need to write it since a lack of a blocked entry 178 /// is equivalent to an unblock entry. 179 /// 180 /// # Errors 181 /// 182 /// Returns `Error` iff `writeln` or `fs::rename` do. 183 pub(crate) fn write( 184 self, 185 path: Option<(AbsFilePath<false>, AbsFilePath<false>)>, 186 ) -> Result<(usize, usize), Error> { 187 /// When `exclude.is_none()`, calls `Rpz::write_rpz_line` with 188 /// `RpzAction::Nxdomain` for each `RpzDomain` in `doms`; 189 /// otherwise calls it with `RpzAction::Passthru` 190 /// so long as `exclude` contains a proper superset of the domain. 191 /// 192 /// Returns the total number of lines written. 193 fn write_domain<W: Write>( 194 doms: &SupersetSet<RpzDomain<'_>>, 195 exclude: Option<&SupersetSet<RpzDomain<'_>>>, 196 mut writer: W, 197 ) -> Result<usize, Error> { 198 let action = exclude.map_or(RpzAction::Nxdomain, |_| RpzAction::Passthru); 199 doms.into_iter().try_fold(0usize, |count, rpz| { 200 if exclude.is_none_or(|other| other.contains_proper_superset(rpz)) { 201 rpz.write_to_rpz(action, &mut writer) 202 .map(|()| count.saturating_add(if rpz.is_subdomains() { 2 } else { 1 })) 203 } else { 204 Ok(count) 205 } 206 }) 207 } 208 path.map_or_else( 209 || { 210 let mut std = io::stdout().lock(); 211 write_domain(&self.unblock, Some(&self.block), &mut std).and_then(|unblock_count| { 212 write_domain(&self.block, None, std) 213 .map(|block_count| (unblock_count, block_count)) 214 }) 215 }, 216 |(rpz, rpz_tmp)| { 217 fs::File::options() 218 .read(false) 219 .write(true) 220 .create_new(true) 221 .open(rpz_tmp.as_path()) 222 .and_then(|file| { 223 write_domain(&self.unblock, Some(&self.block), &file).and_then( 224 |unblock_count| { 225 write_domain(&self.block, None, file).and_then(|block_count| { 226 fs::rename(rpz_tmp.as_path(), rpz) 227 .map(|()| (unblock_count, block_count)) 228 .map_err(|err| { 229 fs::remove_file(rpz_tmp) 230 .map_or_else(convert::identity, |()| err) 231 }) 232 }) 233 }, 234 ) 235 }) 236 }, 237 ) 238 } 239 }