rpz

Response policy zone (RPZ) file generator.
git clone https://git.philomathiclife.com/repos/rpz
Log | Files | Refs | README

commit 77e63a36d0be4305481adfb2f79af7df0e71fada
parent 16f45137001497d044b253edbf352b0fd1eb9c64
Author: Zack Newman <zack@philomathiclife.com>
Date:   Tue, 25 Feb 2025 09:52:40 -0700

rust 2024

Diffstat:
MCargo.toml | 25+++++++++++--------------
MREADME.md | 27++++++++++++++++++++++++---
Msrc/app.rs | 2+-
Msrc/args.rs | 475++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/config.rs | 132++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/dom.rs | 88+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/file.rs | 12++++++------
Msrc/lib.rs | 14+++++++++++++-
Msrc/main.rs | 55+++++++++++++++++++++++++++++++++----------------------
Msrc/priv_sep.rs | 15+++++++++------
10 files changed, 482 insertions(+), 363 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -3,30 +3,27 @@ authors = ["Zack Newman <zack@philomathiclife.com>"] categories = ["command-line-utilities"] description = "RPZ file generator based on HTTP(S) URLs and local file paths entered into a config file." documentation = "https://crates.io/crates/rpz" -edition = "2021" +edition = "2024" keywords = ["adblock", "rpz", "unbound"] license = "MIT OR Apache-2.0" name = "rpz" readme = "README.md" repository = "https://git.philomathiclife.com/repos/rpz/" -version = "1.0.1" - -[badges] -maintenance = { status = "actively-developed" } +version = "1.1.0" [dependencies] -ascii_domain = { version = "0.6.2", default-features = false } +ascii_domain = { version = "0.6.3", default-features = false } num-bigint = { version = "0.4.6", default-features = false } -reqwest = { version = "0.12.7", default-features = false, features = ["brotli", "deflate", "gzip", "rustls-tls-native-roots", "trust-dns"] } -serde = { version = "1.0.210", default-features = false } -superset_map = { version = "0.3.0", default-features = false } -tokio = { version = "1.40.0", default-features = false, features = ["rt", "time"] } -toml = { version = "0.8.19", default-features = false, features = ["parse"] } -url = { version = "2.5.2", default-features = false, features = ["serde"] } -zfc = { version = "0.4.0", default-features = false } +reqwest = { version = "0.12.12", default-features = false, features = ["brotli", "deflate", "gzip", "rustls-tls-native-roots", "trust-dns"] } +serde = { version = "1.0.218", default-features = false } +superset_map = { version = "0.3.1", default-features = false } +tokio = { version = "1.43.0", default-features = false, features = ["rt", "time"] } +toml = { version = "0.8.20", default-features = false, features = ["parse"] } +url = { version = "2.5.4", default-features = false, features = ["serde"] } +zfc = { version = "0.4.1", default-features = false } [target.'cfg(target_os = "openbsd")'.dependencies] -priv_sep = { version = "2.1.0", default-features = false, features = ["openbsd"], optional = true } +priv_sep = { version = "2.2.0", default-features = false, features = ["openbsd"], optional = true } ### FEATURES ################################################################# diff --git a/README.md b/README.md @@ -1,4 +1,8 @@ -# rpz +# `rpz` + +[<img alt="git" src="https://git.philomathiclife.com/badges/rpz.svg" height="20">](https://git.philomathiclife.com/rpz/log.html) +[<img alt="crates.io" src="https://img.shields.io/crates/v/rpz.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/rpz) +[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-rpz-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/rpz/latest/rpz/) `rpz` consists of a binary crate and [library crate](https://docs.rs/rpz/latest/rpz). The binary crate, `rpz`, is an application that downloads, parses, and transforms ad-(un)block files from @@ -179,12 +183,25 @@ to the total number of domains, comments, blanks, and parsing errors. Parsing errors are ignored; all other errors are written to `stderr` before program abortion. +## Minimum Supported Rust Version (MSRV) + +This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that +update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV +will be updated. + +MSRV changes will correspond to a SemVer minor version bump. + +## SemVer Policy + +* All on-by-default features of this library are covered by SemVer +* MSRV is considered exempt from SemVer as noted above + ## License Licensed under either of -* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0). -* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT). +* Apache License, Version 2.0 ([LICENSE-APACHE](https://www.apache.org/licenses/LICENSE-2.0)) +* MIT license ([LICENSE-MIT](https://opensource.org/licenses/MIT)) at your option. @@ -193,6 +210,10 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +Before any PR is sent, `cargo clippy` and `cargo t` should be run for both `--no-default-features` and +`--all-features`. Additionally `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to +ensure documentation can be built. + ### Status This package is actively maintained. diff --git a/src/app.rs b/src/app.rs @@ -198,7 +198,7 @@ impl<'unblock, 'block> Domains<'unblock, 'block> { ) -> Result<usize, Error> { let action = exclude.map_or(RpzAction::Nxdomain, |_| RpzAction::Passthru); doms.into_iter().try_fold(0usize, |count, rpz| { - if exclude.map_or(true, |other| other.contains_proper_superset(rpz)) { + if exclude.is_none_or(|other| other.contains_proper_superset(rpz)) { rpz.write_to_rpz(action, &mut writer) .map(|()| count.saturating_add(if rpz.is_subdomains() { 2 } else { 1 })) } else { diff --git a/src/args.rs b/src/args.rs @@ -5,7 +5,6 @@ use core::{ use rpz::file::AbsFilePath; use std::env::{self, Args}; /// Error returned when parsing arguments passed to the application. -#[expect(clippy::module_name_repetitions, reason = "prefer the name")] #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum ArgsErr { /// Error when no arguments were passed to the application. @@ -144,7 +143,7 @@ impl Opts { return Err(ArgsErr::DuplicateOption("-q/--quiet")); } Self::Verbose | Self::ConfigVerbose(_) => { - return Err(ArgsErr::QuietAndVerbose) + return Err(ArgsErr::QuietAndVerbose); } Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption), }, @@ -166,11 +165,11 @@ impl Opts { opts = Self::ConfigQuiet(get_path(&mut args)?); } Self::Config(_) | Self::ConfigQuiet(_) => { - return Err(ArgsErr::DuplicateOption("-f/--file")) + return Err(ArgsErr::DuplicateOption("-f/--file")); } Self::Quiet => return Err(ArgsErr::DuplicateOption("-q/--quiet")), Self::Verbose | Self::ConfigVerbose(_) => { - return Err(ArgsErr::QuietAndVerbose) + return Err(ArgsErr::QuietAndVerbose); } Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption), }, @@ -179,7 +178,7 @@ impl Opts { opts = Self::ConfigVerbose(get_path(&mut args)?); } Self::Config(_) | Self::ConfigVerbose(_) => { - return Err(ArgsErr::DuplicateOption("-f/--file")) + return Err(ArgsErr::DuplicateOption("-f/--file")); } Self::Verbose => return Err(ArgsErr::DuplicateOption("-v/--verbose")), Self::Quiet | Self::ConfigQuiet(_) => return Err(ArgsErr::QuietAndVerbose), @@ -196,7 +195,7 @@ impl Opts { } #[cfg(test)] mod tests { - use crate::{test_prog, ArgsErr, E}; + use crate::{ArgsErr, E, test_prog}; use core::convert; use std::io::Write; use std::process::Stdio; @@ -205,223 +204,259 @@ mod tests { #[ignore] fn test_args() { test_prog::verify_files(); - assert!(test_prog::get_command() - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::NoArgs)).into_bytes() - })); - assert!(test_prog::get_command() - .arg("-f") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) - .into_bytes() - })); - assert!(test_prog::get_command() - .arg("-q") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) - .into_bytes() - })); - assert!(test_prog::get_command() - .arg("-v") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) - .into_bytes() - })); - assert!(test_prog::get_command() - .arg("-fq") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) - .into_bytes() - })); - assert!(test_prog::get_command() - .arg("-qf") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) - .into_bytes() - })); - assert!(test_prog::get_command() - .arg("-fv") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) - .into_bytes() - })); - assert!(test_prog::get_command() - .arg("-vf") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) - .into_bytes() - })); - assert!(test_prog::get_command() - .args(["-h", "-V"]) - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption)) - .into_bytes() - })); - assert!(test_prog::get_command() - .args(["-h", "-h"]) - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!( - "Error: {:?}\n", - E::Args(ArgsErr::DuplicateOption("-h/--help")) - ) - .into_bytes() - })); - assert!(test_prog::get_command() - .args(["-f", "/home/zack/foo", "-V"]) - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption)) - .into_bytes() - })); - assert!(test_prog::get_command() - .args(["-f", "home/zack/foo"]) - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath)) + assert!( + test_prog::get_command() + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::NoArgs)).into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-f") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-q") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-v") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-fq") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-qf") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-fv") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-vf") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .args(["-h", "-V"]) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .args(["-h", "-h"]) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!( + "Error: {:?}\n", + E::Args(ArgsErr::DuplicateOption("-h/--help")) + ) .into_bytes() - })); - assert!(test_prog::get_command() - .args(["-f", "/home/zack/foo/"]) - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath)) + }) + ); + assert!( + test_prog::get_command() + .args(["-f", "/home/zack/foo", "-V"]) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .args(["-f", "home/zack/foo"]) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .args(["-f", "/home/zack/foo/"]) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath)) + .into_bytes() + }) + ); + assert!( + test_prog::get_command() + .arg("-foo") + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| { + !output.status.success() + && output.stderr + == format!( + "Error: {:?}\n", + E::Args(ArgsErr::InvalidOption(String::from("-foo"))) + ) .into_bytes() - })); - assert!(test_prog::get_command() - .arg("-foo") - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| { - !output.status.success() - && output.stderr - == format!( - "Error: {:?}\n", - E::Args(ArgsErr::InvalidOption(String::from("-foo"))) - ) - .into_bytes() - })); - assert!(test_prog::get_command() - .args(["-f", "-"]) - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .map_or(false, |output| !output.status.success() - && output.stderr.get(..23) == Some(b"Error: TOML parse error"))); - assert!(test_prog::get_command() - .args(["-f", "-"]) - .stderr(Stdio::piped()) - .stdin(Stdio::piped()) - .stdout(Stdio::null()) - .spawn() - .map_or(false, |mut cmd| { - cmd.stdin.take().map_or(false, |mut stdin| { - thread::spawn(move || { - stdin - .write_all(b"junk") - .map_or(false, |_| stdin.flush().map_or(false, |_| true)) + }) + ); + assert!( + test_prog::get_command() + .args(["-f", "-"]) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .output() + .map_or(false, |output| !output.status.success() + && output.stderr.get(..23) == Some(b"Error: TOML parse error")) + ); + assert!( + test_prog::get_command() + .args(["-f", "-"]) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .spawn() + .map_or(false, |mut cmd| { + cmd.stdin.take().map_or(false, |mut stdin| { + thread::spawn(move || { + stdin + .write_all(b"junk") + .map_or(false, |_| stdin.flush().map_or(false, |_| true)) + }) + .join() + .map_or(false, convert::identity) + }) && cmd.wait_with_output().map_or(false, |output| { + !output.status.success() + && output.stderr.get(..23) == Some(b"Error: TOML parse error") }) - .join() - .map_or(false, convert::identity) - }) && cmd.wait_with_output().map_or(false, |output| { - !output.status.success() - && output.stderr.get(..23) == Some(b"Error: TOML parse error") }) - })); - assert!(test_prog::get_command() - .arg("-h") - .stderr(Stdio::null()) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .output() - .map_or(false, |output| output.status.success() - && output.stdout.get(..crate::HELP.len()) - == Some(crate::HELP.as_bytes()))); - assert!(test_prog::get_command() - .arg("-V") - .stderr(Stdio::null()) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .output() - .map_or(false, |output| output.status.success() - && output.stdout.get(..crate::VERSION.len()) - == Some(crate::VERSION.as_bytes()))); + ); + assert!( + test_prog::get_command() + .arg("-h") + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .output() + .map_or(false, |output| output.status.success() + && output.stdout.get(..crate::HELP.len()) + == Some(crate::HELP.as_bytes())) + ); + assert!( + test_prog::get_command() + .arg("-V") + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .output() + .map_or(false, |output| output.status.success() + && output.stdout.get(..crate::VERSION.len()) + == Some(crate::VERSION.as_bytes())) + ); } } diff --git a/src/config.rs b/src/config.rs @@ -92,7 +92,7 @@ impl<'de> Deserialize<'de> for Config { { /// `Visitor` for `Field`. struct FieldVisitor; - impl<'de> Visitor<'de> for FieldVisitor { + impl Visitor<'_> for FieldVisitor { type Value = Field; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str( @@ -256,10 +256,10 @@ impl<'de> Deserialize<'de> for Config { } } if local_dir.is_none() - && ad.as_ref().map_or(true, |urls| urls.0.is_empty()) - && dom.as_ref().map_or(true, |urls| urls.0.is_empty()) - && hst.as_ref().map_or(true, |urls| urls.0.is_empty()) - && wc.as_ref().map_or(true, |urls| urls.0.is_empty()) + && ad.as_ref().is_none_or(|urls| urls.0.is_empty()) + && dom.as_ref().is_none_or(|urls| urls.0.is_empty()) + && hst.as_ref().is_none_or(|urls| urls.0.is_empty()) + && wc.as_ref().is_none_or(|urls| urls.0.is_empty()) { Err(Error::invalid_type( Unexpected::Other("no block list URLs or directory"), @@ -316,86 +316,108 @@ mod tests { #[test] fn test_timeout() { assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok()); - assert!(toml::from_str::<Config>( - r#"timeout=15 + assert!( + toml::from_str::<Config>( + r#"timeout=15 local_dir="/foo/""# - ) - .is_ok()); - assert!(toml::from_str::<Config>( - r#"timeout=4294967295 + ) + .is_ok() + ); + assert!( + toml::from_str::<Config>( + r#"timeout=4294967295 local_dir="/foo/""# - ) - .is_ok()); - assert!(toml::from_str::<Config>( - r#"timeout=-1 + ) + .is_ok() + ); + assert!( + toml::from_str::<Config>( + r#"timeout=-1 local_dir="/foo/""# - ) - .is_err()); - assert!(toml::from_str::<Config>( - r#"timeout=0 + ) + .is_err() + ); + assert!( + toml::from_str::<Config>( + r#"timeout=0 local_dir="/foo/""# - ) - .is_ok()); - assert!(toml::from_str::<Config>( - r#"timeout=4294967296 + ) + .is_ok() + ); + assert!( + toml::from_str::<Config>( + r#"timeout=4294967296 local_dir="/foo/""# - ) - .is_err()); + ) + .is_err() + ); } #[test] fn test_arrays() { - assert!(toml::from_str::<Config>( - r#"adblock=["https://foo.com/foo","https://foo.com/foo"]"# - ) - .is_err()); + assert!( + toml::from_str::<Config>(r#"adblock=["https://foo.com/foo","https://foo.com/foo"]"#) + .is_err() + ); assert!(toml::from_str::<Config>(r#"adblock=["https://foo.com/foo"]"#).is_ok()); - assert!(toml::from_str::<Config>( - r#"adblock=["https://foo.com/foo"] + assert!( + toml::from_str::<Config>( + r#"adblock=["https://foo.com/foo"] domain=["https://foo.com/foo"]"# - ) - .is_err()); + ) + .is_err() + ); } #[test] fn test_urls() { assert!(toml::from_str::<Config>(r#"adblock=["file://foo.com/foo"]"#).is_err()); assert!(toml::from_str::<Config>(r#"adblock=["foo.com/foo"]"#).is_err()); assert!(toml::from_str::<Config>(r#"adblock=["http://foo.com/foo"]"#).is_ok()); - assert!(toml::from_str::<Config>( - r#"adblock=[] + assert!( + toml::from_str::<Config>( + r#"adblock=[] domain=["https:///foo"]"# - ) - .is_ok()); + ) + .is_ok() + ); assert!(toml::from_str::<Config>(r#"adblock=[""]"#).is_err()); assert!(toml::from_str::<Config>(r#"adblock=["https://"]"#).is_err()); assert!(toml::from_str::<Config>(r#"adblock=["ftp://foo.com/foo"]"#).is_err()); } #[test] fn test_paths() { - assert!(toml::from_str::<Config>( - r#"rpz="/foo/" + assert!( + toml::from_str::<Config>( + r#"rpz="/foo/" wildcard=["https://foo.com/foo"]"# - ) - .is_err()); - assert!(toml::from_str::<Config>( - r#"rpz="foo" + ) + .is_err() + ); + assert!( + toml::from_str::<Config>( + r#"rpz="foo" wildcard=["https://foo.com/foo"]"# - ) - .is_err()); - assert!(toml::from_str::<Config>( - r#"rpz="/foo" + ) + .is_err() + ); + assert!( + toml::from_str::<Config>( + r#"rpz="/foo" wildcard=["https://foo.com/foo"]"# - ) - .is_ok()); + ) + .is_ok() + ); assert!(toml::from_str::<Config>(r#"local_dir="foo/""#).is_err()); assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok()); // Directories are allowed to not have a trailing `/`, but they will get it // added. - assert!(toml::from_str::<Config>( - r#"local_dir="/foo" + assert!( + toml::from_str::<Config>( + r#"local_dir="/foo" wildcard=["https://foo.com/foo"]"# - ) - .map_or(false, |config| config.local_dir.map_or(false, |dir| dir - .to_str() - .map_or(false, |val| val == String::from("/foo/"))))); + ) + .map_or(false, |config| config.local_dir.map_or(false, |dir| dir + .to_str() + .map_or(false, |val| val == String::from("/foo/")))) + ); } } diff --git a/src/dom.rs b/src/dom.rs @@ -1,6 +1,6 @@ use crate::dom_count_auto_gen::proper_subdomain_count; use ascii_domain::{ - char_set::{AllowedAscii, ASCII_FIREFOX}, + char_set::{ASCII_FIREFOX, AllowedAscii}, dom::{Domain, DomainErr, DomainOrdering}, }; use core::{ @@ -265,7 +265,7 @@ pub struct Adblock<'a> { /// `true` iff `domain` represents all subdomains. Note that this includes `domain` itself. subdomains: bool, } -impl<'a> Adblock<'a> { +impl Adblock<'_> { /// Returns `true` iff the contained [`Domain`] represents all subdomains. Note this includes the /// `Domain` itself. #[inline] @@ -524,7 +524,6 @@ impl Ord for Adblock<'_> { /// /// For example, `com` `<` `example.com` `<` `||example.com` `<` `||com` `<` `net` `<` `example.net` `<` `||example.net` `<` `||net`. #[inline] - #[must_use] fn cmp(&self, other: &Self) -> Ordering { match self.domain.cmp_by_domain_ordering(&other.domain) { DomainOrdering::Less => Ordering::Less, @@ -734,7 +733,7 @@ pub struct DomainOnly<'a> { /// The `Domain`. domain: Domain<&'a str>, } -impl<'a> DomainOnly<'a> { +impl DomainOnly<'_> { /// Read [`Adblock::cmp_domain_only`]. #[inline] #[must_use] @@ -989,7 +988,7 @@ pub struct Hosts<'a> { /// The `Domain`. domain: Domain<&'a str>, } -impl<'a> Hosts<'a> { +impl Hosts<'_> { /// Read [`Adblock::cmp_hosts`]. #[inline] #[must_use] @@ -1262,7 +1261,7 @@ pub struct Wildcard<'a> { /// `true` iff `domain` represents all proper subdomains. Note that this does _not_ include `domain` itself. proper_subdomains: bool, } -impl<'a> Wildcard<'a> { +impl Wildcard<'_> { /// Returns `true` iff the contained [`Domain`] represents all proper subdomains. Note this does _not_ /// include the `Domain` itself. #[inline] @@ -1392,7 +1391,6 @@ impl Ord for Wildcard<'_> { /// /// For example, `com` `<` `example.com` `<` `*.example.com` `<` `*.com` `<` `net` `<` `example.net` `<` `*.example.net` `<` `*.net`. #[inline] - #[must_use] fn cmp(&self, other: &Self) -> Ordering { match self.domain.cmp_by_domain_ordering(&other.domain) { DomainOrdering::Less => Ordering::Less, @@ -1584,7 +1582,7 @@ pub enum RpzDomain<'a> { /// A `Wildcard` domain. Wildcard(Wildcard<'a>), } -impl<'a> RpzDomain<'a> { +impl RpzDomain<'_> { /// Returns `true` iff `self` represents a single [`Domain`]. #[inline] #[must_use] @@ -2026,25 +2024,31 @@ mod tests { }) ); // Test blank. - assert!(DomainOnly::parse_value(" \t\t \t\t \t ") - .map_or(false, |val| matches!(val, Value::Blank))); + assert!( + DomainOnly::parse_value(" \t\t \t\t \t ") + .map_or(false, |val| matches!(val, Value::Blank)) + ); // Test blank. - assert!(DomainOnly::parse_value("example.xn--abc") - .map_or(false, |val| matches!(val, Value::Domain(_)))); + assert!( + DomainOnly::parse_value("example.xn--abc") + .map_or(false, |val| matches!(val, Value::Domain(_))) + ); // Test invalid TLD. - assert!(DomainOnly::parse_value("www.c1m") - .map_or_else(|err| err == FirefoxDomainErr::InvalidTld, |_| false)); + assert!( + DomainOnly::parse_value("www.c1m") + .map_or_else(|err| err == FirefoxDomainErr::InvalidTld, |_| false) + ); } #[test] fn test_hosts_parse_value() { // Test whitespace and comment. - assert!(Hosts::parse_value( - " \t\t 127.0.0.1\t\t \twww.example.com#asdflkj asdf alskdfj " - ) - .map_or(false, |val| match val { - Value::Domain(ref dom) => dom.domain.as_bytes() == b"www.example.com", - Value::Comment(_) | Value::Blank => false, - })); + assert!( + Hosts::parse_value(" \t\t 127.0.0.1\t\t \twww.example.com#asdflkj asdf alskdfj ") + .map_or(false, |val| match val { + Value::Domain(ref dom) => dom.domain.as_bytes() == b"www.example.com", + Value::Comment(_) | Value::Blank => false, + }) + ); assert!( Hosts::parse_value(" \t\t 0.0.0.0\t\t \twww.example.com \t\t ^ \t\t ") .map_or_else( @@ -2057,14 +2061,22 @@ mod tests { |_| false )); // Test invalid IP - assert!(Hosts::parse_value("::2 www.example.com") - .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)); - assert!(Hosts::parse_value(":2 www.example.com") - .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)); - assert!(Hosts::parse_value("www.example.com") - .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)); - assert!(Hosts::parse_value("10.4.2.256 www.example.com") - .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)); + assert!( + Hosts::parse_value("::2 www.example.com") + .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false) + ); + assert!( + Hosts::parse_value(":2 www.example.com") + .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false) + ); + assert!( + Hosts::parse_value("www.example.com") + .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false) + ); + assert!( + Hosts::parse_value("10.4.2.256 www.example.com") + .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false) + ); // Test case-insensitivity. assert!( Hosts::parse_value(":: www.ExAMPle.Com").map_or(false, |val| match val { @@ -2085,8 +2097,10 @@ mod tests { }) ); // Test blank. - assert!(Hosts::parse_value(" \t\t \t\t \t ") - .map_or(false, |val| matches!(val, Value::Blank))); + assert!( + Hosts::parse_value(" \t\t \t\t \t ") + .map_or(false, |val| matches!(val, Value::Blank)) + ); } #[test] fn test_wildcard_parse_value() { @@ -2195,8 +2209,10 @@ mod tests { }) ); // Test blank. - assert!(Wildcard::parse_value(" \t\t \t\t \t ") - .map_or(false, |val| matches!(val, Value::Blank))); + assert!( + Wildcard::parse_value(" \t\t \t\t \t ") + .map_or(false, |val| matches!(val, Value::Blank)) + ); } #[test] fn test_rpz_parse_value() { @@ -2253,8 +2269,10 @@ mod tests { }) ); // Test blank. - assert!(RpzDomain::parse_value(" \t\t \t\t \t ") - .map_or(false, |val| matches!(val, Value::Blank))); + assert!( + RpzDomain::parse_value(" \t\t \t\t \t ") + .map_or(false, |val| matches!(val, Value::Blank)) + ); } #[test] fn test_rpz_ord_and_eq() -> Result<(), &'static str> { diff --git a/src/file.rs b/src/file.rs @@ -40,7 +40,7 @@ impl<const IS_DIR: bool> AbsFilePath<IS_DIR> { let bytes = val.as_bytes(); if IS_DIR { self.path.as_mut_os_string().push(val); - bytes.last().map_or(true, |byt| { + bytes.last().is_none_or(|byt| { if *byt != b'/' { self.path.as_mut_os_string().push("/"); } @@ -50,15 +50,15 @@ impl<const IS_DIR: bool> AbsFilePath<IS_DIR> { // with `/` as well as verify the last component is not // `..` which is true iff `val == ".."` or the last 3 // characters of `val` is `/..`. - } else if bytes.last().map_or(false, |byt| { + } else if bytes.last().is_some_and(|byt| { *byt == b'/' || bytes .get(bytes.len().wrapping_sub(2)..) - .map_or(false, |byts| { + .is_some_and(|byts| { byts == b".." && bytes .get(bytes.len().wrapping_sub(3)) - .map_or(true, |byt2| *byt2 == b'/') + .is_none_or(|byt2| *byt2 == b'/') }) }) { false @@ -139,7 +139,7 @@ impl<'de, const IS_DIR: bool> Deserialize<'de> for AbsFilePath<IS_DIR> { { /// `Visitor` for `AbsFilePath`. struct FilePathVisitor<const IS_DIR: bool>; - impl<'de, const IS_DIR: bool> Visitor<'de> for FilePathVisitor<IS_DIR> { + impl<const IS_DIR: bool> Visitor<'_> for FilePathVisitor<IS_DIR> { type Value = AbsFilePath<IS_DIR>; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str("struct AbsFilePath") @@ -321,7 +321,7 @@ impl<'de> Deserialize<'de> for HttpUrl { { /// `Visitor` for `HttpUrl`. struct UrlVisitor; - impl<'d> Visitor<'d> for UrlVisitor { + impl Visitor<'_> for UrlVisitor { type Value = HttpUrl; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str("struct HttpUrl") diff --git a/src/lib.rs b/src/lib.rs @@ -1,4 +1,10 @@ -//! # `rpz` +//! [![git]](https://git.philomathiclife.com/rpz/log.html)&ensp;[![crates-io]](https://crates.io/crates/rpz)&ensp;[![docs-rs]](crate) +//! +//! [git]: https://git.philomathiclife.com/git_badge.svg +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! +//! `rpz` is a library for the binary `rpz`. //! //! The primary module is [`mod@dom`] which contains types which can parse //! [`str`]s into different kinds of domains. @@ -34,7 +40,12 @@ clippy::style, clippy::suspicious )] +#![allow( + unknown_lints, + reason = "OpenBSD does not recognize clippy::return_and_then" +)] #![expect( + clippy::arbitrary_source_item_ordering, clippy::blanket_clippy_restriction_lints, clippy::exhaustive_enums, clippy::exhaustive_structs, @@ -44,6 +55,7 @@ clippy::multiple_crate_versions, clippy::question_mark_used, clippy::ref_patterns, + clippy::return_and_then, clippy::single_char_lifetime_names, reason = "never want to use these lints" )] diff --git a/src/main.rs b/src/main.rs @@ -26,7 +26,13 @@ clippy::style, clippy::suspicious )] +#![allow( + unknown_lints, + reason = "OpenBSD does not recognize clippy::return_and_then" +)] +#![allow(clippy::pub_use, reason = "when priv_sep is used, we export UnveilErr")] #![expect( + clippy::arbitrary_source_item_ordering, clippy::blanket_clippy_restriction_lints, clippy::implicit_return, clippy::min_ident_chars, @@ -34,6 +40,7 @@ clippy::multiple_crate_versions, clippy::question_mark_used, clippy::ref_patterns, + clippy::return_and_then, clippy::single_call_fn, clippy::single_char_lifetime_names, clippy::unseparated_literal_suffix, @@ -69,7 +76,7 @@ use rpz::{ use std::{ collections::HashSet, fs, - io::{self, Read, Write}, + io::{self, Read as _, Write as _}, sync::OnceLock, }; use tokio::runtime::Builder; @@ -495,7 +502,7 @@ pub(crate) mod test_prog { } #[cfg(test)] mod tests { - use crate::{test_prog, E}; + use crate::{E, test_prog}; use std::io::Write; use std::process::Stdio; use std::thread; @@ -503,28 +510,32 @@ mod tests { #[ignore] fn test_app() { test_prog::verify_files(); - assert!(test_prog::get_command() - .args(["-f", "-"]) - .stderr(Stdio::piped()) - .stdin(Stdio::piped()) - .stdout(Stdio::null()) - .spawn() - .map_or(false, |mut cmd| { - cmd.stdin.take().map_or(false, |mut stdin| { - thread::spawn(move || { - stdin - .write_all( - format!("local_dir=\"{}\"", test_prog::PROG_DIR_NO_SUB).as_bytes(), - ) - .map_or(false, |_| true) + assert!( + test_prog::get_command() + .args(["-f", "-"]) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .spawn() + .map_or(false, |mut cmd| { + cmd.stdin.take().map_or(false, |mut stdin| { + thread::spawn(move || { + stdin + .write_all( + format!("local_dir=\"{}\"", test_prog::PROG_DIR_NO_SUB) + .as_bytes(), + ) + .map_or(false, |_| true) + }) + .join() + .map_or(false, |v| v) + }) && cmd.wait_with_output().map_or(false, |output| { + !output.status.success() + && output.stderr + == format!("Error: {:?}\n", E::NoBlockEntries).into_bytes() }) - .join() - .map_or(false, |v| v) - }) && cmd.wait_with_output().map_or(false, |output| { - !output.status.success() - && output.stderr == format!("Error: {:?}\n", E::NoBlockEntries).into_bytes() }) - })); + ); assert!(test_prog::get_command() .args(["-vf", "-"]) .stderr(Stdio::null()) diff --git a/src/priv_sep.rs b/src/priv_sep.rs @@ -52,7 +52,7 @@ pub fn pledge_away_create_write(promises: &mut Promises) -> Result<(), Error> { /// No-op that always returns `Ok`. #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")] #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))] -pub fn pledge_away_create_write(_: &mut Zst) -> Result<(), !> { +pub const fn pledge_away_create_write(_: &mut Zst) -> Result<(), !> { Ok(()) } /// Removes `Promise::Unveil`. @@ -63,7 +63,7 @@ pub fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Error> { /// No-op that always returns `Ok`. #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")] #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))] -pub fn pledge_away_unveil(_: &mut Zst) -> Result<(), !> { +pub const fn pledge_away_unveil(_: &mut Zst) -> Result<(), !> { Ok(()) } /// Removes `Promise::Dns` and `Promise::Inet`. @@ -74,7 +74,7 @@ pub fn pledge_away_net(promises: &mut Promises) -> Result<(), Error> { /// No-op that always returns `Ok`. #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")] #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))] -pub fn pledge_away_net(_: &mut Zst) -> Result<(), !> { +pub const fn pledge_away_net(_: &mut Zst) -> Result<(), !> { Ok(()) } /// Removes all `Promise`s except `Stdio`. @@ -85,7 +85,7 @@ pub fn pledge_away_all_but_stdio(promises: &mut Promises) -> Result<(), Error> { /// No-op that always returns `Ok`. #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")] #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))] -pub fn pledge_away_all_but_stdio(_: &mut Zst) -> Result<(), !> { +pub const fn pledge_away_all_but_stdio(_: &mut Zst) -> Result<(), !> { Ok(()) } /// Calls `unveil`_on `path` with no `Permissions`. @@ -123,7 +123,10 @@ pub fn unveil_create<P: AsRef<Path>>(_: P) -> Result<(), !> { } /// Calls `unveil`_on `path` with `Permissions::READ` and returns /// `true` iff `path` exists. -#[expect(clippy::wildcard_enum_match_arm)] +#[expect( + clippy::wildcard_enum_match_arm, + reason = "ErrorKind is non_exhaustive" +)] #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] pub fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, UnveilErr> { Permissions::READ.unveil(path).map_or_else( @@ -167,7 +170,7 @@ pub fn unveil_read_file<P: AsRef<Path>>(_: P) -> Result<(), !> { /// in addition to setting the `SSL_CERT_FILE` variable to `/etc/ssl/cert.pem`. /// We rely on `SSL_CERT_FILE` as that allows one to `unveil(2)` only `/etc/ssl/cert.pem` /// instead of `/etc/ssl/`. -#[expect(unsafe_code)] +#[expect(unsafe_code, reason = "safety comment justifies its correctness")] #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] pub fn unveil_https() -> Result<(), UnveilErr> { /// The path to the root certificate store.