commit 077af97bbeb8f467def905c19a9a5ea0c8b43043
parent 60bc4bf481e42e8f074051eff90bf32610c84b96
Author: Zack Newman <zack@philomathiclife.com>
Date: Wed, 18 Mar 2026 18:06:44 -0600
update deps and lints. address lints and new deps
Diffstat:
| M | Cargo.toml | | | 193 | +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- |
| M | README.md | | | 13 | ++++++------- |
| M | src/app.rs | | | 2 | +- |
| M | src/args.rs | | | 54 | +++++++++++++++++++++++++++--------------------------- |
| M | src/config.rs | | | 122 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
| M | src/dom.rs | | | 332 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ |
| M | src/file.rs | | | 6 | +++--- |
| M | src/lib.rs | | | 9 | +++++++-- |
| M | src/main.rs | | | 184 | ++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- |
| M | src/priv_sep.rs | | | 194 | ++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- |
10 files changed, 640 insertions(+), 469 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,59 +9,108 @@ license = "MIT OR Apache-2.0"
name = "rpz"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/rpz/"
-version = "1.4.1"
+version = "2.1.0"
[lints.rust]
-ambiguous_negative_literals = { level = "deny", priority = -1 }
-closure_returning_async_block = { level = "deny", priority = -1 }
-deprecated_safe = { level = "deny", priority = -1 }
-deref_into_dyn_supertrait = { level = "deny", priority = -1 }
-ffi_unwind_calls = { level = "deny", priority = -1 }
-future_incompatible = { level = "deny", priority = -1 }
-#fuzzy_provenance_casts = { level = "deny", priority = -1 }
-impl_trait_redundant_captures = { level = "deny", priority = -1 }
-keyword_idents = { level = "deny", priority = -1 }
-let_underscore = { level = "deny", priority = -1 }
-#linker_messages = { level = "deny", priority = -1 }
-#lossy_provenance_casts = { level = "deny", priority = -1 }
-macro_use_extern_crate = { level = "deny", priority = -1 }
-meta_variable_misuse = { level = "deny", priority = -1 }
-missing_copy_implementations = { level = "deny", priority = -1 }
-missing_debug_implementations = { level = "deny", priority = -1 }
-missing_docs = { level = "deny", priority = -1 }
-#multiple_supertrait_upcastable = { level = "deny", priority = -1 }
-#must_not_suspend = { level = "deny", priority = -1 }
-non_ascii_idents = { level = "deny", priority = -1 }
-#non_exhaustive_omitted_patterns = { level = "deny", priority = -1 }
-nonstandard_style = { level = "deny", priority = -1 }
-redundant_imports = { level = "deny", priority = -1 }
-redundant_lifetimes = { level = "deny", priority = -1 }
-refining_impl_trait = { level = "deny", priority = -1 }
-rust_2018_compatibility = { level = "deny", priority = -1 }
-rust_2018_idioms = { level = "deny", priority = -1 }
-rust_2021_compatibility = { level = "deny", priority = -1 }
-rust_2024_compatibility = { level = "deny", priority = -1 }
-single_use_lifetimes = { level = "deny", priority = -1 }
-#supertrait_item_shadowing_definition = { level = "deny", priority = -1 }
-trivial_casts = { level = "deny", priority = -1 }
-trivial_numeric_casts = { level = "deny", priority = -1 }
-unit_bindings = { level = "deny", priority = -1 }
-unnameable_types = { level = "deny", priority = -1 }
-#unqualified_local_imports = { level = "deny", priority = -1 }
-unreachable_pub = { level = "deny", priority = -1 }
-unsafe_code = { level = "deny", priority = -1 }
-unstable_features = { level = "deny", priority = -1 }
+deprecated-safe = { level = "deny", priority = -1 }
+future-incompatible = { level = "deny", priority = -1 }
+keyword-idents = { level = "deny", priority = -1 }
+let-underscore = { level = "deny", priority = -1 }
+nonstandard-style = { level = "deny", priority = -1 }
+refining-impl-trait = { level = "deny", priority = -1 }
+rust-2018-compatibility = { level = "deny", priority = -1 }
+rust-2018-idioms = { level = "deny", priority = -1 }
+rust-2021-compatibility = { level = "deny", priority = -1 }
+rust-2024-compatibility = { level = "deny", priority = -1 }
+unknown-or-malformed-diagnostic-attributes = { level = "deny", priority = -1 }
unused = { level = "deny", priority = -1 }
-unused_crate_dependencies = { level = "deny", priority = -1 }
-unused_import_braces = { level = "deny", priority = -1 }
-unused_lifetimes = { level = "deny", priority = -1 }
-unused_qualifications = { level = "deny", priority = -1 }
-unused_results = { level = "deny", priority = -1 }
-variant_size_differences = { level = "deny", priority = -1 }
warnings = { level = "deny", priority = -1 }
+ambiguous-negative-literals = { level = "deny", priority = -1 }
+closure-returning-async-block = { level = "deny", priority = -1 }
+deprecated-in-future = { level = "deny", priority = -1 }
+deref-into-dyn-supertrait = { level = "deny", priority = -1 }
+ffi-unwind-calls = { level = "deny", priority = -1 }
+#fuzzy-provenance-casts = { level = "deny", priority = -1 }
+impl-trait-redundant-captures = { level = "deny", priority = -1 }
+linker-messages = { level = "deny", priority = -1 }
+#lossy-provenance-casts = { level = "deny", priority = -1 }
+macro-use-extern-crate = { level = "deny", priority = -1 }
+meta-variable-misuse = { level = "deny", priority = -1 }
+missing-copy-implementations = { level = "deny", priority = -1 }
+missing-debug-implementations = { level = "deny", priority = -1 }
+missing-docs = { level = "deny", priority = -1 }
+#multiple-supertrait-upcastable = { level = "deny", priority = -1 }
+#must-not-suspend = { level = "deny", priority = -1 }
+non-ascii-idents = { level = "deny", priority = -1 }
+#non-exhaustive-omitted-patterns = { level = "deny", priority = -1 }
+redundant-imports = { level = "deny", priority = -1 }
+redundant-lifetimes = { level = "deny", priority = -1 }
+#resolving-to-items-shadowing-supertrait-items = { level = "deny", priority = -1 }
+#shadowing-supertrait-items = { level = "deny", priority = -1 }
+single-use-lifetimes = { level = "deny", priority = -1 }
+trivial-casts = { level = "deny", priority = -1 }
+trivial-numeric-casts = { level = "deny", priority = -1 }
+unit-bindings = { level = "deny", priority = -1 }
+unnameable-types = { level = "deny", priority = -1 }
+#unqualified-local-imports = { level = "deny", priority = -1 }
+unreachable-pub = { level = "deny", priority = -1 }
+unsafe-code = { level = "deny", priority = -1 }
+unstable-features = { level = "deny", priority = -1 }
+unused-crate-dependencies = { level = "deny", priority = -1 }
+unused-import-braces = { level = "deny", priority = -1 }
+unused-lifetimes = { level = "deny", priority = -1 }
+unused-qualifications = { level = "deny", priority = -1 }
+unused-results = { level = "deny", priority = -1 }
+variant-size-differences = { level = "deny", priority = -1 }
+# Before publishing to crates.io, comment above and uncomment below.
+#warnings = { level = "allow", priority = -1 }
+#ambiguous-associated-items = { level = "allow", priority = -1 }
+#arithmetic-overflow = { level = "allow", priority = -1 }
+#binary-asm-labels = { level = "allow", priority = -1 }
+#bindings-with-variant-name = { level = "allow", priority = -1 }
+#conflicting-repr-hints = { level = "allow", priority = -1 }
+#dangerous-implicit-autorefs = { level = "allow", priority = -1 }
+#default-overrides-default-fields = { level = "allow", priority = -1 }
+#dependency-on-unit-never-type-fallback = { level = "allow", priority = -1 }
+#deref-nullptr = { level = "allow", priority = -1 }
+#elided-lifetimes-in-associated-constant = { level = "allow", priority = -1 }
+#enum-intrinsics-non-enums = { level = "allow", priority = -1 }
+#explicit-builtin-cfgs-in-flags = { level = "allow", priority = -1 }
+#ill-formed-attribute-input = { level = "allow", priority = -1 }
+#incomplete-include = { level = "allow", priority = -1 }
+#ineffective-unstable-trait-impl = { level = "allow", priority = -1 }
+#invalid-atomic-ordering = { level = "allow", priority = -1 }
+#invalid-from-utf8-unchecked = { level = "allow", priority = -1 }
+#invalid-macro-export-arguments = { level = "allow", priority = -1 }
+#invalid-null-arguments = { level = "allow", priority = -1 }
+#invalid-reference-casting = { level = "allow", priority = -1 }
+#invalid-type-param-default = { level = "allow", priority = -1 }
+#legacy-derive-helpers = { level = "allow", priority = -1 }
+#let-underscore-lock = { level = "allow", priority = -1 }
+#long-running-const-eval = { level = "allow", priority = -1 }
+#macro-expanded-macro-exports-accessed-by-absolute-paths = { level = "allow", priority = -1 }
+#mutable-transmutes = { level = "allow", priority = -1 }
+#named-asm-labels = { level = "allow", priority = -1 }
+#never-type-fallback-flowing-into-unsafe = { level = "allow", priority = -1 }
+#no-mangle-const-items = { level = "allow", priority = -1 }
+#out-of-scope-macro-calls = { level = "allow", priority = -1 }
+#overflowing-literals = { level = "allow", priority = -1 }
+#patterns-in-fns-without-body = { level = "allow", priority = -1 }
+#proc-macro-derive-resolution-fallback = { level = "allow", priority = -1 }
+#pub-use-of-private-extern-crate = { level = "allow", priority = -1 }
+#repr-transparent-non-zst-fields = { level = "allow", priority = -1 }
+#semicolon-in-expressions-from-macros = { level = "allow", priority = -1 }
+#soft-unstable = { level = "allow", priority = -1 }
+#test-unstable-lint = { level = "allow", priority = -1 }
+#text-direction-codepoint-in-comment = { level = "allow", priority = -1 }
+#text-direction-codepoint-in-literal = { level = "allow", priority = -1 }
+#unconditional-panic = { level = "allow", priority = -1 }
+#undropped-manually-drops = { level = "allow", priority = -1 }
+#unknown-crate-types = { level = "allow", priority = -1 }
+#useless-deprecated = { level = "allow", priority = -1 }
+# Before publishing to crates.io, comment below.
[lints.clippy]
-all = { level = "deny", priority = -1 }
cargo = { level = "deny", priority = -1 }
complexity = { level = "deny", priority = -1 }
correctness = { level = "deny", priority = -1 }
@@ -88,28 +137,46 @@ single_call_fn = "allow"
single_char_lifetime_names = "allow"
unseparated_literal_suffix = "allow"
-[dependencies]
-ascii_domain = { version = "0.6.6", default-features = false }
-num-bigint = { version = "0.4.6", default-features = false }
-priv_sep = { version = "3.0.0-alpha.1.3", default-features = false, optional = true }
-reqwest = { version = "0.12.23", default-features = false, features = ["brotli", "deflate", "gzip", "rustls-tls-native-roots", "trust-dns"] }
-serde = { version = "1.0.219", default-features = false }
-superset_map = { version = "0.3.4", default-features = false }
-tokio = { version = "1.47.1", default-features = false, features = ["rt", "time"] }
-toml = { version = "0.9.5", default-features = false, features = ["parse", "serde"] }
-url = { version = "2.5.7", default-features = false, features = ["serde"] }
-zfc = { version = "0.4.4", default-features = false }
+[lints.rustdoc]
+# Before publishing to crates.io, comment below and uncomment below that.
+all = { level = "deny", priority = -1 }
+#all = "allow"
+[package.metadata.docs.rs]
+default-target = "x86_64-unknown-linux-gnu"
+targets = [
+ "aarch64-apple-darwin",
+ "aarch64-pc-windows-msvc",
+ "aarch64-unknown-linux-gnu",
+ "i686-pc-windows-msvc",
+ "i686-unknown-linux-gnu",
+ "x86_64-pc-windows-gnu",
+ "x86_64-pc-windows-msvc",
+ "x86_64-unknown-freebsd",
+ "x86_64-unknown-linux-musl",
+ "x86_64-unknown-netbsd"
+]
-### FEATURES #################################################################
+[dependencies]
+ascii_domain = { version = "0.6.9", default-features = false }
+num-bigint = { version = "0.4.6", default-features = false }
+serde = { version = "1.0.228", default-features = false }
+superset_map = { version = "0.3.6", default-features = false }
+tokio = { version = "1.50.0", default-features = false, features = ["rt", "time"] }
+toml = { version = "1.0.7", default-features = false, features = ["parse", "serde"] }
+url = { version = "2.5.8", default-features = false, features = ["serde"] }
+zfc = { version = "0.4.7", default-features = false }
-[features]
-default = ["priv_sep"]
+[target.'cfg(not(target_os = "openbsd"))'.dependencies]
+reqwest = { version = "0.13.2", default-features = false, features = ["brotli", "deflate", "gzip", "http2", "rustls"] }
-# Provide pledge and unveil for the binary crate on OpenBSD platforms.
-priv_sep = ["dep:priv_sep"]
+[target.'cfg(target_os = "openbsd")'.dependencies]
+priv_sep = { version = "3.0.0-alpha.5.0", default-features = false, features = ["std"] }
+reqwest = { version = "0.13.2", default-features = false, features = ["brotli", "deflate", "gzip", "http2", "rustls-no-provider"] }
+rustls = { version = "0.23.37", default-features = false, features = ["ring"] }
[profile.release]
+codegen-units = 1
lto = true
panic = 'abort'
strip = true
diff --git a/README.md b/README.md
@@ -210,15 +210,14 @@ 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.
+Before any PR is sent, `cargo clippy --all-targets`, `cargo test --all-targets`, and `cargo test --doc` should be
+run _for each possible combination of "features"_ using the nightly toolchain. One easy way to achieve this
+is by invoking [`ci-cargo`](https://crates.io/crates/ci-cargo) as
+`ci-cargo clippy --all-targets test --all-targets --include-ignored` in the `rpz` directory.
+
+Last, `cargo +nightly doc` should be run to ensure documentation can be built.
### Status
The crate is only tested on the `x86_64-unknown-linux-gnu`, `x86_64-unknown-openbsd`, and `aarch64-apple-darwin`
targets; but it should work on most platforms.
-
-Nightly `rustc` is required. Once `BTreeMap` [cursors are stabilized](https://github.com/rust-lang/rust/issues/107540), stable `rustc` will work.
-On OpenBSD-stable, one can use the `rust` port as long as `RUSTC_BOOTSTRAP` is `export`ed with a value of `1` before invoking
-`cargo build --all-features --release` or `cargo install --all-features rpz`.
diff --git a/src/app.rs b/src/app.rs
@@ -161,7 +161,7 @@ impl<'unblock, 'block> Domains<'unblock, 'block> {
summaries
}
/// Writes all necessary unblock `RpzDomain`s as `<domain> CNAME rpz-passthru.`
- /// followed by all block `RpzDomain`s as `<domain> CNAME .`
+ /// followed by all block `RpzDomain`s as `<domain> CNAME .`.
///
/// When subdomains exist, `*.<domain>` is used.
///
diff --git a/src/args.rs b/src/args.rs
@@ -197,12 +197,13 @@ impl Opts {
mod tests {
use crate::{ArgsErr, E, test_prog};
use core::convert;
- use std::io::Write;
+ use std::io::Write as _;
use std::process::Stdio;
use std::thread;
+ #[expect(clippy::too_many_lines, reason = "a lot to test")]
#[test]
- #[ignore]
- fn test_args() {
+ #[ignore = "requires I/O"]
+ fn args() {
test_prog::verify_files();
assert!(
test_prog::get_command()
@@ -210,7 +211,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::NoArgs)).into_bytes()
@@ -223,7 +224,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
@@ -237,7 +238,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
@@ -251,7 +252,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
@@ -265,7 +266,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
@@ -279,7 +280,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
@@ -293,7 +294,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
@@ -307,7 +308,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
@@ -321,7 +322,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption))
@@ -335,7 +336,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!(
@@ -352,7 +353,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption))
@@ -366,7 +367,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath))
@@ -380,7 +381,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath))
@@ -394,7 +395,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| {
+ .is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!(
@@ -411,7 +412,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::null())
.output()
- .map_or(false, |output| !output.status.success()
+ .is_ok_and(|output| !output.status.success()
&& output.stderr.get(..23) == Some(b"Error: TOML parse error"))
);
assert!(
@@ -421,16 +422,16 @@ mod tests {
.stdin(Stdio::piped())
.stdout(Stdio::null())
.spawn()
- .map_or(false, |mut cmd| {
- cmd.stdin.take().map_or(false, |mut stdin| {
+ .is_ok_and(|mut cmd| {
+ cmd.stdin.take().is_some_and(|mut stdin| {
thread::spawn(move || {
stdin
.write_all(b"junk")
- .map_or(false, |_| stdin.flush().map_or(false, |_| true))
+ .is_ok_and(|()| stdin.flush().is_ok())
})
.join()
- .map_or(false, convert::identity)
- }) && cmd.wait_with_output().map_or(false, |output| {
+ .is_ok_and(convert::identity)
+ }) && cmd.wait_with_output().is_ok_and(|output| {
!output.status.success()
&& output.stderr.get(..23) == Some(b"Error: TOML parse error")
})
@@ -443,9 +444,8 @@ mod tests {
.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()))
+ .is_ok_and(|output| output.status.success()
+ && output.stdout.get(..crate::HELP.len()) == Some(crate::HELP.as_bytes()))
);
assert!(
test_prog::get_command()
@@ -454,7 +454,7 @@ mod tests {
.stdin(Stdio::null())
.stdout(Stdio::piped())
.output()
- .map_or(false, |output| output.status.success()
+ .is_ok_and(|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
@@ -300,114 +300,120 @@ impl<'de> Deserialize<'de> for Config {
#[cfg(test)]
mod tests {
use crate::Config;
+ #[expect(clippy::unwrap_used, reason = "OK in tests")]
#[test]
- fn test_missing_fields() {
- assert!(toml::from_str::<Config>("").is_err());
- assert!(toml::from_str::<Config>("timeout=15").is_err());
- assert!(toml::from_str::<Config>(r#"rpz="/foo""#).is_err());
- assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok());
- assert!(toml::from_str::<Config>(r#"adblock=["https://foo.com/foo"]"#).is_ok());
+ fn missing_fields() {
+ drop(toml::from_str::<Config>("").unwrap_err());
+ drop(toml::from_str::<Config>("timeout=15").unwrap_err());
+ drop(toml::from_str::<Config>(r#"rpz="/foo""#).unwrap_err());
+ drop(toml::from_str::<Config>(r#"local_dir="/foo/""#).unwrap());
+ drop(toml::from_str::<Config>(r#"adblock=["https://foo.com/foo"]"#).unwrap());
}
+ #[expect(clippy::unwrap_used, reason = "OK in tests")]
#[test]
- fn test_invalid_fields() {
- assert!(toml::from_str::<Config>("bob=15").is_err());
- assert!(toml::from_str::<Config>(r#"foo=["https://foo.com/foo"]"#).is_err());
+ fn invalid_fields() {
+ drop(toml::from_str::<Config>("bob=15").unwrap_err());
+ drop(toml::from_str::<Config>(r#"foo=["https://foo.com/foo"]"#).unwrap_err());
}
+ #[expect(clippy::unwrap_used, reason = "OK in tests")]
#[test]
- fn test_timeout() {
- assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok());
- assert!(
+ fn timeout() {
+ drop(toml::from_str::<Config>(r#"local_dir="/foo/""#).unwrap());
+ drop(
toml::from_str::<Config>(
r#"timeout=15
-local_dir="/foo/""#
+local_dir="/foo/""#,
)
- .is_ok()
+ .unwrap(),
);
- assert!(
+ drop(
toml::from_str::<Config>(
r#"timeout=4294967295
-local_dir="/foo/""#
+local_dir="/foo/""#,
)
- .is_ok()
+ .unwrap(),
);
- assert!(
+ drop(
toml::from_str::<Config>(
r#"timeout=-1
-local_dir="/foo/""#
+local_dir="/foo/""#,
)
- .is_err()
+ .unwrap_err(),
);
- assert!(
+ drop(
toml::from_str::<Config>(
r#"timeout=0
-local_dir="/foo/""#
+local_dir="/foo/""#,
)
- .is_ok()
+ .unwrap(),
);
- assert!(
+ drop(
toml::from_str::<Config>(
r#"timeout=4294967296
-local_dir="/foo/""#
+local_dir="/foo/""#,
)
- .is_err()
+ .unwrap_err(),
);
}
+ #[expect(clippy::unwrap_used, reason = "OK in tests")]
#[test]
- fn test_arrays() {
- assert!(
+ fn arrays() {
+ drop(
toml::from_str::<Config>(r#"adblock=["https://foo.com/foo","https://foo.com/foo"]"#)
- .is_err()
+ .unwrap_err(),
);
- assert!(toml::from_str::<Config>(r#"adblock=["https://foo.com/foo"]"#).is_ok());
- assert!(
+ drop(toml::from_str::<Config>(r#"adblock=["https://foo.com/foo"]"#).unwrap());
+ drop(
toml::from_str::<Config>(
r#"adblock=["https://foo.com/foo"]
-domain=["https://foo.com/foo"]"#
+domain=["https://foo.com/foo"]"#,
)
- .is_err()
+ .unwrap_err(),
);
}
+ #[expect(clippy::unwrap_used, reason = "OK in tests")]
#[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!(
+ fn urls() {
+ drop(toml::from_str::<Config>(r#"adblock=["file://foo.com/foo"]"#).unwrap_err());
+ drop(toml::from_str::<Config>(r#"adblock=["foo.com/foo"]"#).unwrap_err());
+ drop(toml::from_str::<Config>(r#"adblock=["http://foo.com/foo"]"#).unwrap());
+ drop(
toml::from_str::<Config>(
r#"adblock=[]
-domain=["https:///foo"]"#
+domain=["https:///foo"]"#,
)
- .is_ok()
+ .unwrap(),
);
- 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());
+ drop(toml::from_str::<Config>(r#"adblock=[""]"#).unwrap_err());
+ drop(toml::from_str::<Config>(r#"adblock=["https://"]"#).unwrap_err());
+ drop(toml::from_str::<Config>(r#"adblock=["ftp://foo.com/foo"]"#).unwrap_err());
}
+ #[expect(clippy::unwrap_used, reason = "OK in tests")]
#[test]
- fn test_paths() {
- assert!(
+ fn paths() {
+ drop(
toml::from_str::<Config>(
r#"rpz="/foo/"
-wildcard=["https://foo.com/foo"]"#
+wildcard=["https://foo.com/foo"]"#,
)
- .is_err()
+ .unwrap_err(),
);
- assert!(
+ drop(
toml::from_str::<Config>(
r#"rpz="foo"
-wildcard=["https://foo.com/foo"]"#
+wildcard=["https://foo.com/foo"]"#,
)
- .is_err()
+ .unwrap_err(),
);
- assert!(
+ drop(
toml::from_str::<Config>(
r#"rpz="/foo"
-wildcard=["https://foo.com/foo"]"#
+wildcard=["https://foo.com/foo"]"#,
)
- .is_ok()
+ .unwrap(),
);
- assert!(toml::from_str::<Config>(r#"local_dir="foo/""#).is_err());
- assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok());
+ drop(toml::from_str::<Config>(r#"local_dir="foo/""#).unwrap_err());
+ drop(toml::from_str::<Config>(r#"local_dir="/foo/""#).unwrap());
// Directories are allowed to not have a trailing `/`, but they will get it
// added.
assert!(
@@ -415,9 +421,9 @@ wildcard=["https://foo.com/foo"]"#
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/"))))
+ .is_ok_and(|config| config
+ .local_dir
+ .is_some_and(|dir| dir.to_str().is_some_and(|val| val == "/foo/")))
);
}
}
diff --git a/src/dom.rs b/src/dom.rs
@@ -1697,7 +1697,7 @@ impl Ord for RpzDomain<'_> {
/// For example the following is a sequence of domains in
/// ascending order:
///
- /// `bar.com`, `www.bar.com`, `*.www.bar.com`, `||www.bar.com`, `*.bar.com`, `||bar.com`, `example.com`, `www.example.com`, `*.www.example.com`, `||www.example.com`, `*.example.com`, `||example.com`, `foo.com`, `www.foo.com`, `*.foo.com`, `*.com`, `example.net`, `*.net`
+ /// `bar.com`, `www.bar.com`, `*.www.bar.com`, `||www.bar.com`, `*.bar.com`, `||bar.com`, `example.com`, `www.example.com`, `*.www.example.com`, `||www.example.com`, `*.example.com`, `||example.com`, `foo.com`, `www.foo.com`, `*.foo.com`, `*.com`, `example.net`, `*.net`.
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
match *self {
@@ -1917,16 +1917,16 @@ impl<'a> ParsedDomain<'a> for RpzDomain<'a> {
#[cfg(test)]
mod tests {
use super::{
- Adblock, DomainOnly, FirefoxDomainErr, Hosts, ParsedDomain, RpzDomain, Value, Wildcard,
+ Adblock, DomainOnly, FirefoxDomainErr, Hosts, ParsedDomain as _, RpzDomain, Value, Wildcard,
};
use ascii_domain::dom::DomainErr;
use num_bigint::BigUint;
use superset_map::SupersetSet;
#[test]
- fn test_adblock_parse() {
+ fn adblock_parse() {
// Test subdomains.
assert!(
- Adblock::parse_value("||www.example.com").map_or(false, |val| match val {
+ Adblock::parse_value("||www.example.com").is_ok_and(|val| match val {
Value::Domain(ref dom) =>
dom.subdomains && dom.domain.as_bytes() == b"www.example.com",
Value::Comment(_) | Value::Blank => false,
@@ -1934,8 +1934,7 @@ mod tests {
);
// Test whitespace and '^' removal.
assert!(
- Adblock::parse_value(" \t\t ||\t\t \twww.example.com \t\t ^ \t\t ").map_or(
- false,
+ Adblock::parse_value(" \t\t ||\t\t \twww.example.com \t\t ^ \t\t ").is_ok_and(
|val| match val {
Value::Domain(ref dom) =>
dom.subdomains && dom.domain.as_bytes() == b"www.example.com",
@@ -1944,7 +1943,7 @@ mod tests {
)
);
assert!(
- Adblock::parse_value("\t\t \twww.example.com \t\t \t\t ").map_or(false, |val| {
+ Adblock::parse_value("\t\t \twww.example.com \t\t \t\t ").is_ok_and(|val| {
match val {
Value::Domain(ref dom) => {
!dom.subdomains && dom.domain.as_bytes() == b"www.example.com"
@@ -1953,66 +1952,60 @@ mod tests {
}
})
);
- assert!(Adblock::parse_value("www .example.com").map_or_else(
+ assert!(Adblock::parse_value("www .example.com").is_err_and(
|err| err == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')),
- |_| false
));
- assert!(
- Adblock::parse_value("||www.ExAMPle.COm").map_or(false, |val| {
- match val {
- Value::Domain(ref dom) => {
- Adblock::parse_value("||www.example.com").map_or(false, |val| match val {
- Value::Domain(ref dom2) => {
- dom == dom2
- && dom.subdomains
- && dom2.subdomains
- && dom.cmp(dom2).is_eq()
- }
- Value::Comment(_) | Value::Blank => false,
- })
- }
- Value::Comment(_) | Value::Blank => false,
+ assert!(Adblock::parse_value("||www.ExAMPle.COm").is_ok_and(|val| {
+ match val {
+ Value::Domain(ref dom) => {
+ Adblock::parse_value("||www.example.com").is_ok_and(|val_2| match val_2 {
+ Value::Domain(ref dom2) => {
+ dom == dom2
+ && dom.subdomains
+ && dom2.subdomains
+ && dom.cmp(dom2).is_eq()
+ }
+ Value::Comment(_) | Value::Blank => false,
+ })
}
- })
- );
+ Value::Comment(_) | Value::Blank => false,
+ }
+ }));
// Test comment
assert!(
- Adblock::parse_value(" \t\t #hi").map_or(false, |val| match val {
+ Adblock::parse_value(" \t\t #hi").is_ok_and(|val| match val {
Value::Comment(com) => com == "hi",
Value::Domain(_) | Value::Blank => false,
})
);
assert!(
- Adblock::parse_value(" \t\t !! foo").map_or(false, |val| match val {
+ Adblock::parse_value(" \t\t !! foo").is_ok_and(|val| match val {
Value::Comment(com) => com == "! foo",
Value::Domain(_) | Value::Blank => false,
})
);
// Test blank
- assert!(Adblock::parse_value(" \t\t ").map_or(false, |val| matches!(val, Value::Blank)));
+ assert!(Adblock::parse_value(" \t\t ").is_ok_and(|val| matches!(val, Value::Blank)));
}
#[test]
- fn test_domain_only_parse_value() {
+ fn domain_only_parse_value() {
// Test whitespace and comment.
assert!(
DomainOnly::parse_value(" \t\t \t\t \twww.example.com#asdflkj asdf alskdfj ")
- .map_or(false, |val| match val {
+ .is_ok_and(|val| match val {
Value::Domain(ref dom) => dom.domain.as_bytes() == b"www.example.com",
Value::Comment(_) | Value::Blank => false,
})
);
assert!(
DomainOnly::parse_value(" \t\t \t\t \twww.example.com \t\t ^ \t\t ")
- .map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')),
- |_| false
- )
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
);
// Test case-insensitivity.
assert!(
- DomainOnly::parse_value("www.ExAMPle.CoM").map_or(false, |val| match val {
+ DomainOnly::parse_value("www.ExAMPle.CoM").is_ok_and(|val| match val {
Value::Domain(ref dom) =>
- DomainOnly::parse_value("www.example.com").map_or(false, |val2| match val2 {
+ DomainOnly::parse_value("www.example.com").is_ok_and(|val2| match val2 {
Value::Domain(ref dom2) => dom.cmp(dom2).is_eq(),
Value::Comment(_) | Value::Blank => false,
}),
@@ -2021,7 +2014,7 @@ mod tests {
);
// Test comment.
assert!(
- DomainOnly::parse_value(" \t\t \t\t \t # hi").map_or(false, |val| match val {
+ DomainOnly::parse_value(" \t\t \t\t \t # hi").is_ok_and(|val| match val {
Value::Comment(com) => com == " hi",
Value::Domain(_) | Value::Blank => false,
})
@@ -2029,63 +2022,59 @@ mod tests {
// Test blank.
assert!(
DomainOnly::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank))
+ .is_ok_and(|val| matches!(val, Value::Blank))
);
// Test blank.
assert!(
DomainOnly::parse_value("example.xn--abc")
- .map_or(false, |val| matches!(val, Value::Domain(_)))
+ .is_ok_and(|val| matches!(val, Value::Domain(_)))
);
// Test invalid TLD.
assert!(
DomainOnly::parse_value("www.c1m")
- .map_or_else(|err| err == FirefoxDomainErr::InvalidTld, |_| false)
+ .is_err_and(|err| err == FirefoxDomainErr::InvalidTld)
);
}
#[test]
- fn test_hosts_parse_value() {
+ fn 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 {
+ .is_ok_and(|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(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')),
- |_| false
- )
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
+ );
+ assert!(
+ Hosts::parse_value("::1\twww .example.com")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
);
- assert!(Hosts::parse_value("::1\twww .example.com").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')),
- |_| false
- ));
// Test invalid IP
assert!(
Hosts::parse_value("::2 www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
);
assert!(
Hosts::parse_value(":2 www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
);
assert!(
Hosts::parse_value("www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
);
assert!(
Hosts::parse_value("10.4.2.256 www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
);
// Test case-insensitivity.
assert!(
- Hosts::parse_value(":: www.ExAMPle.Com").map_or(false, |val| match val {
- Value::Domain(ref dom) =>
- Hosts::parse_value("127.0.0.1 www.example.com").map_or(false, |val2| match val2
- {
+ Hosts::parse_value(":: www.ExAMPle.Com").is_ok_and(|val| match val {
+ Value::Domain(ref dom) => Hosts::parse_value("127.0.0.1 www.example.com")
+ .is_ok_and(|val2| match val2 {
Value::Domain(ref dom2) => dom.cmp(dom2).is_eq(),
Value::Comment(_) | Value::Blank => false,
}),
@@ -2094,49 +2083,48 @@ mod tests {
);
// Test comment.
assert!(
- Hosts::parse_value(" \t\t \t\t \t # hi").map_or(false, |val| match val {
+ Hosts::parse_value(" \t\t \t\t \t # hi").is_ok_and(|val| match val {
Value::Comment(com) => com == " hi",
Value::Domain(_) | Value::Blank => false,
})
);
// Test blank.
assert!(
- Hosts::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank))
+ Hosts::parse_value(" \t\t \t\t \t ").is_ok_and(|val| matches!(val, Value::Blank))
);
}
#[test]
- fn test_wildcard_parse_value() {
+ fn wildcard_parse_value() {
// Test bad asterisk.
- assert!(Wildcard::parse_value("*").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')),
- |_| false
- ));
- assert!(Wildcard::parse_value("www*.example.com").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')),
- |_| false
- ));
- assert!(Wildcard::parse_value("www.*.com").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')),
- |_| false
- ));
- assert!(Wildcard::parse_value("*..com").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::EmptyLabel),
- |_| false
- ));
- assert!(Wildcard::parse_value("www.com*").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')),
- |_| false
- ));
- assert!(Wildcard::parse_value("ww*w.com").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')),
- |_| false
- ));
+ assert!(
+ Wildcard::parse_value("*")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
+ );
+ assert!(
+ Wildcard::parse_value("www*.example.com")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
+ );
+ assert!(
+ Wildcard::parse_value("www.*.com")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
+ );
+ assert!(
+ Wildcard::parse_value("*..com")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::EmptyLabel))
+ );
+ assert!(
+ Wildcard::parse_value("www.com*")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
+ );
+ assert!(
+ Wildcard::parse_value("ww*w.com")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
+ );
// Test case-insensitivity.
assert!(
- Wildcard::parse_value("*.wWw.ExamPLE.com").map_or(false, |val| match val {
+ Wildcard::parse_value("*.wWw.ExamPLE.com").is_ok_and(|val| match val {
Value::Domain(ref dom) =>
- Wildcard::parse_value("*.www.example.com").map_or(false, |val2| match val2 {
+ Wildcard::parse_value("*.www.example.com").is_ok_and(|val2| match val2 {
Value::Domain(ref dom2) =>
dom.cmp(dom2).is_eq()
&& dom == dom2
@@ -2149,7 +2137,7 @@ mod tests {
);
// Test proper subdomains.
assert!(
- Wildcard::parse_value("*.www.example.com").map_or(false, |val| match val {
+ Wildcard::parse_value("*.www.example.com").is_ok_and(|val| match val {
Value::Domain(ref dom) =>
dom.domain.as_bytes() == b"www.example.com" && dom.proper_subdomains,
Value::Comment(_) | Value::Blank => false,
@@ -2158,7 +2146,7 @@ mod tests {
// Test comment.
assert!(
Wildcard::parse_value(" \t\t \t\t \t*.www.example.com#asdflkj asdf alskdfj ")
- .map_or(false, |val| match val {
+ .is_ok_and(|val| match val {
Value::Domain(ref dom) =>
dom.domain.as_bytes() == b"www.example.com" && dom.proper_subdomains,
Value::Comment(_) | Value::Blank => false,
@@ -2166,7 +2154,7 @@ mod tests {
);
assert!(
Wildcard::parse_value(" \t\t \t\t \twww.example.com #asdflkj asdf alskdfj ")
- .map_or(false, |val| match val {
+ .is_ok_and(|val| match val {
Value::Domain(ref dom) =>
dom.domain.as_bytes() == b"www.example.com" && !dom.proper_subdomains,
Value::Comment(_) | Value::Blank => false,
@@ -2174,7 +2162,7 @@ mod tests {
);
// Test whitespace removal.
assert!(
- Wildcard::parse_value(" \t\t *.www.example.com \t\t \t ").map_or(false, |val| {
+ Wildcard::parse_value(" \t\t *.www.example.com \t\t \t ").is_ok_and(|val| {
match val {
Value::Domain(ref dom) => {
dom.domain.as_bytes() == b"www.example.com" && dom.proper_subdomains
@@ -2184,7 +2172,7 @@ mod tests {
})
);
assert!(
- Wildcard::parse_value("\t\t \twww.example.com \t\t \t\t ").map_or(false, |val| {
+ Wildcard::parse_value("\t\t \twww.example.com \t\t \t\t ").is_ok_and(|val| {
match val {
Value::Domain(ref dom) => {
dom.domain.as_bytes() == b"www.example.com" && !dom.proper_subdomains
@@ -2193,20 +2181,20 @@ mod tests {
}
})
);
- assert!(Wildcard::parse_value("www .example.com").map_or_else(
- |e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')),
- |_| false
- ));
+ assert!(
+ Wildcard::parse_value("www .example.com")
+ .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
+ );
// Test 127 labels after wildcard error.
- assert!(Wildcard::parse_value("*.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").map_or_else(|e| e == FirefoxDomainErr::InvalidWildcardDomain, |_| false));
+ assert!(Wildcard::parse_value("*.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_err_and(|e| e == FirefoxDomainErr::InvalidWildcardDomain));
// Test 126 labels after wildcard is ok.
- assert!(Wildcard::parse_value("*.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").map_or(false, |val| match val {
+ assert!(Wildcard::parse_value("*.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_ok_and(|val| match val {
Value::Domain(ref dom) => dom.domain.iter().count() == 126 && dom.proper_subdomains,
Value::Comment(_) | Value::Blank => false,
}));
// Test comment.
assert!(
- Wildcard::parse_value(" \t\t \t\t \t # hi").map_or(false, |val| match val {
+ Wildcard::parse_value(" \t\t \t\t \t # hi").is_ok_and(|val| match val {
Value::Comment(com) => com == " hi",
Value::Domain(_) | Value::Blank => false,
})
@@ -2214,42 +2202,40 @@ mod tests {
// Test blank.
assert!(
Wildcard::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank))
+ .is_ok_and(|val| matches!(val, Value::Blank))
);
}
#[test]
- fn test_rpz_parse_value() {
+ fn rpz_parse_value() {
assert!(
- RpzDomain::parse_value("*.www.example.com").map_or(false, |val| {
+ RpzDomain::parse_value("*.www.example.com").is_ok_and(|val| {
let dom = val.unwrap_domain();
dom.is_proper_subdomains() && dom.domain().as_bytes() == b"www.example.com"
})
);
assert!(
- RpzDomain::parse_value("||www.example.com").map_or(false, |val| {
+ RpzDomain::parse_value("||www.example.com").is_ok_and(|val| {
let dom = val.unwrap_domain();
dom.is_subdomains() && dom.domain().as_bytes() == b"www.example.com"
})
);
assert!(
- RpzDomain::parse_value("0.0.0.0 www.example.com").map_or(false, |val| {
- let dom = val.unwrap_domain();
- !(dom.is_subdomains() || dom.is_proper_subdomains())
- && dom.domain().as_bytes() == b"www.example.com"
- })
- );
- assert!(
- RpzDomain::parse_value("www.example.com").map_or(false, |val| {
+ RpzDomain::parse_value("0.0.0.0 www.example.com").is_ok_and(|val| {
let dom = val.unwrap_domain();
!(dom.is_subdomains() || dom.is_proper_subdomains())
&& dom.domain().as_bytes() == b"www.example.com"
})
);
+ assert!(RpzDomain::parse_value("www.example.com").is_ok_and(|val| {
+ let dom = val.unwrap_domain();
+ !(dom.is_subdomains() || dom.is_proper_subdomains())
+ && dom.domain().as_bytes() == b"www.example.com"
+ }));
// Test case-insensitivity.
assert!(
- RpzDomain::parse_value("*.Www.ExaMPle.COm").map_or(false, |val| {
+ RpzDomain::parse_value("*.Www.ExaMPle.COm").is_ok_and(|val| {
let dom = val.unwrap_domain();
- RpzDomain::parse_value("*.www.example.com").map_or(false, |val2| {
+ RpzDomain::parse_value("*.www.example.com").is_ok_and(|val2| {
let dom2 = val2.unwrap_domain();
dom.is_proper_subdomains()
&& dom2.is_proper_subdomains()
@@ -2260,13 +2246,13 @@ mod tests {
);
// Test comment.
assert!(
- RpzDomain::parse_value(" \t\t \t\t \t # hi").map_or(false, |val| match val {
+ RpzDomain::parse_value(" \t\t \t\t \t # hi").is_ok_and(|val| match val {
Value::Comment(com) => com == " hi",
Value::Domain(_) | Value::Blank => false,
})
);
assert!(
- RpzDomain::parse_value(" \t\t \t\t \t ! hi").map_or(false, |val| match val {
+ RpzDomain::parse_value(" \t\t \t\t \t ! hi").is_ok_and(|val| match val {
Value::Comment(com) => com == " hi",
Value::Domain(_) | Value::Blank => false,
})
@@ -2274,12 +2260,15 @@ mod tests {
// Test blank.
assert!(
RpzDomain::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank))
+ .is_ok_and(|val| matches!(val, Value::Blank))
);
}
+ #[expect(clippy::expect_used, clippy::unwrap_in_result, reason = "OK in tests")]
+ #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
+ #[expect(clippy::nonminimal_bool, reason = "want to test all ord ops")]
#[test]
- fn test_rpz_ord_and_eq() -> Result<(), &'static str> {
- "www.bar.com,*.www.bar.com,||www.bar.com,*.bar.com,||bar.com,Example.com,WwW.exaMple.com,*.www.example.com,||www.example.com,*.example.com,||example.com,FOo.coM,Www.foo.com,*.foo.com,*.coM,example.net,*.net".split(|b| b == ',').try_fold(RpzDomain::DomainOnly(DomainOnly::parse_value("bar.com").expect("bug in DomainOnly::parse_value").unwrap_domain()), |prev, slice| {
+ fn rpz_ord_and_eq() -> Result<(), &'static str> {
+ "www.bar.com,*.www.bar.com,||www.bar.com,*.bar.com,||bar.com,Example.com,WwW.exaMple.com,*.www.example.com,||www.example.com,*.example.com,||example.com,FOo.coM,Www.foo.com,*.foo.com,*.coM,example.net,*.net".split(',').try_fold(RpzDomain::DomainOnly(DomainOnly::parse_value("bar.com").expect("bug in DomainOnly::parse_value").unwrap_domain()), |prev, slice| {
let cur = if slice.as_bytes()[0] == b'|' {
RpzDomain::Adblock(Adblock::parse_value(slice).expect("Bug in Adblock::parse_value").unwrap_domain())
} else {
@@ -2292,9 +2281,11 @@ mod tests {
}
}).map(|_| ())
}
+ #[expect(clippy::expect_used, reason = "OK in tests")]
+ #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
#[test]
- fn test_superset_set() {
- let mut iter = "*.NeT,*.net,www.bar.com,*.net,*.www.bar.com,||www.bar.com,*.bar.com,||bar.com,example.com,www.example.com,*.www.example.com,||www.example.com,*.example.com,||example.com,foo.com,www.foo.com,*.foo.com,*.com,example.net,*.abc.abc,||aawww.abc,abc.abc".split(|b| b == ',').fold(SupersetSet::new(), |mut doms, slice| {
+ fn superset_set() {
+ let mut iter = "*.NeT,*.net,www.bar.com,*.net,*.www.bar.com,||www.bar.com,*.bar.com,||bar.com,example.com,www.example.com,*.www.example.com,||www.example.com,*.example.com,||example.com,foo.com,www.foo.com,*.foo.com,*.com,example.net,*.abc.abc,||aawww.abc,abc.abc".split(',').fold(SupersetSet::new(), |mut doms, slice| {
_ = doms.insert(if slice.as_bytes()[0] == b'|' {
RpzDomain::Adblock(Adblock::parse_value(slice).expect("Bug in Adblock::parse_value").unwrap_domain())
} else {
@@ -2302,45 +2293,88 @@ mod tests {
});
doms
}).into_iter();
- assert!(iter.next().map_or(false, |d| {
- d.domain().as_bytes() == b"aawww.abc" && d.is_subdomains()
- }));
- assert!(iter.next().map_or(false, |d| {
- d.domain().as_bytes() == b"abc.abc" && d.is_domain()
- }));
- assert!(iter.next().map_or(false, |d| {
- d.domain().as_bytes() == b"abc.abc" && d.is_proper_subdomains()
- }));
- assert!(iter.next().map_or(false, |d| {
- d.domain().as_bytes() == b"com" && d.is_proper_subdomains()
- }));
- assert!(iter.next().map_or(false, |d| {
- d.domain().as_bytes() == b"NeT" && d.is_proper_subdomains()
- }));
+ assert!(
+ iter.next()
+ .is_some_and(|d| { d.domain().as_bytes() == b"aawww.abc" && d.is_subdomains() })
+ );
+ assert!(
+ iter.next()
+ .is_some_and(|d| { d.domain().as_bytes() == b"abc.abc" && d.is_domain() })
+ );
+ assert!(
+ iter.next().is_some_and(|d| {
+ d.domain().as_bytes() == b"abc.abc" && d.is_proper_subdomains()
+ })
+ );
+ assert!(
+ iter.next()
+ .is_some_and(|d| { d.domain().as_bytes() == b"com" && d.is_proper_subdomains() })
+ );
+ assert!(
+ iter.next()
+ .is_some_and(|d| { d.domain().as_bytes() == b"NeT" && d.is_proper_subdomains() })
+ );
assert!(iter.next().is_none());
}
#[test]
- fn test_card() {
+ fn card() {
// Geometric series.
// We can have two labels each with one character,
// one label with one to three characters, or 0 labels.
// This is 1 + 52 + 52^2 + 52^3 + 52^2 = (1-52^4)/(1-52) + 52^2 = (52^4 - 1)/51 + 52^2 = 146069.
- assert!(Adblock::parse_value("||a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").map_or(false, |val| { let dom = val.unwrap_domain(); dom.domain.len().get() == 249 && dom.domain.iter().count() == 125 && dom.domain_count() == BigUint::new(vec![146069]) }));
+ assert!(Adblock::parse_value("||a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_ok_and(|val| { let dom = val.unwrap_domain(); dom.domain.len().get() == 249 && dom.domain.iter().count() == 125 && dom.domain_count() == BigUint::new(vec![146_069]) }));
// A subdomain of length 252 or 253 gets converted to a domain.
- assert!(Adblock::parse_value("||a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").map_or(false, |val| { let dom = val.unwrap_domain(); dom.domain.iter().count() == 127 && !dom.subdomains && dom.domain_count() == BigUint::new(vec![1]) }));
+ assert!(Adblock::parse_value("||a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_ok_and(|val| { let dom = val.unwrap_domain(); dom.domain.iter().count() == 127 && !dom.subdomains && dom.domain_count() == BigUint::new(vec![1]) }));
// Pre-calculated manually.
// This is the number of domains possible between 2 and 252 characters.
- assert!(Wildcard::parse_value("*.a").map_or(false, |val| {
+ assert!(Wildcard::parse_value("*.a").is_ok_and(|val| {
val.unwrap_domain().domain_count()
== BigUint::new(vec![
- 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239,
- 3589178618, 348125705, 1709233643, 958334503, 3780539710, 2181893897,
- 2457156833, 3204765645, 2728103430, 1817547150, 3102358416, 444185044,
- 3659003776, 10341713, 306326206, 1336386425, 3942332649, 2036577878,
- 2460939277, 3976861337, 2101094571, 2241770079, 2667853164, 3687350273,
- 109356153, 3455569358, 2333076459, 2433207896, 1553903141, 2621943843,
- 4223295645, 1753858368, 130924388, 965594304, 3942586845, 1573844087,
- 4237886128, 481383133, 56931017,
+ 375_288_404,
+ 2_460_223_985,
+ 1_334_358_771,
+ 2_543_621_408,
+ 2_519_466_280,
+ 1_133_682_239,
+ 3_589_178_618,
+ 348_125_705,
+ 1_709_233_643,
+ 958_334_503,
+ 3_780_539_710,
+ 2_181_893_897,
+ 2_457_156_833,
+ 3_204_765_645,
+ 2_728_103_430,
+ 1_817_547_150,
+ 3_102_358_416,
+ 444_185_044,
+ 3_659_003_776,
+ 10_341_713,
+ 306_326_206,
+ 1_336_386_425,
+ 3_942_332_649,
+ 2_036_577_878,
+ 2_460_939_277,
+ 3_976_861_337,
+ 2_101_094_571,
+ 2_241_770_079,
+ 2_667_853_164,
+ 3_687_350_273,
+ 109_356_153,
+ 3_455_569_358,
+ 2_333_076_459,
+ 2_433_207_896,
+ 1_553_903_141,
+ 2_621_943_843,
+ 4_223_295_645,
+ 1_753_858_368,
+ 130_924_388,
+ 965_594_304,
+ 3_942_586_845,
+ 1_573_844_087,
+ 4_237_886_128,
+ 481_383_133,
+ 56_931_017,
])
}));
}
diff --git a/src/file.rs b/src/file.rs
@@ -394,7 +394,7 @@ impl Display for Name {
}
}
}
-/// An in-memory file sourced from [`Self::name`];
+/// An in-memory file sourced from [`Self::name`].
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct File {
/// The name/origin of the file.
@@ -528,9 +528,9 @@ impl Files {
Ok(())
}
timeout_per_file = if timeout_per_file == Duration::ZERO {
- Duration::from_secs(3600)
+ Duration::from_hours(1)
} else {
- timeout_per_file.clamp(Duration::from_secs(1), Duration::from_secs(3600))
+ timeout_per_file.clamp(Duration::from_secs(1), Duration::from_hours(1))
};
let mut files = Self {
adblock: Vec::with_capacity(ext.adblock.len()),
diff --git a/src/lib.rs b/src/lib.rs
@@ -16,6 +16,10 @@
//! ad-blocking files into a [response policy zone (RPZ)](https://en.wikipedia.org/wiki/Response_policy_zone)
//! file easier.
#![expect(
+ clippy::doc_paragraphs_missing_punctuation,
+ reason = "false positive for crate documentation having image links and regex expressions for domain types"
+)]
+#![expect(
clippy::multiple_crate_versions,
reason = "dependencies haven't updated to newest crates"
)]
@@ -23,7 +27,6 @@
unstable_features,
reason = "the only reason we require nightly is for the below features"
)]
-#![feature(btree_cursors)]
#![feature(io_error_more)]
/// Module for hostname-like domains including parsing [`str`]s
/// from a variety of formats.
@@ -34,6 +37,8 @@ mod dom_count_auto_gen;
/// Module for fetching and parsing local and HTTP(S) files into
/// [`dom::RpzDomain`]s.
pub mod file;
-#[cfg(feature = "priv_sep")]
+#[cfg(target_os = "openbsd")]
use priv_sep as _;
+#[cfg(target_os = "openbsd")]
+use rustls as _;
use toml as _;
diff --git a/src/main.rs b/src/main.rs
@@ -10,7 +10,6 @@
reason = "dependencies haven't updated to newest crates"
)]
#![feature(never_type)]
-#![cfg_attr(doc, feature(doc_auto_cfg))]
/// Contains a wrapper of block and unblock `RpzDomain`s
/// which can be used to write to a `File` or `stdout`.
mod app;
@@ -26,8 +25,6 @@ use crate::{
args::{ArgsErr, ConfigPath, Opts},
config::Config,
};
-#[cfg(all(feature = "priv_sep", not(target_os = "openbsd")))]
-use ::priv_sep as _;
use ascii_domain as _;
use core::{
error::Error,
@@ -35,13 +32,20 @@ use core::{
time::Duration,
};
use num_bigint as _;
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-use priv_sep::NulOrIoErr;
-use reqwest::Client;
+use reqwest::{Client, Error as HttpErr};
use rpz::{
dom::FirefoxDomainErr,
file::{AbsFilePath, ExtFileErr, ExternalFiles, Files, HttpUrl, LocalFiles, Summary},
};
+#[cfg(target_os = "openbsd")]
+use rustls::{
+ ClientConfig, Error as TlsErr, RootCertStore,
+ pki_types::{
+ CertificateDer,
+ pem::{Error as PkiErr, PemObject as _},
+ },
+ version::TLS13,
+};
use std::{
collections::HashSet,
fs,
@@ -78,25 +82,32 @@ enum E {
Args(ArgsErr),
/// Variant for errors due to issues with the TOML config file.
Config(de::Error),
- /// Variant for errors due to calls to `unveil`.
- #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
- Unveil(NulOrIoErr),
/// Variant for IO errors.
Io(io::Error),
/// Variant for errors due to downloading external HTTP(S) block files.
ExtFile(ExtFileErr),
/// Variant when there are no block entries to be written.
NoBlockEntries,
+ /// Variant when there is an issue creating the HTTP(S) client.
+ HttpClient(HttpErr),
+ /// Variant when there is a `rustls` issue.
+ #[cfg(target_os = "openbsd")]
+ Tls(TlsErr),
+ /// Variant when there is an issue parsing the root certificate store.
+ #[cfg(target_os = "openbsd")]
+ Pki(PkiErr),
}
impl fmt::Debug for E {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::Args(ref e) => write!(f, "{e}.\nFor more information, try '--help'."),
- Self::Config(_) | Self::Io(_) | Self::ExtFile(_) | Self::NoBlockEntries => {
- <Self as Display>::fmt(self, f)
- }
- #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
- Self::Unveil(_) => <Self as Display>::fmt(self, f),
+ Self::Config(_)
+ | Self::Io(_)
+ | Self::ExtFile(_)
+ | Self::NoBlockEntries
+ | Self::HttpClient(_) => <Self as Display>::fmt(self, f),
+ #[cfg(target_os = "openbsd")]
+ Self::Tls(_) | Self::Pki(_) => <Self as Display>::fmt(self, f),
}
}
}
@@ -105,11 +116,14 @@ impl Display for E {
match *self {
Self::Args(ref e) => e.fmt(f),
Self::Config(ref e) => e.fmt(f),
- #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
- Self::Unveil(ref err) => write!(f, "unveil(2) failed with {err}"),
Self::Io(ref e) => e.fmt(f),
Self::ExtFile(ref e) => e.fmt(f),
Self::NoBlockEntries => f.write_str("there are no domains to block"),
+ Self::HttpClient(ref e) => e.fmt(f),
+ #[cfg(target_os = "openbsd")]
+ Self::Tls(ref e) => e.fmt(f),
+ #[cfg(target_os = "openbsd")]
+ Self::Pki(ref e) => e.fmt(f),
}
}
}
@@ -134,13 +148,7 @@ impl From<ExtFileErr> for E {
Self::ExtFile(value)
}
}
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-impl From<NulOrIoErr> for E {
- fn from(value: NulOrIoErr) -> Self {
- Self::Unveil(value)
- }
-}
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
impl From<!> for E {
fn from(value: !) -> Self {
value
@@ -187,6 +195,41 @@ fn get_local_files(local_dir: Option<AbsFilePath<true>>) -> Result<Option<LocalF
},
)
}
+/// Retrieves the HTTP(S) client.
+#[cfg(target_os = "openbsd")]
+fn https_client() -> Result<Client, E> {
+ fs::read("/etc/ssl/cert.pem")
+ .map_err(E::Io)
+ .and_then(|root_cert_file| {
+ let mut root_store = RootCertStore::empty();
+ CertificateDer::pem_slice_iter(&root_cert_file)
+ .try_fold((), |(), cert| {
+ cert.map_err(E::Pki)
+ .and_then(|c| root_store.add(c).map_err(E::Tls))
+ })
+ .and_then(|()| {
+ let mut conf =
+ ClientConfig::builder_with_protocol_versions([&TLS13].as_slice())
+ .with_root_certificates(root_store)
+ .with_no_client_auth();
+ conf.alpn_protocols = vec![vec![b'h', b'2']];
+ Client::builder()
+ .user_agent(USER_AGENT)
+ .tls_backend_preconfigured(conf)
+ .build()
+ .map_err(E::HttpClient)
+ })
+ })
+}
+/// Retrieves the HTTP(S) client.
+#[cfg(not(target_os = "openbsd"))]
+fn https_client() -> Result<Client, E> {
+ Client::builder()
+ .user_agent(USER_AGENT)
+ .tls_backend_rustls()
+ .build()
+ .map_err(E::HttpClient)
+}
/// Downloads block files from HTTP(S) servers.
#[expect(clippy::unreachable, reason = "there is a bug and we want to crash")]
fn get_external_files(
@@ -203,33 +246,25 @@ fn get_external_files(
|e| Err(E::Io(e)),
|runtime| {
runtime.block_on(async {
- Files::from_external(
- {
- let mut files = ExternalFiles::new();
- CLIENT
- .set(
- Client::builder()
- .user_agent(USER_AGENT)
- .use_rustls_tls()
- .build()
- .map_err(ExtFileErr::Http)?,
- )
- .unwrap_or_else(|_e| {
- unreachable!("there is a bug in OnceLock::set")
- });
+ match https_client() {
+ Ok(c) => {
+ CLIENT.set(c).unwrap_or_else(|_e| {
+ unreachable!("there is a bug in OnceLock::set")
+ });
let client = CLIENT
.get()
.unwrap_or_else(|| unreachable!("there is a bug in OnceLock::get"));
+ let mut files = ExternalFiles::new();
files.add_adblock(client, adblock);
files.add_domain(client, domain);
files.add_hosts(client, hosts);
files.add_wildcard(client, wildcard);
- files
- },
- timeout,
- )
- .await
- .map_err(E::ExtFile)
+ Files::from_external(files, timeout)
+ .await
+ .map_err(E::ExtFile)
+ }
+ Err(e) => Err(e),
+ }
})
},
)
@@ -353,7 +388,7 @@ fn main() -> Result<(), E> {
.map_err(E::from)
.and_then(|()| {
get_external_files(
- config.timeout.unwrap_or(Duration::from_secs(3600)),
+ config.timeout.unwrap_or(Duration::from_hours(1)),
config.adblock,
config.domain,
config.hosts,
@@ -392,8 +427,7 @@ fn main() -> Result<(), E> {
}
#[cfg(test)]
pub(crate) mod test_prog {
- use std::fs;
- use std::process::Command;
+ use std::{fs, process::Command};
/// The path to the program dir with no block subdirectories.
pub(crate) const PROG_DIR_NO_SUB: &str = "/home/zack/projects/rpz/target/";
/// The path to the program dir with block subdirectories.
@@ -403,19 +437,20 @@ pub(crate) mod test_prog {
/// Message to append to the appropriate variable above if an issue occurs.
pub(crate) const ERR_MSG: &str = " does not exist, so program testing cannot occur";
/// Verify the correct directories and files exist.
+ #[expect(clippy::panic, reason = "OK in tests")]
pub(crate) fn verify_files() {
if !fs::metadata(PROG)
- .expect(format!("{PROG}{ERR_MSG}").as_str())
+ .unwrap_or_else(|_| panic!("{PROG}{ERR_MSG}"))
.is_file()
{
panic!("{PROG} is not an executable file")
} else if !fs::metadata(PROG_DIR)
- .expect(format!("{PROG_DIR}{ERR_MSG}").as_str())
+ .unwrap_or_else(|_| panic!("{PROG_DIR}{ERR_MSG}"))
.is_dir()
{
panic!("{PROG_DIR} is not a directory")
} else if !fs::metadata(PROG_DIR_NO_SUB)
- .expect(format!("{PROG_DIR_NO_SUB}{ERR_MSG}").as_str())
+ .unwrap_or_else(|_| panic!("{PROG_DIR_NO_SUB}{ERR_MSG}"))
.is_dir()
{
panic!("{PROG_DIR_NO_SUB} is not a directory")
@@ -424,34 +459,31 @@ pub(crate) mod test_prog {
let len = files.len();
files.push_str("block/adblock/foo");
if fs::metadata(files.as_str())
- .expect(format!("{files}{ERR_MSG}. it must only contain '||bar.com'").as_str())
+ .unwrap_or_else(|_| panic!("{files}{ERR_MSG}. it must only contain '||bar.com'"))
.is_file()
{
files.truncate(len);
files.push_str("block/domain/foo");
if fs::metadata(files.as_str())
- .expect(
- format!("{files}{ERR_MSG}. it must only contain 'www.example.com'")
- .as_str(),
- )
+ .unwrap_or_else(|_| {
+ panic!("{files}{ERR_MSG}. it must only contain 'www.example.com'")
+ })
.is_file()
{
files.truncate(len);
files.push_str("unblock/hosts/foo");
if fs::metadata(files.as_str())
- .expect(
- format!("{files}{ERR_MSG}. it must only contain '0.0.0.0 www.bar.com'")
- .as_str(),
- )
+ .unwrap_or_else(|_| {
+ panic!("{files}{ERR_MSG}. it must only contain '0.0.0.0 www.bar.com'")
+ })
.is_file()
{
files.truncate(len);
files.push_str("unblock/wildcard/foo");
if !fs::metadata(files.as_str())
- .expect(
- format!("{files}{ERR_MSG}. it must only contain '*.foo.com'")
- .as_str(),
- )
+ .unwrap_or_else(|_| {
+ panic!("{files}{ERR_MSG}. it must only contain '*.foo.com'")
+ })
.is_file()
{
panic!("{files} is not a file");
@@ -474,12 +506,10 @@ pub(crate) mod test_prog {
#[cfg(test)]
mod tests {
use crate::{E, test_prog};
- use std::io::Write;
- use std::process::Stdio;
- use std::thread;
+ use std::{io::Write as _, process::Stdio, thread};
#[test]
- #[ignore]
- fn test_app() {
+ #[ignore = "performs I/O"]
+ fn app() {
test_prog::verify_files();
assert!(
test_prog::get_command()
@@ -488,19 +518,19 @@ mod tests {
.stdin(Stdio::piped())
.stdout(Stdio::null())
.spawn()
- .map_or(false, |mut cmd| {
- cmd.stdin.take().map_or(false, |mut stdin| {
+ .is_ok_and(|mut cmd| {
+ cmd.stdin.take().is_some_and(|mut stdin| {
thread::spawn(move || {
stdin
.write_all(
format!("local_dir=\"{}\"", test_prog::PROG_DIR_NO_SUB)
.as_bytes(),
)
- .map_or(false, |_| true)
+ .is_ok()
})
.join()
- .map_or(false, |v| v)
- }) && cmd.wait_with_output().map_or(false, |output| {
+ .is_ok_and(|v| v)
+ }) && cmd.wait_with_output().is_ok_and(|output| {
!output.status.success()
&& output.stderr
== format!("Error: {:?}\n", E::NoBlockEntries).into_bytes()
@@ -513,16 +543,16 @@ mod tests {
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
- .map_or(false, |mut cmd| {
- cmd.stdin.take().map_or(false, |mut stdin| {
+ .is_ok_and(|mut cmd| {
+ cmd.stdin.take().is_some_and(|mut stdin| {
thread::spawn(move || {
stdin
.write_all(format!("local_dir=\"{}\"", test_prog::PROG_DIR).as_bytes())
- .map_or(false, |_| true)
+ .is_ok()
})
.join()
- .map_or(false, |v| v)
- }) && cmd.wait_with_output().map_or(false, |output| {
+ .is_ok_and(|v| v)
+ }) && cmd.wait_with_output().is_ok_and(|output| {
output.status.success()
&& output.stdout
== b"www.bar.com CNAME rpz-passthru.
diff --git a/src/priv_sep.rs b/src/priv_sep.rs
@@ -1,25 +1,20 @@
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) use priv_sep::NulOrIoErr;
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-use priv_sep::{Permissions, Promise, Promises};
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[cfg(target_os = "openbsd")]
+extern crate alloc;
+#[cfg(target_os = "openbsd")]
+use alloc::ffi::CString;
+#[cfg(target_os = "openbsd")]
+use core::ffi::CStr;
+#[cfg(target_os = "openbsd")]
+use priv_sep::{Errno, Permissions, Promise, Promises};
+#[cfg(target_os = "openbsd")]
use std::env;
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
-use std::fs;
-use std::{
- io::{Error, ErrorKind},
- path::Path,
-};
-/// Used instead of `()` for the parameter
-/// in the `pledge` functions. This allows
-/// one to avoid having to disable certain lints.
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
-#[derive(Clone, Copy)]
-pub(crate) struct Zst;
+#[cfg(not(target_os = "openbsd"))]
+use std::{fs, io::ErrorKind};
+use std::{io::Error, path::Path};
/// Calls `pledge` with only the sys calls necessary for a minimal application
/// to run. Specifically, the `Promise`s `Cpath`, `Dns`, `Inet`, `Rpath`, `Stdio`, `Unveil`, and `Wpath`
/// are passed.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[cfg(target_os = "openbsd")]
pub(crate) fn pledge_init() -> Result<Promises, Error> {
let promises = Promises::new([
Promise::Cpath,
@@ -32,12 +27,16 @@ pub(crate) fn pledge_init() -> Result<Promises, Error> {
]);
match promises.pledge() {
Ok(()) => Ok(promises),
- Err(e) => Err(e),
+ Err(e) => Err(e.into()),
}
}
+/// ZST that doesn't trigger lint errors in contrast to `()`.
+#[derive(Clone, Copy)]
+#[cfg(not(target_os = "openbsd"))]
+pub(crate) struct Zst;
/// 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")))]
+#[cfg(not(target_os = "openbsd"))]
pub(crate) const fn pledge_init() -> Result<Zst, !> {
Ok(Zst)
}
@@ -45,107 +44,123 @@ pub(crate) const fn pledge_init() -> Result<Zst, !> {
///
/// This should only be called when `stdout` is written to
/// instead of an RPZ file.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[cfg(target_os = "openbsd")]
pub(crate) fn pledge_away_create_write(promises: &mut Promises) -> Result<(), Error> {
- promises.remove_promises_then_pledge([Promise::Cpath, Promise::Wpath])
+ promises
+ .remove_promises_then_pledge([Promise::Cpath, Promise::Wpath])
+ .map_err(Error::from)
}
/// 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")))]
+#[cfg(not(target_os = "openbsd"))]
pub(crate) const fn pledge_away_create_write(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Removes `Promise::Unveil`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[cfg(target_os = "openbsd")]
pub(crate) fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Error> {
- promises.remove_then_pledge(Promise::Unveil)
+ promises
+ .remove_then_pledge(Promise::Unveil)
+ .map_err(Error::from)
}
/// 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")))]
+#[cfg(not(target_os = "openbsd"))]
pub(crate) const fn pledge_away_unveil(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Removes `Promise::Dns` and `Promise::Inet`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[cfg(target_os = "openbsd")]
pub(crate) fn pledge_away_net(promises: &mut Promises) -> Result<(), Error> {
- promises.remove_promises_then_pledge([Promise::Dns, Promise::Inet])
+ promises
+ .remove_promises_then_pledge([Promise::Dns, Promise::Inet])
+ .map_err(Error::from)
}
/// 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")))]
+#[cfg(not(target_os = "openbsd"))]
pub(crate) const fn pledge_away_net(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Removes all `Promise`s except `Stdio`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[cfg(target_os = "openbsd")]
pub(crate) fn pledge_away_all_but_stdio(promises: &mut Promises) -> Result<(), Error> {
- promises.retain_then_pledge([Promise::Stdio])
+ promises
+ .retain_then_pledge([Promise::Stdio])
+ .map_err(Error::from)
}
/// 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")))]
+#[cfg(not(target_os = "openbsd"))]
pub(crate) const fn pledge_away_all_but_stdio(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Calls `unveil`_on `path` with no `Permissions`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) fn unveil_none<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
- Permissions::NONE.unveil(path)
+#[cfg(target_os = "openbsd")]
+pub(crate) fn unveil_none<P: AsRef<Path>>(path: P) -> Result<(), Error> {
+ CString::new(path.as_ref().as_os_str().as_encoded_bytes())
+ .map_err(Error::other)
+ .and_then(|p| Permissions::NONE.unveil(p.as_c_str()).map_err(Error::from))
}
/// No-op that always returns `Ok`.
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
pub(crate) fn unveil_none<P: AsRef<Path>>(_: P) -> Result<(), !> {
Ok(())
}
-/// Calls `unveil_none` on `/`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) fn veil_all() -> Result<(), NulOrIoErr> {
- unveil_none("/")
+/// Calls `unveil` on `/`.
+#[cfg(target_os = "openbsd")]
+pub(crate) fn veil_all() -> Result<(), Error> {
+ Permissions::NONE.unveil(c"/").map_err(Error::from)
}
/// No-op that always returns `Ok`.
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
pub(crate) const fn veil_all() -> Result<(), !> {
Ok(())
}
/// Calls `unveil`_on `path` with `Permissions::CREATE`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) fn unveil_create<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
- Permissions::CREATE.unveil(path)
+#[cfg(target_os = "openbsd")]
+pub(crate) fn unveil_create<P: AsRef<Path>>(path: P) -> Result<(), Error> {
+ CString::new(path.as_ref().as_os_str().as_encoded_bytes())
+ .map_err(Error::other)
+ .and_then(|p| {
+ Permissions::CREATE
+ .unveil(p.as_c_str())
+ .map_err(Error::from)
+ })
}
/// No-op that always returns `Ok`.
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
pub(crate) fn unveil_create<P: AsRef<Path>>(_: P) -> Result<(), !> {
Ok(())
}
/// Calls `unveil`_on `path` with `Permissions::READ` and returns
/// `true` iff `path` exists.
-#[expect(
- clippy::wildcard_enum_match_arm,
- reason = "ErrorKind is non_exhaustive"
-)]
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, NulOrIoErr> {
- Permissions::READ.unveil(path).map_or_else(
- |err| match err {
- NulOrIoErr::Io(ref err2) => match err2.kind() {
- ErrorKind::NotFound => Ok(false),
- _ => Err(err),
- },
- NulOrIoErr::Nul(_) => Err(err),
- },
- |()| Ok(true),
- )
-}
-/// Returns `true` iff `path` exists
+#[cfg(target_os = "openbsd")]
+pub(crate) fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, Error> {
+ CString::new(path.as_ref().as_os_str().as_encoded_bytes())
+ .map_err(Error::other)
+ .and_then(|p| {
+ Permissions::READ.unveil(p.as_c_str()).map_or_else(
+ |err| {
+ if matches!(err, Errno::ENOENT) {
+ Ok(false)
+ } else {
+ Err(err.into())
+ }
+ },
+ |()| Ok(true),
+ )
+ })
+}
+/// Returns `true` iff `path` exists.
#[expect(
clippy::wildcard_enum_match_arm,
reason = "too many branches to write out manually"
)]
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
pub(crate) fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, Error> {
fs::metadata(path).map_or_else(
|err| match err.kind() {
@@ -156,12 +171,14 @@ pub(crate) fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, Error> {
)
}
/// Calls `unveil`_on `path` with `Permissions::READ`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) fn unveil_read_file<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
- Permissions::READ.unveil(path)
+#[cfg(target_os = "openbsd")]
+pub(crate) fn unveil_read_file<P: AsRef<Path>>(path: P) -> Result<(), Error> {
+ CString::new(path.as_ref().as_os_str().as_encoded_bytes())
+ .map_err(Error::other)
+ .and_then(|p| Permissions::READ.unveil(p.as_c_str()).map_err(Error::from))
}
/// No-op that always returns `Ok`.
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
pub(crate) fn unveil_read_file<P: AsRef<Path>>(_: P) -> Result<(), !> {
Ok(())
@@ -171,30 +188,43 @@ pub(crate) fn unveil_read_file<P: AsRef<Path>>(_: P) -> Result<(), !> {
/// 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, reason = "safety comment justifies its correctness")]
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) fn unveil_https() -> Result<(), NulOrIoErr> {
+#[cfg(target_os = "openbsd")]
+pub(crate) fn unveil_https() -> Result<(), Error> {
/// The path to the root certificate store.
- const CERTS: &str = "/etc/ssl/cert.pem";
- unveil_read_file(CERTS).map(|()| {
- // SAFETY:
- // `unveil_https` is only called in `super::main`
- // in a single-threaded context; thus this is OK.
- unsafe { env::set_var("SSL_CERT_FILE", CERTS) }
- })
+ const CERTS: &CStr = c"/etc/ssl/cert.pem";
+ const CERTS_STR: &str = match CERTS.to_str() {
+ Ok(val) => val,
+ Err(_) => panic!("/etc/ssl/cert.pem is not a valid str"),
+ };
+ Permissions::READ
+ .unveil(CERTS)
+ .map(|()| {
+ // SAFETY:
+ // `unveil_https` is only called in `super::main`
+ // in a single-threaded context; thus this is OK.
+ unsafe { env::set_var("SSL_CERT_FILE", CERTS_STR) }
+ })
+ .map_err(Error::from)
}
/// No-op that always returns `Ok`.
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
pub(crate) const fn unveil_https() -> Result<(), !> {
Ok(())
}
/// Calls `unveil`_on `path` with create, read, and write `Permissions`.
-#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
-pub(crate) fn unveil_create_read_write<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
- (!Permissions::EXECUTE).unveil(path)
+#[cfg(target_os = "openbsd")]
+pub(crate) fn unveil_create_read_write<P: AsRef<Path>>(path: P) -> Result<(), Error> {
+ CString::new(path.as_ref().as_os_str().as_encoded_bytes())
+ .map_err(Error::other)
+ .and_then(|p| {
+ (!Permissions::EXECUTE)
+ .unveil(p.as_c_str())
+ .map_err(Error::from)
+ })
}
/// No-op that always returns `Ok`.
-#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[cfg(not(target_os = "openbsd"))]
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
pub(crate) fn unveil_create_read_write<P: AsRef<Path>>(_: P) -> Result<(), !> {
Ok(())