commit 25565a5e2001cd4169d0c1e372d92bd47088ffe5
Author: Zack Newman <zack@philomathiclife.com>
Date: Sun, 29 Oct 2023 17:47:41 -0600
init
Diffstat:
A | .gitignore | | | 2 | ++ |
A | Cargo.toml | | | 50 | ++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | LICENSE-APACHE | | | 177 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | LICENSE-MIT | | | 20 | ++++++++++++++++++++ |
A | README.md | | | 191 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | build.rs | | | 12 | ++++++++++++ |
A | rust-toolchain.toml | | | 2 | ++ |
A | src/app.rs | | | 256 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/args.rs | | | 430 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/config.rs | | | 415 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/dom.rs | | | 3265 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/dom_count_auto_gen.rs | | | 1532 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/file.rs | | | 952 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/lib.rs | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
A | src/main.rs | | | 546 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/priv_sep.rs | | | 220 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
16 files changed, 8115 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+target/**
diff --git a/Cargo.toml b/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+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"
+keywords = ["adblock", "rpz", "unbound"]
+license = "MIT OR Apache-2.0"
+name = "rpz"
+readme = "README.md"
+repository = "https://git.philomathiclife.com/repos/rpz/"
+version = "0.1.0"
+
+[lib]
+name = "rpz"
+path = "src/lib.rs"
+
+[[bin]]
+name = "rpz"
+path = "src/main.rs"
+
+[dependencies]
+num-bigint = { version = "0.4.4", default-features = false }
+priv_sep = { version = "0.8.0", default-features = false, features = ["openbsd"], optional = true }
+reqwest = { version = "0.11.22", default-features = false, features = ["brotli", "deflate", "gzip", "rustls-tls-native-roots", "trust-dns"] }
+serde = { version = "1.0.190", default-features = false }
+superset_map = { version = "0.2.1", default-features = false }
+tokio = { version = "1.33.0", default-features = false, features = ["rt", "time"] }
+toml = { version = "0.8.6", default-features = false, features = ["parse"] }
+url = { version = "2.4.1", default-features = false, features = ["serde"] }
+zfc = { version = "0.3.1", default-features = false }
+
+[build-dependencies]
+rustc_version = "0.4.0"
+
+[features]
+priv_sep = ["dep:priv_sep"]
+default = ["priv_sep"]
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[badges]
+maintenance = { status = "actively-developed" }
+
+[profile.release]
+lto = true
+panic = 'abort'
+strip = true
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/LICENSE-MIT b/LICENSE-MIT
@@ -0,0 +1,20 @@
+Copyright © 2023 Zack Newman
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+“Software”), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
@@ -0,0 +1,191 @@
+# 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
+URLs and local file paths into a response policy zone (RPZ) file. This RPZ file can be consumed
+by a DNS server that supports such files (e.g., [Unbound](https://nlnetlabs.nl/projects/unbound/about/)).
+
+## rpz in action
+
+In this example it is assumed [`unbound.conf(5)`](https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html) is properly configured
+and has `name` and `zonefile` in the `rpz` section set to `.` and `/var/unbound/db/rpz` respectively in addition to `control-enable` set to `true`
+in the `remote-control` section.
+
+```bash
+[zack@laptop ~]$ cat<<EOF>/usr/local/etc/rpz/config
+> timeout = 15
+> rpz = "/var/unbound/db/rpz"
+> local_dir = "/usr/local/etc/rpz/"
+> adblock = [
+ "https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/BaseFilter/sections/adservers.txt",
+ "https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/BaseFilter/sections/adservers_firstparty.txt",
+ "https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/MobileFilter/sections/adservers.txt",
+ "https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/SpywareFilter/sections/mobile.txt",
+ "https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/SpywareFilter/sections/tracking_servers.txt",
+ "https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/SpywareFilter/sections/tracking_servers_firstparty.txt",
+ "https://raw.githubusercontent.com/easylist/easylist/master/easylist/easylist_adservers.txt",
+ "https://raw.githubusercontent.com/easylist/easylist/master/easylist/easylist_thirdparty.txt",
+ "https://raw.githubusercontent.com/easylist/easylist/master/easyprivacy/easyprivacy_thirdparty.txt",
+ "https://raw.githubusercontent.com/easylist/easylist/master/easyprivacy/easyprivacy_trackingservers.txt",
+ "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-agh.txt"
+ ]
+domain = ["https://www.stopforumspam.com/downloads/toxic_domains_whole.txt"]
+hosts = ["https://raw.githubusercontent.com/AdAway/adaway.github.io/master/hosts.txt", "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"]
+wildcard = ["https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblock&showintro=0&mimetype=plaintext"]
+> EOF
+[zack@laptop ~]$ cat /usr/local/etc/rpz/unblock/domain/unbound
+dpm.demdex.net # ESPN app on PS5 needs this.
+[zack@laptop ~]$ rpz -f /usr/local/etc/rpz/config
+unblock count written: 1
+block count written: 271559
+total lines written: 271560
+domains parsed: 254147
+comments parsed: 6629
+blanks parsed: 4519
+parsing errors: 24624
+[zack@laptop ~]$ head -1 /var/unbound/db/rpz
+dpm.demdex.net CNAME rpz-passthru.
+[zack@laptop ~]$ tail -6 /var/unbound/db/rpz
+stats.zone-telechargement CNAME .
+*.stats.zone-telechargement CNAME .
+5wh.co.zw CNAME .
+www.5wh.co.zw CNAME .
+pandi.co.zw CNAME .
+www.pandi.co.zw CNAME .
+[zack@laptop ~]$ unbound-control -q auth_zone_reload . && unbound-control -q flush_zone . && unbound-control -q flush_negative
+```
+
+## Ad-(un)block file format and encoding
+
+All ad-(un)block files must be valid UTF-8; however for a given domain, each label must only contain 1–63 Unicode scalar values from the set:
+`!`, `$`, `&`, `'`, `(`, `)`, `+`, `,`, `-`, `0`–`9`, `;`, `=`, `_`, `` ` ``, `A`–`Z`, `a`–`z`, `{`, `}`, and `~`. Labels must be delimited
+by `.`. Domains in the file must be delimited by a line feed or carriage return and line feed. A domain must be less than 254 characters in length
+including the `.` label separator. Domains are treated as case-insensitive with uppercase letters treated as lowercase. Domains must not be an
+[Ipv4Addr](https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html).
+
+### Adblock-style
+
+Domain constructed from an [Adblock-style rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#adblock-style-syntax)
+with the requirement that the rule conforms to the following extended regex:
+
+`^<ws>*(\|\|)?<ws>*<domain><ws>*\^?<ws>*$`
+
+where `<domain>` conforms to a valid [`Domain`](https://docs.rs/rpz/latest/rpz/dom/struct.Domain.html) with the added requirement that it does not contain `$`, and
+`<ws>` is any sequence of [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace).
+
+Lines that begin with `||` cause all subdomains to be blocked (i.e., the domain itself and all proper subdomains); without
+`||`, only the specific domain is blocked.
+
+Due to the conservative nature in how these files are processed, one is encouraged to still use an application-level
+ad blocker (e.g., [uBlock Origin](https://ublockorigin.com/)). Adblock-style files often contain paths as well as
+additional information (e.g., “third-party”) that require application-level information to process correctly as such
+entries will be considered “parsing errors” by `rpz`.
+
+### Domain-style
+
+Domain constructed from a [domains-only rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#domains-only-syntax)
+with the requirement that the rule conforms to the following regex:
+
+`^<ws>*<domain><ws>*(#.*)?$`
+
+where `<domain>` conforms to a valid `Domain`, and `<ws>` is any sequence of ASCII whitespace.
+
+Domains only represent themselves (i.e., proper subdomains will not be blocked).
+
+### Hosts-style
+
+Domain constructed from a [`hosts(5)`-style rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#etc-hosts-syntax)
+with the requirement that the rule conforms to the following extended regex:
+
+`^<ws>*<ip><ws>+<domain><ws>*(#.*)?$`
+
+where `<domain>` conforms to a valid `Domain`, `<ws>` is any sequence of ASCII whitespace, and `<ip>` is one of the following:
+
+`::`, `::1`, `0.0.0.0`, or `127.0.0.1`.
+
+Domains only represent themselves (i.e., proper subdomains will not be blocked).
+
+### Wildcard-style
+
+Domain constructed from a [wildcard domain rule](https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblock&showintro=0&mimetype=plaintext)
+with the requirement that the rule conforms to the following extended regex:
+
+`^<ws>*(\*\.)?<domain><ws>*(#.*)?$`
+
+where `<domain>` conforms to a valid `Domain`, and `<ws>` is any sequence of ASCII whitespace.
+
+If `domain` begins with `*.`, then `domain` must have length less than 252 and all proper subdomains are blocked—this
+does _not_ include the domain itself; otherwise, only the `domain` is blocked.
+
+## Config file
+
+Either `-` or the absolute path to the TOML config file must be passed via the `-f`/`--file` CLI option. If `-` is passed, then `stdin` will be read. The
+format of this file must conform to the following:
+
+```bash
+timeout = <timeout_in_seconds>
+rpz = <absolute_file_path_to_the_RPZ_file_to_be_written>
+local_dir = <absolute_file_path_to_the_directory_containing_local_files>
+adblock = [<HTTP(S)_URLs>]
+domain = [<HTTP(S)_URLs>]
+hosts = [<HTTP(S)_URLs>]
+wildcard = [<HTTP(S)_URLs>]
+```
+
+If `rpz` does not exist, then the file will be written to `stdout`. If `local_dir` is specified, `block/` and `unblock/` subdirectories are searched; and for each of those subdirectories,
+`adblock/`, `domain/`, `hosts/`, and `wildcard/` subdirectories are searched for files which are parsed according to the directory they are in. It is not
+an error if any of the directories do not exist.
+
+In the event keys are specified corresponding to arrays, URLs must be unique across all arrays. The files these URLs
+point to are interpreted as block files (i.e., unblock files are only allowed on the local file system).
+
+The `timeout` corresponds to the maximum _seconds_ allowed for an HTTP(S) file to be downloaded.
+If it does not exist or has a value of 0, then a timeout of one hour will be used. If the value specified exceeds one hour,
+then it will be truncated to one hour.
+
+## RPZ file
+
+Unless `stdout` is the destination, a temporary RPZ file is written in the same location as the `rpz` value in the config file except with `tmp` appended to the name. Upon success, this file
+is renamed to the `rpz` value in the config file. The contents of this file contain the minimum number of lines possible with unblock entries taking precedence
+over block entries.
+
+In the event there are no block entries or the temp file already exists, the program will abort.
+
+## Options
+
+When `rpz` is passed `-V`/`--version`, the version of `rpz` will be printed to `stdout`. When passed `-h`/`--help`,
+information about the program and its options will be printed to `stdout`. When passed `-f`/`--file` along with
+`-` or the absolute path to the TOML config file, `rpz` will run normally printing summary information to `stdout`
+upon completion. One can additionally pass `-q`/`--quiet` along with `-f`/`--file` in order to suppress summary
+information from being printed to `stdout`. When `-v`/`--verbose` is passed along with `-f`/`--file`, in addition to
+the normal summary information being printed to `stdout`, itemized summary information for each input file
+including the kinds of errors and counts of errors will be printed to `stdout`.
+
+### Example
+
+If `www.example.com`, `*.example.com`, and `foo.com` are to be blocked while `foo.example.com` and `||foo.com` are to be unblocked, the RPZ file would look like the following:
+
+```bash
+foo.example.com CNAME rpz-passthru.
+*.example.com CNAME .
+```
+
+Upon success, the quantity of unblock, block, and total lines written is written to `stdout` in addition
+to the total number of domains, comments, blanks, and parsing errors.
+
+## Errors
+
+Parsing errors are ignored; all other errors are written to `stderr` before program abortion.
+
+### Status
+
+This package will be actively maintained until it is deemed “feature complete”.
+
+The crates are only tested on the `x86_64-unknown-linux-gnu` and `x86_64-unknown-openbsd` targets, but
+they should work on any [Tier 1 with Host Tools](https://doc.rust-lang.org/beta/rustc/platform-support.html)
+target.
+
+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`. Note that the `rust-ring` port must also be installed with
+the `[patch]` section of `Cargo.toml` or `~/.cargo/config.toml` configured appropriately.
diff --git a/build.rs b/build.rs
@@ -0,0 +1,12 @@
+use rustc_version::{version_meta, Channel};
+
+fn main() {
+ // Set cfg flags depending on release channel
+ let channel = match version_meta().unwrap().channel {
+ Channel::Stable => "CHANNEL_STABLE",
+ Channel::Beta => "CHANNEL_BETA",
+ Channel::Nightly => "CHANNEL_NIGHTLY",
+ Channel::Dev => "CHANNEL_DEV",
+ };
+ println!("cargo:rustc-cfg={}", channel)
+}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
diff --git a/src/app.rs b/src/app.rs
@@ -0,0 +1,256 @@
+#![allow(
+ clippy::into_iter_on_ref,
+ clippy::ref_patterns,
+ clippy::single_char_lifetime_names,
+ clippy::unseparated_literal_suffix
+)]
+use core::convert;
+use rpz::dom::{
+ Adblock, DomainErr, DomainOnly, Hosts, ParsedDomain, RpzAction, RpzDomain, Value, Wildcard,
+};
+use rpz::file::{AbsFilePath, File, Files, Kind, LocalFiles, Summary};
+use std::collections::HashMap;
+use std::fs;
+use std::io::{self, Error, Write};
+use superset_map::SupersetSet;
+/// Helper that returns the `Kind` of a file.
+pub trait Helper {
+ /// Returns the `Kind` of file.
+ fn kind() -> Kind;
+}
+impl Helper for Adblock<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::Adblock
+ }
+}
+impl Helper for DomainOnly<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::DomainOnly
+ }
+}
+impl Helper for Hosts<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::Hosts
+ }
+}
+impl Helper for Wildcard<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::Wildcard
+ }
+}
+/// Container of `Domain`s to block and unblock.
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct Domains<'unblock, 'block> {
+ /// `RpzDomain`s to not block.
+ unblock: SupersetSet<RpzDomain<'unblock>>,
+ /// `RpzDomain`s to block.
+ block: SupersetSet<RpzDomain<'block>>,
+}
+impl<'unblock, 'block> Domains<'unblock, 'block> {
+ /// Returns a reference to the `RpzDomain`s to block.
+ #[inline]
+ pub const fn block(&self) -> &SupersetSet<RpzDomain<'block>> {
+ &self.block
+ }
+ /// Returns an empty `Domains`.
+ #[inline]
+ pub const fn new() -> Self {
+ Self {
+ unblock: SupersetSet::new(),
+ block: SupersetSet::new(),
+ }
+ }
+ /// Returns `Domains` based on `LocalFiles` along
+ /// with a `Vec` of `Summary`.
+ #[inline]
+ pub fn new_with<'c: 'unblock + 'block>(
+ local: &'c LocalFiles,
+ ) -> (Self, Vec<Summary<'c, DomainErr>>) {
+ let mut val = Self {
+ unblock: SupersetSet::new(),
+ block: SupersetSet::new(),
+ };
+ let summaries = val.add_local_files(local);
+ (val, summaries)
+ }
+ /// Parses each line in `file` as an `RpzDomain` before
+ /// adding it to `Domains::block` iff `Domains::unblock` does not contain
+ /// a superset of it.
+ ///
+ /// All parsing errors are ignored.
+ #[inline]
+ fn add_block_file<
+ 'c: 'block,
+ T: Into<RpzDomain<'block>> + ParsedDomain<'block, Error = DomainErr> + Helper,
+ >(
+ &mut self,
+ file: &'c File,
+ summaries: &mut Vec<Summary<'c, DomainErr>>,
+ ) {
+ let mut summary = Summary {
+ file,
+ kind: T::kind(),
+ domain_count: 0,
+ comment_count: 0,
+ blank_count: 0,
+ errors: HashMap::new(),
+ };
+ file.data
+ .lines()
+ .fold((), |(), line| match T::parse_value(line) {
+ Ok(val) => match val {
+ Value::Domain(dom) => {
+ summary.domain_count = summary.domain_count.saturating_add(1);
+ let domain = dom.into();
+ if !self.unblock.contains_superset(&domain) {
+ self.block.insert(domain);
+ }
+ }
+ Value::Comment(_) => {
+ summary.comment_count = summary.comment_count.saturating_add(1);
+ }
+ Value::Blank => {
+ summary.blank_count = summary.blank_count.saturating_add(1);
+ }
+ },
+ Err(err) => {
+ let count = summary.errors.entry(err).or_insert(0);
+ *count = count.saturating_add(1);
+ }
+ });
+ summaries.push(summary);
+ }
+ /// Parses each line in the files in `files` as an `RpzDomain` before
+ /// adding it to `Domains::block` iff `Domains::unblock` does not contain
+ /// a superset of it.
+ ///
+ /// All parsing errors are ignored.
+ #[inline]
+ pub fn add_block_files<'c: 'block>(
+ &mut self,
+ files: &'c Files,
+ summaries: &mut Vec<Summary<'c, DomainErr>>,
+ ) {
+ /// Parses each line in the `String`s in `files` as an `RpzDomain` before
+ /// adding it to `Domains::block` iff `Domains::unblock` does not contain
+ /// a superset of it.
+ ///
+ /// All parsing errors are ignored.
+ #[inline]
+ fn add_files<
+ 'unblock,
+ 'block,
+ 'c: 'block,
+ T: Into<RpzDomain<'block>> + ParsedDomain<'block, Error = DomainErr> + Helper,
+ >(
+ doms: &mut Domains<'unblock, 'block>,
+ files: &'c [File],
+ summaries: &mut Vec<Summary<'c, DomainErr>>,
+ ) {
+ files
+ .into_iter()
+ .fold((), |(), file| doms.add_block_file::<T>(file, summaries));
+ }
+ add_files::<Adblock>(self, &files.adblock, summaries);
+ add_files::<DomainOnly>(self, &files.domain, summaries);
+ add_files::<Hosts>(self, &files.hosts, summaries);
+ add_files::<Wildcard>(self, &files.wildcard, summaries);
+ }
+ /// Parses each line in the files in `files` as an `RpzDomain`.
+ /// For unblock files, the domain is added to `Domains::unblock`;
+ /// for block files, the domain is added to `Domains::block` iff
+ /// `Domains::unblock` does not contain a superset of it.
+ ///
+ /// All parsing errors are ignored.
+ #[inline]
+ fn add_local_files<'c: 'unblock + 'block>(
+ &mut self,
+ files: &'c LocalFiles,
+ ) -> Vec<Summary<'c, DomainErr>> {
+ let mut summaries = files.unblock.add_to_superset(&mut self.unblock);
+ self.add_block_files(&files.block, &mut summaries);
+ summaries
+ }
+ /// Writes all necessary unblock `RpzDomain`s as `<domain> CNAME rpz-passthru.`
+ /// followed by all block `RpzDomain`s as `<domain> CNAME .`
+ ///
+ /// When subdomains exist, `*.<domain>` is used.
+ ///
+ /// When `path` is `None`, `stdout` is written to; otherwise,
+ /// `path.unwrap().1` is written to before being renamed
+ /// to `path.unwrap().0`.
+ ///
+ /// Returns the quantity of unblock lines and block lines written
+ /// respectively.
+ ///
+ /// Note that by "necessary unblock" we mean that there exists
+ /// a proper superset of it in the blocked `RpzDomain`s; as if not,
+ /// there is no need to write it since a lack of a blocked entry
+ /// is equivalent to an unblock entry.
+ ///
+ /// # Errors
+ ///
+ /// Returns `Error` iff `writeln` or `fs::rename` do.
+ #[inline]
+ pub fn write(
+ self,
+ path: Option<(AbsFilePath<false>, AbsFilePath<false>)>,
+ ) -> Result<(usize, usize), Error> {
+ /// When `exclude.is_none()`, calls `Rpz::write_rpz_line` with
+ /// `RpzAction::Nxdomain` for each `RpzDomain` in `doms`;
+ /// otherwise calls it with `RpzAction::Passthru`
+ /// so long as `exclude` contains a proper superset of the domain.
+ ///
+ /// Returns the total number of lines written.
+ #[inline]
+ fn write_domain<W: Write>(
+ doms: &SupersetSet<RpzDomain<'_>>,
+ exclude: Option<&SupersetSet<RpzDomain<'_>>>,
+ mut writer: W,
+ ) -> 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)) {
+ rpz.write_to_rpz(action, &mut writer)
+ .map(|()| count.saturating_add(if rpz.is_subdomains() { 2 } else { 1 }))
+ } else {
+ Ok(count)
+ }
+ })
+ }
+ path.map_or_else(
+ || {
+ let mut std = io::stdout().lock();
+ write_domain(&self.unblock, Some(&self.block), &mut std).and_then(|unblock_count| {
+ write_domain(&self.block, None, std)
+ .map(|block_count| (unblock_count, block_count))
+ })
+ },
+ |(rpz, rpz_tmp)| {
+ fs::File::options()
+ .read(false)
+ .write(true)
+ .create_new(true)
+ .open(rpz_tmp.as_path())
+ .and_then(|file| {
+ write_domain(&self.unblock, Some(&self.block), &file).and_then(
+ |unblock_count| {
+ write_domain(&self.block, None, file).and_then(|block_count| {
+ fs::rename(rpz_tmp.as_path(), rpz)
+ .map(|()| (unblock_count, block_count))
+ .map_err(|err| {
+ fs::remove_file(rpz_tmp)
+ .map_or_else(convert::identity, |()| err)
+ })
+ })
+ },
+ )
+ })
+ },
+ )
+ }
+}
diff --git a/src/args.rs b/src/args.rs
@@ -0,0 +1,430 @@
+#![allow(clippy::question_mark_used, clippy::ref_patterns)]
+use core::fmt::{self, Display, Formatter};
+use rpz::file::AbsFilePath;
+use std::env::{self, Args};
+use std::error::Error;
+/// Error returned when parsing arguments passed to the application.
+#[allow(clippy::exhaustive_enums, clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub enum ArgsErr {
+ /// Error when no arguments were passed to the application.
+ NoArgs,
+ /// Error when `-f`/`--file` is not passed or is passed
+ /// without a path to the file.
+ ConfigPathNotPassed,
+ /// Error when an invalid option is passed. The contained [`String`]
+ /// is the value of the invalid option.
+ InvalidOption(String),
+ /// Some options when passed must be the only option passed.
+ /// For such options, this is the error when other options are passed.
+ MoreThanOneOption,
+ /// Error when the passed path to the config file is not `-` nor an absolute file path to a file.
+ InvalidConfigPath,
+ /// Error when an option is passed more than once.
+ DuplicateOption(&'static str),
+ /// Error when the quiet and verbose options were passed.
+ QuietAndVerbose,
+}
+impl Display for ArgsErr {
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::NoArgs => write!(f, "no arguments were passed, but at least two are required containing the option '-f' and its value which must be an absolute path to the config file"),
+ Self::ConfigPathNotPassed => f.write_str("'-f' followed by '-' or the absolute file path to the config file was not passed"),
+ Self::InvalidOption(ref arg) => write!(f, "{arg} is an invalid option. Only '-f'/'--file' followed by the absolute path to the config file, '-q'/'--quiet', '-h'/'--help', '-v'/'--verbose' and '-V'/'--version' are allowed"),
+ Self::MoreThanOneOption => f.write_str("'-V'/'--version' or '-h'/'--help' was passed with other options; but when those options are passed, they must be the only one"),
+ Self::InvalidConfigPath => write!(f, "an absolute file path to the config file or '-' was not passed"),
+ Self::DuplicateOption(arg) => write!(f, "{arg} was passed more than once"),
+ Self::QuietAndVerbose => f.write_str("'-q'/'--quiet' and '-v'/'--verbose' were both passed, but at most only one of them is allowed to be passed"),
+ }
+ }
+}
+impl Error for ArgsErr {}
+/// The location of the configuration file.
+#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub enum ConfigPath {
+ /// The config file is to be read from `stdin`.
+ Stdin,
+ /// The config file resides on the local file system.
+ Path(AbsFilePath<false>),
+}
+/// The options passed to the application.
+#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub enum Opts {
+ /// Variant when no arguments were passed.
+ None,
+ /// Variant when the help argument was passed.
+ Help,
+ /// Variant when the version argument was passed.
+ Version,
+ /// Variant when the quiet argument was passed.
+ Quiet,
+ /// Variant when the verbose argument was passed.
+ Verbose,
+ /// Variant when the file argument with the path to the file was passed.
+ Config(ConfigPath),
+ /// Variant when the quiet argument and the file argument with the path to the file
+ /// were passed.
+ ConfigQuiet(ConfigPath),
+ /// Variant when the verbose argument and the file argument with the path to the file
+ /// were passed.
+ ConfigVerbose(ConfigPath),
+}
+impl Opts {
+ /// Returns `Opts` based on arguments passed to the application.
+ #[allow(clippy::too_many_lines)]
+ #[inline]
+ pub fn from_args() -> Result<Self, ArgsErr> {
+ /// Attempts to parse the next `Arg` into `-` or an absolute
+ /// path to a file.
+ #[allow(clippy::option_if_let_else)]
+ #[inline]
+ fn get_path(args: &mut Args) -> Result<ConfigPath, ArgsErr> {
+ args.next()
+ .map_or(Err(ArgsErr::ConfigPathNotPassed), |path| {
+ if path == "-" {
+ Ok(ConfigPath::Stdin)
+ } else {
+ AbsFilePath::<false>::from_string(path)
+ .map_or(Err(ArgsErr::InvalidConfigPath), |config| {
+ Ok(ConfigPath::Path(config))
+ })
+ }
+ })
+ }
+ let mut args = env::args();
+ if args.next().is_some() {
+ let mut opts = Self::None;
+ while let Some(arg) = args.next() {
+ match arg.as_str() {
+ "-h" | "--help" => match opts {
+ Self::None => {
+ opts = Self::Help;
+ }
+ Self::Help => return Err(ArgsErr::DuplicateOption("-h/--help")),
+ Self::Verbose
+ | Self::Quiet
+ | Self::Version
+ | Self::Config(_)
+ | Self::ConfigQuiet(_)
+ | Self::ConfigVerbose(_) => return Err(ArgsErr::MoreThanOneOption),
+ },
+ "-V" | "--version" => match opts {
+ Self::None => {
+ opts = Self::Version;
+ }
+ Self::Version => return Err(ArgsErr::DuplicateOption("-V/--version")),
+ Self::Verbose
+ | Self::Quiet
+ | Self::Help
+ | Self::Config(_)
+ | Self::ConfigQuiet(_)
+ | Self::ConfigVerbose(_) => return Err(ArgsErr::MoreThanOneOption),
+ },
+ "-f" | "--file" => match opts {
+ Self::None => {
+ opts = Self::Config(get_path(&mut args)?);
+ }
+ Self::Quiet => {
+ opts = Self::ConfigQuiet(get_path(&mut args)?);
+ }
+ Self::Verbose => {
+ opts = Self::ConfigVerbose(get_path(&mut args)?);
+ }
+ Self::Config(_) | Self::ConfigQuiet(_) | Self::ConfigVerbose(_) => {
+ return Err(ArgsErr::DuplicateOption("-f/--file"));
+ }
+ Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
+ },
+ "-q" | "--quiet" => match opts {
+ Self::None => {
+ opts = Self::Quiet;
+ }
+ Self::Config(path) => {
+ opts = Self::ConfigQuiet(path);
+ }
+ Self::Quiet | Self::ConfigQuiet(_) => {
+ return Err(ArgsErr::DuplicateOption("-q/--quiet"));
+ }
+ Self::Verbose | Self::ConfigVerbose(_) => {
+ return Err(ArgsErr::QuietAndVerbose)
+ }
+ Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
+ },
+ "-v" | "--verbose" => match opts {
+ Self::None => {
+ opts = Self::Verbose;
+ }
+ Self::Config(path) => {
+ opts = Self::ConfigVerbose(path);
+ }
+ Self::Quiet | Self::ConfigQuiet(_) => return Err(ArgsErr::QuietAndVerbose),
+ Self::Verbose | Self::ConfigVerbose(_) => {
+ return Err(ArgsErr::DuplicateOption("-v/--verbose"));
+ }
+ Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
+ },
+ "-fq" | "-qf" => match opts {
+ Self::None => {
+ opts = Self::ConfigQuiet(get_path(&mut args)?);
+ }
+ Self::Config(_) | Self::ConfigQuiet(_) => {
+ return Err(ArgsErr::DuplicateOption("-f/--file"))
+ }
+ Self::Quiet => return Err(ArgsErr::DuplicateOption("-q/--quiet")),
+ Self::Verbose | Self::ConfigVerbose(_) => {
+ return Err(ArgsErr::QuietAndVerbose)
+ }
+ Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
+ },
+ "-fv" | "-vf" => match opts {
+ Self::None => {
+ opts = Self::ConfigVerbose(get_path(&mut args)?);
+ }
+ Self::Config(_) | Self::ConfigVerbose(_) => {
+ return Err(ArgsErr::DuplicateOption("-f/--file"))
+ }
+ Self::Verbose => return Err(ArgsErr::DuplicateOption("-v/--verbose")),
+ Self::Quiet | Self::ConfigQuiet(_) => return Err(ArgsErr::QuietAndVerbose),
+ Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
+ },
+ _ => return Err(ArgsErr::InvalidOption(arg)),
+ }
+ }
+ Ok(opts)
+ } else {
+ Err(ArgsErr::NoArgs)
+ }
+ }
+}
+#[cfg(test)]
+mod tests {
+ use crate::{test_prog, ArgsErr, E};
+ use core::convert;
+ use std::io::Write;
+ use std::process::Stdio;
+ use std::thread;
+ #[test]
+ #[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))
+ .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()
+ .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")
+ })
+ }));
+ 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
@@ -0,0 +1,415 @@
+#![allow(
+ clippy::exhaustive_structs,
+ clippy::implicit_return,
+ clippy::into_iter_on_ref,
+ clippy::missing_trait_methods,
+ clippy::question_mark_used,
+ clippy::single_char_lifetime_names
+)]
+extern crate alloc;
+use alloc::borrow::Cow;
+use core::fmt::{self, Display, Formatter};
+use core::time::Duration;
+use rpz::file::{AbsFilePath, HttpUrl};
+use serde::de::{Deserialize, Deserializer, Error, MapAccess, SeqAccess, Unexpected, Visitor};
+use std::collections::HashSet;
+/// The TOML config file.
+#[derive(Debug)]
+pub struct Config {
+ /// The maximum amount of time allowed for an HTTP(S) file to be downloaded.
+ pub timeout: Option<Duration>,
+ /// The absolute file path for the [response policy zone (RPZ)](https://en.wikipedia.org/wiki/Response_policy_zone) file.
+ pub rpz: Option<AbsFilePath<false>>,
+ /// The absolute file path to the directory that contains local (un)block files.
+ pub local_dir: Option<AbsFilePath<true>>,
+ /// The unique absolute HTTP(S) URLs to [Adblock-style](https://adguard-dns.io/kb/general/dns-filtering-syntax/#adblock-style-syntax)
+ /// block lists.
+ pub adblock: HashSet<HttpUrl>,
+ /// The unique absolute HTTP(S) URLs to [domains-only](https://adguard-dns.io/kb/general/dns-filtering-syntax/#domains-only-syntax)
+ /// block lists.
+ pub domain: HashSet<HttpUrl>,
+ /// The unique absolute HTTP(S) URLs to [`hosts(5)`-style](https://adguard-dns.io/kb/general/dns-filtering-syntax/#etc-hosts-syntax)
+ /// block lists.
+ pub hosts: HashSet<HttpUrl>,
+ /// The unique absolute HTTP(S) URLs to [wildcard domain](https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblock&showintro=0&mimetype=plaintext)
+ /// block lists.
+ pub wildcard: HashSet<HttpUrl>,
+}
+impl Display for Config {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ /// Helper function that writes the `Url`s in a `HashSet<HttpUrl>`.
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn keys(set: &HashSet<HttpUrl>, f: &mut Formatter<'_>, name: &str) -> fmt::Result {
+ write!(f, "{name}: [").and_then(|()| {
+ set.into_iter()
+ .try_fold((), |(), url| write!(f, "{url}, "))
+ .and_then(|()| f.write_str("], "))
+ })
+ }
+ write!(
+ f,
+ "Config {{ timeout: {} rpz: {}, local_dir: {}, ",
+ self.timeout
+ .map_or_else(String::new, |dur| dur.as_secs().to_string()),
+ &self
+ .rpz
+ .as_ref()
+ .map_or_else(|| Cow::Owned(String::new()), |file| file.to_string_lossy()),
+ &self
+ .local_dir
+ .as_ref()
+ .map_or_else(|| Cow::Owned(String::new()), |dir| dir.to_string_lossy())
+ )
+ .and_then(|()| {
+ keys(&self.adblock, f, "adblock")
+ .and_then(|()| keys(&self.domain, f, "domain"))
+ .and_then(|()| keys(&self.hosts, f, "hosts"))
+ .and_then(|()| keys(&self.wildcard, f, "wildcard"))
+ .and_then(|()| f.write_str("}"))
+ })
+ }
+}
+impl<'de> Deserialize<'de> for Config {
+ #[allow(clippy::too_many_lines)]
+ #[inline]
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ /// Config file fields.
+ enum Field {
+ /// Timeout field.
+ Timeout,
+ /// RPZ field.
+ Rpz,
+ /// Local directory field.
+ LocalDir,
+ /// Adblock URLs field.
+ Adblock,
+ /// Domain-only URLs field.
+ Domain,
+ /// hosts(5) URLs field.
+ Hosts,
+ /// Wildcard URLs field.
+ Wildcard,
+ }
+ impl<'d> Deserialize<'d> for Field {
+ #[inline]
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'d>,
+ {
+ /// `Visitor` for `Field`.
+ struct FieldVisitor;
+ impl<'de> Visitor<'de> for FieldVisitor {
+ type Value = Field;
+ #[inline]
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ formatter.write_str(
+ "'timeout', 'rpz', 'local_dir', 'adblock', 'domain', 'hosts', or 'wildcard'",
+ )
+ }
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+ where
+ E: Error,
+ {
+ match v {
+ "timeout" => Ok(Field::Timeout),
+ "rpz" => Ok(Field::Rpz),
+ "local_dir" => Ok(Field::LocalDir),
+ "adblock" => Ok(Field::Adblock),
+ "domain" => Ok(Field::Domain),
+ "hosts" => Ok(Field::Hosts),
+ "wildcard" => Ok(Field::Wildcard),
+ _ => Err(E::unknown_field(v, &VARIANTS)),
+ }
+ }
+ }
+ deserializer.deserialize_identifier(FieldVisitor)
+ }
+ }
+ /// `Visitor` for `Config`.
+ struct ConfigVisitor;
+ impl<'d> Visitor<'d> for ConfigVisitor {
+ type Value = Config;
+ #[inline]
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ formatter.write_str("struct Config")
+ }
+ #[allow(clippy::as_conversions, clippy::cast_lossless, clippy::too_many_lines)]
+ #[inline]
+ fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+ where
+ A: MapAccess<'d>,
+ {
+ /// Verifies that the `HashSet`s are pairwise disjoint.
+ #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)]
+ #[inline]
+ fn hash_overlap<E: Error>(maps: &[&HashSet<HttpUrl>]) -> Result<(), E> {
+ /// Verifies the intersection of `left` and `right` is empty.
+ #[inline]
+ fn url_overlap<E: Error>(
+ left: &HashSet<HttpUrl>,
+ right: &HashSet<HttpUrl>,
+ ) -> Result<(), E> {
+ let (mut iter, urls) = if left.len() <= right.len() {
+ (left.into_iter(), right)
+ } else {
+ (right.into_iter(), left)
+ };
+ iter.try_fold((), |(), url| {
+ if urls.contains(url) {
+ Err(E::invalid_type(
+ Unexpected::Other(url.to_string().as_str()),
+ &"unique URLs across the block list types",
+ ))
+ } else {
+ Ok(())
+ }
+ })
+ }
+ maps.into_iter().enumerate().try_fold((), |(), (idx, map)| {
+ maps[idx + 1..]
+ .into_iter()
+ .try_fold((), |(), map2| url_overlap(map, map2))
+ })
+ }
+ /// Wrapper around a `HashSet` that is deserializable.
+ struct Urls(HashSet<HttpUrl>);
+ impl<'de> Deserialize<'de> for Urls {
+ #[inline]
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ /// `Visitor` for `Urls`.
+ struct HashVisitor;
+ impl<'d> Visitor<'d> for HashVisitor {
+ type Value = Urls;
+ #[inline]
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ formatter.write_str("struct Urls")
+ }
+ #[inline]
+ fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'d>,
+ {
+ let mut urls = HashSet::with_capacity(seq.size_hint().unwrap_or(0));
+ while let Some(url) = seq.next_element::<HttpUrl>()? {
+ urls.replace(url).map_or(Ok(()), |dup| {
+ Err(Error::invalid_value(
+ Unexpected::Other(dup.to_string().as_str()),
+ &"a set of unique URLs",
+ ))
+ })?;
+ }
+ Ok(Urls(urls))
+ }
+ }
+ deserializer.deserialize_seq(HashVisitor)
+ }
+ }
+ let mut timeout = None;
+ let mut rpz = None;
+ let mut local_dir = None;
+ let mut ad = None;
+ let mut dom = None;
+ let mut hst = None;
+ let mut wc = None;
+ while let Some(key) = map.next_key()? {
+ match key {
+ Field::Timeout => {
+ if timeout.is_some() {
+ return Err(Error::duplicate_field("timeout"));
+ }
+ timeout = Some(Duration::from_secs(map.next_value::<u32>()? as u64));
+ }
+ Field::Rpz => {
+ if rpz.is_some() {
+ return Err(Error::duplicate_field("rpz"));
+ }
+ rpz = Some(map.next_value::<AbsFilePath<false>>()?);
+ }
+ Field::LocalDir => {
+ if local_dir.is_some() {
+ return Err(Error::duplicate_field("local_dir"));
+ }
+ local_dir = Some(map.next_value::<AbsFilePath<true>>()?);
+ }
+ Field::Adblock => {
+ if ad.is_some() {
+ return Err(Error::duplicate_field("adblock"));
+ }
+ ad = Some(map.next_value::<Urls>()?);
+ }
+ Field::Domain => {
+ if dom.is_some() {
+ return Err(Error::duplicate_field("domain"));
+ }
+ dom = Some(map.next_value::<Urls>()?);
+ }
+ Field::Hosts => {
+ if hst.is_some() {
+ return Err(Error::duplicate_field("hosts"));
+ }
+ hst = Some(map.next_value::<Urls>()?);
+ }
+ Field::Wildcard => {
+ if wc.is_some() {
+ return Err(Error::duplicate_field("wildcard"));
+ }
+ wc = Some(map.next_value::<Urls>()?);
+ }
+ }
+ }
+ 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())
+ {
+ Err(Error::invalid_type(
+ Unexpected::Other("no block list URLs or directory"),
+ &"at least one block list entry (i.e., 'local_dir', 'adblock', 'domain', 'hosts', or 'wildcard' must exist and not be empty)",
+ ))
+ } else {
+ let adblock = ad.map_or_else(HashSet::new, |urls| urls.0);
+ let domain = dom.map_or_else(HashSet::new, |urls| urls.0);
+ let hosts = hst.map_or_else(HashSet::new, |urls| urls.0);
+ let wildcard = wc.map_or_else(HashSet::new, |urls| urls.0);
+ hash_overlap([&adblock, &domain, &hosts, &wildcard].as_slice()).map(|()| {
+ Config {
+ timeout,
+ rpz,
+ local_dir,
+ adblock,
+ domain,
+ hosts,
+ wildcard,
+ }
+ })
+ }
+ }
+ }
+ /// `Config` fields.
+ const VARIANTS: [&str; 7] = [
+ "timeout",
+ "rpz",
+ "local_dir",
+ "adblock",
+ "domain",
+ "hosts",
+ "wildcard",
+ ];
+ deserializer.deserialize_struct("Config", &VARIANTS, ConfigVisitor)
+ }
+}
+#[cfg(test)]
+mod tests {
+ use crate::Config;
+ use toml;
+ #[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());
+ }
+ #[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());
+ }
+ #[test]
+ fn test_timeout() {
+ assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok());
+ assert!(toml::from_str::<Config>(
+ r#"timeout=15
+local_dir="/foo/""#
+ )
+ .is_ok());
+ assert!(toml::from_str::<Config>(
+ r#"timeout=4294967295
+local_dir="/foo/""#
+ )
+ .is_ok());
+ assert!(toml::from_str::<Config>(
+ r#"timeout=-1
+local_dir="/foo/""#
+ )
+ .is_err());
+ assert!(toml::from_str::<Config>(
+ r#"timeout=0
+local_dir="/foo/""#
+ )
+ .is_ok());
+ assert!(toml::from_str::<Config>(
+ r#"timeout=4294967296
+local_dir="/foo/""#
+ )
+ .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"]"#).is_ok());
+ assert!(toml::from_str::<Config>(
+ r#"adblock=["https://foo.com/foo"]
+domain=["https://foo.com/foo"]"#
+ )
+ .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=[]
+domain=["https:///foo"]"#
+ )
+ .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/"
+wildcard=["https://foo.com/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"
+wildcard=["https://foo.com/foo"]"#
+ )
+ .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"
+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/")))));
+ }
+}
diff --git a/src/dom.rs b/src/dom.rs
@@ -0,0 +1,3265 @@
+#![allow(
+ clippy::arithmetic_side_effects,
+ clippy::missing_trait_methods,
+ clippy::implicit_return,
+ clippy::into_iter_on_ref,
+ clippy::question_mark_used,
+ clippy::ref_patterns,
+ clippy::single_char_lifetime_names,
+ clippy::unseparated_literal_suffix,
+ clippy::wildcard_enum_match_arm
+)]
+use crate::dom_count_auto_gen::proper_subdomain_count;
+use core::borrow::Borrow;
+use core::cmp::Ordering;
+use core::convert::{self, AsRef};
+use core::fmt::{self, Display, Formatter};
+use core::hash::{Hash, Hasher};
+use core::iter::FusedIterator;
+use core::num::NonZeroU8;
+use core::ops::Deref;
+use core::str;
+use num_bigint::BigUint;
+use std::error;
+use std::io::{Error, Write};
+use std::net::Ipv4Addr;
+use superset_map::SetOrd;
+use zfc::{BoundedCardinality, Cardinality, Set};
+/// A flag used to indicate information about the characters
+/// in a `Domain`. This flag is used to perform more efficient
+/// comparisons that can potentially avoid temporary
+/// memory allocations to treat uppercase letters as if they
+/// were lowercase.
+#[allow(clippy::exhaustive_enums)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum CharFlag {
+ /// No letters, ticks, or underscores.
+ None,
+ /// No uppercase letters; but there are lowercase letters, ticks, or underscores.
+ LowerOrTick,
+ /// No lowercase letters, ticks, or underscores; but there are uppercase letters.
+ Upper,
+ /// There are uppercase letters and ticks or underscores.
+ /// Note this means there can also be lowercase letters, but that is fine.
+ UpperAndTick,
+ /// There are both lowercase and uppercase letters, but there are no ticks or underscores.
+ Mixed,
+}
+impl CharFlag {
+ /// Returns a `bool` that indicates
+ /// if equivalence must be done in a case
+ /// insensitive way.
+ #[inline]
+ const fn eq_ignore_case(self, other: Self) -> bool {
+ match self {
+ Self::None => false,
+ Self::LowerOrTick => !matches!(other, Self::None | Self::LowerOrTick),
+ Self::Upper | Self::UpperAndTick => {
+ !matches!(other, Self::None | Self::Upper | Self::UpperAndTick)
+ }
+ Self::Mixed => !matches!(other, Self::None),
+ }
+ }
+}
+/// A domain that consists of at least one [`Label`].
+/// The total length of a `Domain` is at most 253
+/// characters in length including the `.` seperator.
+/// The trailing `.`, if one exists, is always removed.
+///
+/// This is more restrictive than what a domain is allowed to be
+/// per the [Domain Name System (DNS)](https://www.rfc-editor.org/rfc/rfc2181),
+/// but it is more permissive than what [RFC 1123](https://www.rfc-editor.org/rfc/rfc1123)
+/// and [RFC 5891](https://datatracker.ietf.org/doc/html/rfc5891) allow.
+/// In particular only the ASCII/UTF-8 encoding of the following Unicode scalar values is allowed in a `Label`:
+///
+/// `!`, `$`, `&`, `'`, `(`, `)`, `+`, `,`, `-`, `0`–`9`, `;`, `=`, `_`, `` ` ``, `A`–`Z`, `a`–`z`, `{`, `}`, `~`.
+///
+/// with each `Label` delimited by `.`. Uppercase letters are treated as lowercase;
+/// however for better comparison performance that doesn't lead to intermediate memory allocations,
+/// two `Domain`s should consist entirely of the same case. `Domain`s must not be an [`Ipv4Addr`].
+///
+/// Those Unicode scalar values were chosen based on what [Firefox](https://www.mozilla.org/en-US/firefox/)
+/// allows as of 2023-09-03T20:50+00:00.
+#[derive(Clone, Debug)]
+pub struct Domain<'a> {
+ /// The domain value.
+ /// Guaranteed to have length between 1 and 253.
+ /// Guaranteed to be the UTF-8 encoding of a
+ /// sequence of Unicode scalar values from the set:
+ /// `!`, `$`, `&`, `'`, `(`, `)`, `+`, `,`, `-`, `0`–`9`,
+ /// `;`, `=`, `_`, `` ` ``, `A`–`Z`, `a`–`z`, `.`, `{`, `}`, and `~`.
+ /// This is stored as a slice of bytes to
+ /// allow for easier construction of `Label`s.
+ value: &'a [u8],
+ /// The lengths of each label.
+ /// Guaranteed to have length between 1 and 127
+ /// with each value being between 1 and 63.
+ label_lens: Vec<NonZeroU8>,
+ /// Flag that contain information about the kind of
+ /// characters in `value`.
+ flag: CharFlag,
+}
+impl<'a> Domain<'a> {
+ /// The maximum length of a `Domain`.
+ /// `Domain`s don't include the trailing `.` nor the 0-octet root label,
+ /// so this is 253.
+ // SAFETY: 0 < 253 < 256.
+ #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+ pub const MAX_LEN: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(253) };
+ /// The minimum length of a `Domain`.
+ /// `Domain`s don't include the trailing `.` nor the 0-octet root label,
+ /// so this is 1.
+ // SAFETY: 0 < 1 < 256.
+ #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+ pub const MIN_LEN: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(1) };
+ /// The domain which always has at least one label.
+ /// The last label is _not_ trailed by `.`
+ #[allow(unsafe_code)]
+ #[inline]
+ #[must_use]
+ pub const fn as_str(&self) -> &'a str {
+ // SAFETY:
+ // The only way to construct a `Domain` is via
+ // `try_from` which only uses a valid ASCII
+ // substring from the original `str` input.
+ // `Domain` is immutable ensuring such invariants are kept.
+ unsafe { str::from_utf8_unchecked(self.value) }
+ }
+ /// Returns the count of [`Label`]s.
+ /// Due to length requirements of `Label` and `Domain`,
+ /// this is less than `128`.
+ #[inline]
+ #[must_use]
+ #[allow(unsafe_code, clippy::as_conversions, clippy::cast_possible_truncation)]
+ pub fn label_count(&self) -> NonZeroU8 {
+ // SAFETY:
+ // The only way to construct a `Domain` is via
+ // `try_from` which only uses a valid ASCII
+ // substring from the original `str` input.
+ // `Domain` is immutable ensuring such invariants are kept.
+ // Due to the length requirements of both `Domain` and `Label`,
+ // `label_lens` has between 1 and 127 values which is a valid
+ // `NonZeroU8`.
+ unsafe { NonZeroU8::new_unchecked(self.label_lens.len() as u8) }
+ }
+ /// The length of the `Domain`.
+ /// This is the same as `self.as_str().len()`.
+ #[inline]
+ #[must_use]
+ #[allow(unsafe_code, clippy::as_conversions, clippy::cast_possible_truncation)]
+ pub const fn len(&self) -> NonZeroU8 {
+ // SAFETY:
+ // The only way to construct a `Domain` is via
+ // `try_from` which only uses a valid ASCII
+ // substring from the original `str` input.
+ // `Domain` is immutable ensuring such invariants are kept.
+ // Due to the length requirements of `Domain` ,
+ // `self.value` is guaranteed to have length > 0 and < 254.
+ unsafe { NonZeroU8::new_unchecked(self.value.len() as u8) }
+ }
+ /// Returns an [`Iterator`] of [`Label`]s without
+ /// consuming the `Domain`.
+ #[inline]
+ #[must_use]
+ pub fn iter(&self) -> LabelIter<'a, '_> {
+ self.into_iter()
+ }
+ /// Recursively checks from the TLD if each label is the same
+ /// until one domain has no more labels.
+ #[inline]
+ fn same_labels(&self, other: &Domain<'_>) -> bool {
+ self.into_iter()
+ .zip(other)
+ .try_fold((), |(), (label, label2)| {
+ if label.value == label2.value {
+ Ok(())
+ } else {
+ Err(())
+ }
+ })
+ .map_or(false, |()| true)
+ }
+ /// Recursively checks from the TLD if each label is the same ignoring case
+ /// until one domain has no more labels.
+ #[inline]
+ fn same_labels_ignore_case(&self, other: &Domain<'_>) -> bool {
+ self.into_iter()
+ .zip(other)
+ .try_fold((), |(), (label, label2)| {
+ if label.value.eq_ignore_ascii_case(label2.value) {
+ Ok(())
+ } else {
+ Err(())
+ }
+ })
+ .map_or(false, |()| true)
+ }
+ /// Function that transforms a slice of bytes into a `Domain`.
+ /// It is not public since we only want to allow valid `str`s.
+ /// Trailing `.` is removed first.
+ #[allow(
+ clippy::as_conversions,
+ clippy::indexing_slicing,
+ clippy::option_if_let_else,
+ clippy::unreachable
+ )]
+ #[inline]
+ fn try_from_slice<'b: 'a>(mut value: &'b [u8]) -> Result<Self, DomainErr> {
+ if Ipv4Addr::parse_ascii(value).is_ok() {
+ return Err(DomainErr::Ipv4);
+ }
+ value = value.last().map_or(value, |byt| {
+ if *byt == b'.' {
+ &value[..value.len() - 1]
+ } else {
+ value
+ }
+ });
+ let len = value.len();
+ if len < Self::MIN_LEN.get() as usize {
+ Err(DomainErr::Empty)
+ } else if value.len() > Self::MAX_LEN.get() as usize {
+ Err(DomainErr::LenExceeds253(len))
+ } else {
+ let mut label_lens = Vec::with_capacity(3);
+ let mut label_len = 0;
+ // Bitwise flag that means:
+ // 0 => no letters, ticks, or underscores,
+ // 1 => lowercase letters; but no uppercase letters, ticks, or underscores.
+ // 2 => uppercase letters; but no lowercase letters, ticks, or underscores.
+ // 4 => ticks or underscores, but no letters.
+ let mut flag = 0u8;
+ match value.into_iter().try_fold((), |(), byt| {
+ match *byt {
+ b'.' => {
+ return NonZeroU8::new(label_len).map_or(
+ Err(DomainErr::EmptyLabel),
+ |length| {
+ label_lens.push(length);
+ label_len = 0;
+ Ok(())
+ },
+ )
+ }
+ b'A'..=b'Z' => flag |= 2,
+ b'`' | b'_' => flag |= 4,
+ b'a'..=b'z' => flag |= 1,
+ 0..=b' '
+ | b'"'..=b'#'
+ | b'%'
+ | b'*'
+ | b'/'
+ | b':'
+ | b'<'
+ | b'>'..=b'@'
+ | b'['..=b'^'
+ | b'|'
+ | 127.. => return Err(DomainErr::InvalidByte(*byt)),
+ _ => (),
+ }
+ if label_len == 63 {
+ Err(DomainErr::LabelLenExceeds63)
+ } else {
+ label_len += 1;
+ Ok(())
+ }
+ }) {
+ Ok(()) => match NonZeroU8::new(label_len) {
+ Some(length) => {
+ label_lens.push(length);
+ Ok(Self {
+ value,
+ label_lens,
+ flag: match flag {
+ 0 => CharFlag::None,
+ 1 | 4 | 5 => CharFlag::LowerOrTick,
+ 2 => CharFlag::Upper,
+ 3 => CharFlag::Mixed,
+ 6 | 7 => CharFlag::UpperAndTick,
+ _ => unreachable!("there is a bug in Domain::try_from_slice"),
+ },
+ })
+ }
+ None => Err(DomainErr::EmptyLabel),
+ },
+ Err(err) => Err(err),
+ }
+ }
+ }
+}
+impl PartialEq<Domain<'_>> for Domain<'_> {
+ #[inline]
+ fn eq(&self, other: &Domain<'_>) -> bool {
+ if self.flag.eq_ignore_case(other.flag) {
+ self.value.eq_ignore_ascii_case(other.value)
+ } else {
+ self.value == other.value
+ }
+ }
+}
+impl Eq for Domain<'_> {}
+impl PartialOrd<Domain<'_>> for Domain<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Domain<'_>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl<'a> AsRef<str> for Domain<'a> {
+ #[inline]
+ fn as_ref(&self) -> &'a str {
+ self.as_str()
+ }
+}
+impl<'a> Deref for Domain<'a> {
+ type Target = str;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.as_str()
+ }
+}
+/// `Ok(())` is returned iff the `Domain`s have the same labels
+/// with one `Domain` having more labels than the other.
+#[inline]
+fn cmp_doms(left: &Domain<'_>, right: &Domain<'_>) -> Result<(), Ordering> {
+ let left_input: Vec<u8>;
+ let right_input: Vec<u8>;
+ let left_dom: Domain<'_>;
+ let right_dom: Domain<'_>;
+ let (left_ref, right_ref) = match (left.flag, right.flag) {
+ (CharFlag::None, _)
+ | (_, CharFlag::None)
+ | (CharFlag::LowerOrTick, CharFlag::LowerOrTick)
+ | (CharFlag::Upper, CharFlag::Upper) => (left, right),
+ (CharFlag::LowerOrTick, _) => {
+ right_input = right.value.to_ascii_lowercase();
+ right_dom = Domain {
+ value: right_input.as_slice(),
+ label_lens: right.label_lens.clone(),
+ flag: CharFlag::LowerOrTick,
+ };
+ (left, &right_dom)
+ }
+ (CharFlag::Upper, CharFlag::Mixed) => {
+ right_input = right.value.to_ascii_uppercase();
+ right_dom = Domain {
+ value: right_input.as_slice(),
+ label_lens: right.label_lens.clone(),
+ flag: CharFlag::Upper,
+ };
+ (left, &right_dom)
+ }
+ (_, CharFlag::LowerOrTick) => {
+ left_input = left.value.to_ascii_lowercase();
+ left_dom = Domain {
+ value: left_input.as_slice(),
+ label_lens: left.label_lens.clone(),
+ flag: CharFlag::LowerOrTick,
+ };
+ (&left_dom, right)
+ }
+ (CharFlag::Mixed, CharFlag::Upper) => {
+ left_input = left.value.to_ascii_uppercase();
+ left_dom = Domain {
+ value: left_input.as_slice(),
+ label_lens: left.label_lens.clone(),
+ flag: CharFlag::Upper,
+ };
+ (&left_dom, right)
+ }
+ (_, _) => {
+ left_input = left.value.to_ascii_lowercase();
+ left_dom = Domain {
+ value: left_input.as_slice(),
+ label_lens: left.label_lens.clone(),
+ flag: CharFlag::LowerOrTick,
+ };
+ right_input = right.value.to_ascii_lowercase();
+ right_dom = Domain {
+ value: right_input.as_slice(),
+ label_lens: right.label_lens.clone(),
+ flag: CharFlag::LowerOrTick,
+ };
+ (&left_dom, &right_dom)
+ }
+ };
+ // Faster to compare the entire value when we can instead of each label.
+ if left_ref.value == right_ref.value {
+ Err(Ordering::Equal)
+ } else {
+ left_ref
+ .into_iter()
+ .zip(right_ref)
+ .try_fold((), |(), (label, label2)| {
+ match label.value.cmp(label2.value) {
+ Ordering::Less => Err(Ordering::Less),
+ Ordering::Equal => Ok(()),
+ Ordering::Greater => Err(Ordering::Greater),
+ }
+ })
+ }
+}
+impl Ord for Domain<'_> {
+ /// The total order that is defined follows the following hierarchy:
+ /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs.
+ /// 2. If 1. evaluates as not equivalent, then return the result.
+ /// 3. Return the comparison of `Label` counts.
+ ///
+ /// For example, `com` `<` `example.com` `<` `net` `<` `example.net`.
+ ///
+ /// This is the same as the [canonical DNS name order](https://datatracker.ietf.org/doc/html/rfc4034#section-6.1).
+ #[inline]
+ fn cmp(&self, other: &Self) -> Ordering {
+ cmp_doms(self, other).map_or_else(convert::identity, |()| {
+ self.label_count().cmp(&other.label_count())
+ })
+ }
+}
+impl Hash for Domain<'_> {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ match self.flag {
+ CharFlag::None | CharFlag::LowerOrTick => self.value.hash(state),
+ _ => self.value.to_ascii_lowercase().hash(state),
+ }
+ }
+}
+impl Display for Domain<'_> {
+ #[inline]
+ #[allow(clippy::min_ident_chars)]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.write_str(self.as_str())
+ }
+}
+/// Error returned when an invalid string
+/// is passed to [`Domain::try_from`], [`Adblock::parse_value`],
+/// [`DomainOnly::parse_value`], [`Hosts::parse_value`], [`Wildcard::parse_value`],
+/// or [`RpzDomain::parse_value`].
+#[allow(clippy::exhaustive_enums)]
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
+pub enum DomainErr {
+ /// The domain was empty.
+ Empty,
+ /// The length of the string had length greater than 253
+ /// not counting a terminating `.` if there was one.
+ LenExceeds253(usize),
+ /// The domain contained at least one empty label.
+ EmptyLabel,
+ /// The domain contained at least one label whose length exceeded 63.
+ LabelLenExceeds63,
+ /// The domain contained an invalid byte value.
+ ///
+ /// Note the contained `u8` is ASCII iff it is `<= 127`; otherwise
+ /// it is the first UTF-8 code unit of a multi-byte Unicode scalar value.
+ InvalidByte(u8),
+ /// The domain was an [`Ipv4Addr`].
+ Ipv4,
+ /// The string passed to [`Adblock::parse_value`] contained `$`.
+ InvalidAdblockDomain,
+ /// The string passed to [`Hosts::parse_value`] did not conform
+ /// to the required [`Hosts`] format.
+ InvalidHostsIP,
+ /// The length of the non-wildcard portion of the string passed to
+ /// [`Wildcard::parse_value`] was at least 252 which means there are
+ /// no proper subdomains.
+ InvalidWildcardDomain,
+}
+impl Display for DomainErr {
+ #[inline]
+ #[allow(unsafe_code, clippy::min_ident_chars)]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Empty => f.write_str("domain is empty"),
+ Self::LenExceeds253(len) => write!(
+ f,
+ "domain has length {len} which is greater than the max length of 253"
+ ),
+ Self::EmptyLabel => f.write_str("domain has an empty label"),
+ Self::LabelLenExceeds63 => {
+ f.write_str("domain has a label that exceeds the max length of 63")
+ }
+ Self::InvalidByte(byt) => {
+ if byt > 31 && byt < 127 {
+ let utf8 = [byt];
+ // SAFETY:
+ // `byt` is inclusively between 32 and 126 which is valid ASCII which
+ // in turn is valid UTF-8.
+ let printable_ascii = unsafe { str::from_utf8_unchecked(utf8.as_slice()) };
+ write!(f, "domain has a label with the invalid character '{printable_ascii}'")
+ } else {
+ write!(f, "domain has a label with the invalid byte value {byt}")
+ }
+ }
+ Self::Ipv4 => f.write_str("domain was an IPv4 address"),
+ Self::InvalidAdblockDomain => f.write_str("Adblock-style domain contained a '$'"),
+ Self::InvalidHostsIP => f.write_str("hosts-style domain does not begin with the IP '::', '::1', '0.0.0.0', or '127.0.0.1' followed by at least one space or tab"),
+ Self::InvalidWildcardDomain => f.write_str("non-wildcard portion of a wildcard domain had length of at least 252 which means there are 0 proper subdomains"),
+ }
+ }
+}
+impl error::Error for DomainErr {}
+impl<'a: 'b, 'b> From<Domain<'a>> for &'b str {
+ #[inline]
+ fn from(value: Domain<'a>) -> Self {
+ value.as_str()
+ }
+}
+impl<'a: 'c, 'b, 'c> From<&'b Domain<'a>> for &'c str {
+ #[inline]
+ fn from(value: &'b Domain<'a>) -> Self {
+ value.as_str()
+ }
+}
+impl<'a: 'b, 'b> TryFrom<&'a str> for Domain<'b> {
+ type Error = DomainErr;
+ #[inline]
+ fn try_from(val: &'a str) -> Result<Self, Self::Error> {
+ Self::try_from_slice(val.as_bytes())
+ }
+}
+/// A label of a [`Domain`].
+/// The total length of a `Label` is between 1 and 63
+/// bytes with each Unicode scalar value being one of the following:
+///
+/// `!`, `$`, `&`, `'`, `(`, `)`, `+`, `,`, `-`, `0`–`9`, `;`, `=`, `_`, `` ` ``, `A`–`Z`, `a`–`z`, `{`, `}`, `~`.
+///
+/// Note that the uppercase letters are treated as lowercase.
+#[derive(Debug, Clone, Copy, Hash)]
+pub struct Label<'a> {
+ /// The label value.
+ value: &'a str,
+}
+impl<'a> Label<'a> {
+ /// The maximum length of a `Label` which is 63.
+ // SAFETY: 0 < 63 < 256.
+ #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+ pub const MAX_LEN: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(63) };
+ /// The minimum length of a `Label` which is 1.
+ // SAFETY: 0 < 1 < 256.
+ #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+ pub const MIN_LEN: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(1) };
+ /// The label.
+ #[inline]
+ #[must_use]
+ pub const fn as_str(self) -> &'a str {
+ self.value
+ }
+}
+impl Display for Label<'_> {
+ #[inline]
+ #[allow(clippy::min_ident_chars)]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.write_str(self.value)
+ }
+}
+impl PartialEq<Label<'_>> for Label<'_> {
+ #[inline]
+ fn eq(&self, other: &Label<'_>) -> bool {
+ self.value.eq_ignore_ascii_case(other.value)
+ }
+}
+impl Eq for Label<'_> {}
+impl PartialOrd<Label<'_>> for Label<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Label<'_>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl Ord for Label<'_> {
+ #[inline]
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.value
+ .to_ascii_lowercase()
+ .cmp(&other.value.to_ascii_lowercase())
+ }
+}
+impl<'a: 'b, 'b> From<Label<'a>> for &'b str {
+ #[inline]
+ fn from(value: Label<'a>) -> Self {
+ value.as_str()
+ }
+}
+impl<'a> AsRef<str> for Label<'a> {
+ #[inline]
+ fn as_ref(&self) -> &'a str {
+ self.as_str()
+ }
+}
+impl Deref for Label<'_> {
+ type Target = str;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.as_str()
+ }
+}
+/// [`Iterator`] that iterates [`Label`]s
+/// from a [`Domain`] starting from the TLD
+/// down.
+pub struct IntoLabelIter<'a> {
+ /// Domain that contains `Label`s to iterate.
+ domain: Domain<'a>,
+ /// Starts at domain.label_count().get() - 1
+ /// which is valid since domain.label_count().get() > 0.
+ /// idx is 255 when the iterator is exhausted.
+ /// Since idx is decremented each time and it starts
+ /// at a value less than 254, this is a valid value to use
+ /// as a flag.
+ idx: u8,
+ /// This is used to mark the start of a label before
+ /// the length of the label has been subtracted.
+ /// After a label is read, 1 must be subtracted
+ /// to account for '.'.
+ start: u8,
+ /// Starts at 0 which is valid since domain.label_count().get() > 0.
+ /// idx_back is 255 when the iterator is exhausted.
+ /// Since idx_back is incremented each time and the max label count
+ /// is 127, this is a valid value to use as a flag.
+ idx_back: u8,
+ /// This is used to mark the start of a label before
+ /// the length of the label has been added.
+ /// After a label is read, 1 must be added
+ /// to account for '.'.
+ start_back: u8,
+}
+impl<'a> IntoLabelIter<'a> {
+ /// Helper function to construct an instance.
+ #[inline]
+ #[must_use]
+ fn new(domain: Domain<'a>) -> IntoLabelIter<'a> {
+ Self {
+ idx: domain.label_count().get() - 1,
+ start: domain.len().get(),
+ idx_back: 0,
+ start_back: 0,
+ domain,
+ }
+ }
+}
+impl<'a> Iterator for IntoLabelIter<'a> {
+ type Item = Label<'a>;
+ #[inline]
+ #[allow(
+ unsafe_code,
+ clippy::as_conversions,
+ clippy::cast_possible_truncation,
+ clippy::indexing_slicing
+ )]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.domain.label_lens.get(self.idx as usize).map(|len| {
+ self.start -= len.get();
+ let lbl = &self.domain.value[self.start as usize..(self.start + len.get()) as usize];
+ let label = Label {
+ // SAFETY:
+ // The only way to construct a `Domain` is via
+ // `try_from` which only uses a valid ASCII
+ // substring from the original `str` input.
+ // `Domain` is immutable ensuring such invariants are kept.
+ value: unsafe { str::from_utf8_unchecked(lbl) },
+ };
+ if self.idx == 0 || self.idx <= self.idx_back {
+ self.idx = 255;
+ self.idx_back = 255;
+ } else {
+ self.idx -= 1;
+ self.start -= 1;
+ }
+ label
+ })
+ }
+}
+impl FusedIterator for IntoLabelIter<'_> {}
+impl ExactSizeIterator for IntoLabelIter<'_> {
+ #[allow(clippy::as_conversions)]
+ #[inline]
+ fn len(&self) -> usize {
+ if self.idx == 255 {
+ 0
+ } else {
+ (self.idx - self.idx_back + 1) as usize
+ }
+ }
+}
+impl DoubleEndedIterator for IntoLabelIter<'_> {
+ #[inline]
+ #[allow(
+ unsafe_code,
+ clippy::as_conversions,
+ clippy::cast_possible_truncation,
+ clippy::indexing_slicing
+ )]
+ fn next_back(&mut self) -> Option<Self::Item> {
+ self.domain
+ .label_lens
+ .get(self.idx_back as usize)
+ .map(|len| {
+ let lbl = &self.domain.value
+ [self.start_back as usize..(self.start_back + len.get()) as usize];
+ let label = Label {
+ // SAFETY:
+ // The only way to construct a `Domain` is via
+ // `try_from` which only uses a valid ASCII
+ // substring from the original `str` input.
+ // `Domain` is immutable ensuring such invariants are kept.
+ value: unsafe { str::from_utf8_unchecked(lbl) },
+ };
+ if self.idx_back + 1 == self.domain.label_count().get() || self.idx_back >= self.idx
+ {
+ self.idx = 255;
+ self.idx_back = 255;
+ } else {
+ self.idx_back += 1;
+ self.start_back += len.get() + 1;
+ }
+ label
+ })
+ }
+}
+/// [`Iterator`] that iterates [`Label`]s
+/// from a borrowed [`Domain`] starting from the TLD
+/// down.
+pub struct LabelIter<'a, 'b> {
+ /// Domain that contains `Label`s to iterate.
+ domain: &'b Domain<'a>,
+ /// Starts at domain.label_count().get() - 1
+ /// which is valid since domain.label_count().get() > 0.
+ /// idx is 255 when the iterator is exhausted.
+ /// Since idx is decremented each time and it starts
+ /// at a value less than 254, this is a valid value to use
+ /// as a flag.
+ idx: u8,
+ /// This is used to mark the start of a label before
+ /// the length of the label has been subtracted.
+ /// After a label is read, 1 must be subtracted
+ /// to account for '.'.
+ start: u8,
+ /// Starts at 0 which is valid since domain.label_count().get() > 0.
+ /// idx_back is 255 when the iterator is exhausted.
+ /// Since idx_back is incremented each time and the max label count
+ /// is 127, this is a valid value to use as a flag.
+ idx_back: u8,
+ /// This is used to mark the start of a label before
+ /// the length of the label has been added.
+ /// After a label is read, 1 must be added
+ /// to account for '.'.
+ start_back: u8,
+}
+impl<'a, 'b> LabelIter<'a, 'b> {
+ /// Helper function to construct an instance.
+ #[inline]
+ #[must_use]
+ fn new(domain: &'b Domain<'a>) -> LabelIter<'a, 'b> {
+ Self {
+ idx: domain.label_count().get() - 1,
+ start: domain.len().get(),
+ idx_back: 0,
+ start_back: 0,
+ domain,
+ }
+ }
+}
+impl<'a> Iterator for LabelIter<'a, '_> {
+ type Item = Label<'a>;
+ #[inline]
+ #[allow(
+ unsafe_code,
+ clippy::as_conversions,
+ clippy::cast_possible_truncation,
+ clippy::indexing_slicing
+ )]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.domain.label_lens.get(self.idx as usize).map(|len| {
+ self.start -= len.get();
+ let lbl = &self.domain.value[self.start as usize..(self.start + len.get()) as usize];
+ let label = Label {
+ // SAFETY:
+ // The only way to construct a `Domain` is via
+ // `try_from` which only uses a valid ASCII
+ // substring from the original `str` input.
+ // `Domain` is immutable ensuring such invariants are kept.
+ value: unsafe { str::from_utf8_unchecked(lbl) },
+ };
+ if self.idx == 0 || self.idx <= self.idx_back {
+ self.idx = 255;
+ self.idx_back = 255;
+ } else {
+ self.idx -= 1;
+ self.start -= 1;
+ }
+ label
+ })
+ }
+}
+impl FusedIterator for LabelIter<'_, '_> {}
+impl ExactSizeIterator for LabelIter<'_, '_> {
+ #[allow(clippy::as_conversions)]
+ #[inline]
+ fn len(&self) -> usize {
+ if self.idx == 255 {
+ 0
+ } else {
+ (self.idx - self.idx_back + 1) as usize
+ }
+ }
+}
+impl DoubleEndedIterator for LabelIter<'_, '_> {
+ #[inline]
+ #[allow(
+ unsafe_code,
+ clippy::as_conversions,
+ clippy::cast_possible_truncation,
+ clippy::indexing_slicing
+ )]
+ fn next_back(&mut self) -> Option<Self::Item> {
+ self.domain
+ .label_lens
+ .get(self.idx_back as usize)
+ .map(|len| {
+ let lbl = &self.domain.value
+ [self.start_back as usize..(self.start_back + len.get()) as usize];
+ let label = Label {
+ // SAFETY:
+ // The only way to construct a `Domain` is via
+ // `try_from` which only uses a valid ASCII
+ // substring from the original `str` input.
+ // `Domain` is immutable ensuring such invariants are kept.
+ value: unsafe { str::from_utf8_unchecked(lbl) },
+ };
+ if self.idx_back + 1 == self.domain.label_count().get() || self.idx_back >= self.idx
+ {
+ self.idx = 255;
+ self.idx_back = 255;
+ } else {
+ self.idx_back += 1;
+ self.start_back += len.get() + 1;
+ }
+ label
+ })
+ }
+}
+impl<'a> IntoIterator for Domain<'a> {
+ type Item = Label<'a>;
+ type IntoIter = IntoLabelIter<'a>;
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter {
+ IntoLabelIter::new(self)
+ }
+}
+impl<'a, 'b> IntoIterator for &'b Domain<'a> {
+ type Item = Label<'a>;
+ type IntoIter = LabelIter<'a, 'b>;
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter {
+ LabelIter::new(self)
+ }
+}
+/// Action taken by a DNS server when a domain
+/// matches.
+#[allow(clippy::exhaustive_enums)]
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum RpzAction {
+ /// Send `NXDOMAIN` reply.
+ Nxdomain,
+ /// Send `NODATA` reply.
+ Nodata,
+ /// Do nothing; continue as normal.
+ Passthru,
+ /// Drop the query.
+ Drop,
+ /// Answer over TCP.
+ TcpOnly,
+}
+impl Display for RpzAction {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Nxdomain => f.write_str("NXDOMAIN"),
+ Self::Nodata => f.write_str("NODATA"),
+ Self::Passthru => f.write_str("PASSTHRU"),
+ Self::Drop => f.write_str("DROP"),
+ Self::TcpOnly => f.write_str("TCP-Only"),
+ }
+ }
+}
+/// Writes the following line with `writer` based on `action`:
+/// * `RpzAction::Nxdomain`: `<dom> CNAME .`.
+/// * `RpzAction::Nodata`: `<dom> CNAME *.`.
+/// * `RpzAction::Passthru`: `<dom> CNAME rpz-passthru.`.
+/// * `RpzAction::Drop`: `<dom> CNAME rpz-drop.`.
+/// * `RpzAction::TcpOnly`: `<dom> CNAME rpz-tcp-only.`.
+///
+/// `*.` is prepended to `<dom>` iff `wildcard`.
+///
+/// # Errors
+///
+/// Returns [`Error`] iff [`writeln`] does.
+#[inline]
+pub fn write_rpz_line<W: Write>(
+ mut writer: W,
+ dom: &Domain<'_>,
+ action: RpzAction,
+ wildcard: bool,
+) -> Result<(), Error> {
+ writeln!(
+ writer,
+ "{}{} CNAME {}.",
+ if wildcard { "*." } else { "" },
+ dom,
+ match action {
+ RpzAction::Nxdomain => "",
+ RpzAction::Nodata => "*",
+ RpzAction::Passthru => "rpz-passthru",
+ RpzAction::Drop => "rpz-drop",
+ RpzAction::TcpOnly => "rpz-tcp-only",
+ }
+ )
+}
+/// Type that can be returned by [`Domain`]-like
+/// parsers (e.g., [`Adblock`]).
+#[allow(clippy::exhaustive_enums)]
+#[derive(Clone, Copy, Debug)]
+pub enum Value<'a, T: ParsedDomain<'a>> {
+ /// The parsed value is a domain.
+ Domain(T),
+ /// The parsed value is a comment.
+ Comment(&'a str),
+ /// The parsed value is blank or just [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace).
+ Blank,
+}
+impl<'a, T: ParsedDomain<'a>> Value<'a, T> {
+ /// Returns `true` iff `self` is a [`Self::Domain`].
+ #[inline]
+ pub const fn is_domain(&self) -> bool {
+ match *self {
+ Self::Domain(_) => true,
+ Self::Comment(_) | Self::Blank => false,
+ }
+ }
+ /// Returns `true` iff `self` is a [`Self::Comment`].
+ #[inline]
+ pub const fn is_comment(&self) -> bool {
+ match *self {
+ Self::Comment(_) => true,
+ Self::Domain(_) | Self::Blank => false,
+ }
+ }
+ /// Returns `true` iff `self` is a [`Self::Blank`].
+ #[inline]
+ pub const fn is_blank(&self) -> bool {
+ matches!(*self, Value::Blank)
+ }
+ /// Returns the contained [`Self::Domain`] value.
+ ///
+ /// # Panics
+ ///
+ /// Panics iff `self` is [`Self::Comment`] or [`Self::Blank`].
+ #[allow(clippy::panic)]
+ #[inline]
+ pub fn unwrap_domain(self) -> T {
+ match self {
+ Self::Domain(dom) => dom,
+ Self::Comment(_) | Self::Blank => {
+ panic!("called `ParsedDomain::unwrap_domain()` on a `Comment` or `Blank` value")
+ }
+ }
+ }
+ /// Returns the contained [`prim@str`] in [`Self::Comment`].
+ ///
+ /// # Panics
+ ///
+ /// Panics iff `self` is [`Self::Domain`] or [`Self::Blank`].
+ #[allow(clippy::panic)]
+ #[inline]
+ pub fn unwrap_comment(self) -> &'a str {
+ match self {
+ Self::Comment(com) => com,
+ Self::Domain(_) | Self::Blank => {
+ panic!("called `ParsedDomain::unwrap_comment()` on a `Domain` or `Blank` value")
+ }
+ }
+ }
+ /// Returns [`unit`] when `self` is [`Self::Blank`].
+ ///
+ /// # Panics
+ ///
+ /// Panics iff `self` is [`Self::Domain`] or [`Self::Comment`].
+ #[allow(clippy::panic)]
+ #[inline]
+ pub fn unwrap_blank(self) {
+ match self {
+ Self::Blank => {}
+ Self::Domain(_) | Self::Comment(_) => {
+ panic!("called `ParsedDomain::unwrap_blank()` on a `Domain` or `Comment` value")
+ }
+ }
+ }
+}
+/// Structure of a [`Domain`]-like type that can parse [`prim@str`]s into [`Value`]s.
+/// When parsed into a [`Value::Domain`], the domain can
+/// be written to a [response policy zone (RPZ)](https://en.wikipedia.org/wiki/Response_policy_zone)
+/// file.
+pub trait ParsedDomain<'a>: Sized {
+ type Error;
+ /// Parses a `str` into a `Value`.
+ /// # Errors
+ ///
+ /// Errors iff `val` is unable to be parsed into a `Value`.
+ fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error>;
+ /// Reference to the contained `Domain`.
+ fn domain(&self) -> &Domain<'a>;
+ /// Writes `self` as RPZ lines via `writer`.
+ ///
+ /// # Errors
+ ///
+ /// Errors iff `writer` errors.
+ fn write_to_rpz<W: Write>(&self, action: RpzAction, writer: W) -> Result<(), Error>;
+}
+/// Domain constructed from an [Adblock-style rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#adblock-style-syntax)
+/// with the requirement that the rule conforms to the following extended regex:
+///
+/// `^<ws>*(\|\|)?<ws>*<domain><ws>*\^?<ws>*$`
+///
+/// where `<domain>` conforms to a valid [`Domain`] with the added requirement that it does not contain `$`, and
+/// `<ws>` is any sequence of [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace).
+///
+/// Comments are any lines that start with `!` or `#` (ignoring whitespace). Any in-line comments
+/// after a valid domain are ignored and will be parsed into a [`Value::Domain`].
+///
+/// Note that this means some valid Adblock-style rules are not considered valid since
+/// such rules often contain path information or modifiers (e.g., “third-party”), but this only
+/// considers domain-only rules.
+#[derive(Clone, Debug)]
+pub struct Adblock<'a> {
+ /// The `Domain`.
+ domain: Domain<'a>,
+ /// `true` iff `domain` represents all subdomains.
+ /// Note that this includes `domain` itself.
+ subdomains: bool,
+}
+impl<'a> Adblock<'a> {
+ /// Returns `true` iff the contained [`Domain`] represents all subdomains.
+ /// Note this includes the `Domain` itself.
+ #[inline]
+ #[must_use]
+ pub const fn is_subdomains(&self) -> bool {
+ self.subdomains
+ }
+ /// Since `DomainOnly` and `Hosts` are treated the same,
+ /// we have this helper function that can be used for both.
+ #[inline]
+ #[must_use]
+ fn cmp_dom(&self, other: &Domain<'_>) -> Ordering {
+ cmp_doms(&self.domain, other).map_or_else(
+ |ord| {
+ if ord == Ordering::Equal && self.subdomains {
+ Ordering::Greater
+ } else {
+ ord
+ }
+ },
+ |()| {
+ // At this point `self` and `other` have different number of labels;
+ // otherwise they would have the same domain which was already
+ // checked for in `cmp_doms`.
+ if self.subdomains {
+ Ordering::Greater
+ } else {
+ self.domain.label_count().cmp(&other.label_count())
+ }
+ },
+ )
+ }
+ /// The total order that is defined follows the following hierarchy:
+ /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs.
+ /// 2. If 1. evaluates as not equivalent, then return the result.
+ /// 3. If `self` represents a single `Domain` (i.e., `!self.is_subdomains()`),
+ /// then return the comparison of label counts.
+ /// 4. `self` is greater.
+ ///
+ /// For example, `com` `<` `example.com` `<` `||example.com` `<` `||com` `<` `net` `<` `example.net` `<` `||example.net` `<` `||net`.
+ #[inline]
+ #[must_use]
+ pub fn cmp_domain_only(&self, other: &DomainOnly<'_>) -> Ordering {
+ self.cmp_dom(&other.domain)
+ }
+ /// Same as [`Adblock::cmp_domain_only`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_hosts(&self, other: &Hosts<'_>) -> Ordering {
+ self.cmp_dom(&other.domain)
+ }
+ /// The total order that is defined follows the following hierarchy:
+ /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs.
+ /// 2. If 1. evaluates as not equivalent, then return the result.
+ /// 3. If both domains represent a single `Domain`, then return the comparison
+ /// of label counts.
+ /// 4. If one domain represents a single `Domain`, then return that that domain is less.
+ /// 5. If the label counts are the same, `self` is greater.
+ /// 6. Return the inverse of the comparison of label counts.
+ ///
+ /// 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`
+ #[inline]
+ #[must_use]
+ pub fn cmp_wildcard(&self, other: &Wildcard<'_>) -> Ordering {
+ cmp_doms(&self.domain, &other.domain).map_or_else(
+ |ord| {
+ if ord == Ordering::Equal {
+ if self.subdomains {
+ Ordering::Greater
+ } else if other.proper_subdomains {
+ Ordering::Less
+ } else {
+ ord
+ }
+ } else {
+ ord
+ }
+ },
+ |()| {
+ // At this point `self` and `other` have different number of labels;
+ // otherwise they would have the same domain which was already
+ // checked for in `cmp_doms`.
+ if self.subdomains {
+ if !other.proper_subdomains
+ || self.domain.label_count() < other.domain.label_count()
+ {
+ Ordering::Greater
+ } else {
+ Ordering::Less
+ }
+ } else if other.proper_subdomains
+ || self.domain.label_count() < other.domain.label_count()
+ {
+ Ordering::Less
+ } else {
+ Ordering::Greater
+ }
+ },
+ )
+ }
+ /// Same as [`Adblock::cardinality`] except
+ /// that a `BigUint` is returned.
+ /// Note the count _includes_ the `Domain` itself
+ /// when `self.is_subdomains()`.
+ ///
+ /// `!self.is_subdomains()` ⇔ `self.domain_count() == BigUint::new(vec![1])`.
+ #[inline]
+ #[must_use]
+ pub fn domain_count(&self) -> BigUint {
+ if self.subdomains {
+ proper_subdomain_count(&self.domain) + BigUint::new(vec![1])
+ } else {
+ BigUint::new(vec![1])
+ }
+ }
+}
+impl Display for Adblock<'_> {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}{}",
+ if self.subdomains { "||" } else { "" },
+ self.domain
+ )
+ }
+}
+impl PartialEq<Adblock<'_>> for Adblock<'_> {
+ #[inline]
+ fn eq(&self, other: &Adblock<'_>) -> bool {
+ self.domain == other.domain && self.subdomains == other.subdomains
+ }
+}
+impl PartialEq<DomainOnly<'_>> for Adblock<'_> {
+ #[inline]
+ fn eq(&self, other: &DomainOnly<'_>) -> bool {
+ !self.subdomains && self.domain == other.domain
+ }
+}
+impl PartialEq<Hosts<'_>> for Adblock<'_> {
+ #[inline]
+ fn eq(&self, other: &Hosts<'_>) -> bool {
+ !self.subdomains && self.domain == other.domain
+ }
+}
+impl PartialEq<Wildcard<'_>> for Adblock<'_> {
+ #[allow(clippy::suspicious_operation_groupings)]
+ #[inline]
+ fn eq(&self, other: &Wildcard<'_>) -> bool {
+ !(self.subdomains || other.proper_subdomains) && self.domain == other.domain
+ }
+}
+impl Eq for Adblock<'_> {}
+impl Hash for Adblock<'_> {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.domain.hash(state);
+ }
+}
+impl PartialOrd<Adblock<'_>> for Adblock<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl Ord for Adblock<'_> {
+ /// The total order that is defined follows the following hierarchy:
+ /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs.
+ /// 2. If 1. evaluates as not equivalent, then return the result.
+ /// 3. If both domains represent a single `Domain`, then return the comparison
+ /// of label counts.
+ /// 4. If one domain represents a single `Domain`, then return that that domain is less.
+ /// 5. Return the inverse of the comparison of label counts.
+ ///
+ /// For example, `com` `<` `example.com` `<` `||example.com` `<` `||com` `<` `net` `<` `example.net` `<` `||example.net` `<` `||net`.
+ #[inline]
+ #[must_use]
+ fn cmp(&self, other: &Self) -> Ordering {
+ cmp_doms(&self.domain, &other.domain).map_or_else(
+ |ord| {
+ if ord == Ordering::Equal {
+ if self.subdomains {
+ if other.subdomains {
+ ord
+ } else {
+ Ordering::Greater
+ }
+ } else if other.subdomains {
+ Ordering::Less
+ } else {
+ ord
+ }
+ } else {
+ ord
+ }
+ },
+ |()| {
+ // At this point `self` and `other` have different number of labels;
+ // otherwise they would have the same domain which was already
+ // checked for in `cmp_doms`.
+ if self.subdomains {
+ if !other.subdomains || self.domain.label_count() < other.domain.label_count() {
+ Ordering::Greater
+ } else {
+ Ordering::Less
+ }
+ } else if other.subdomains || self.domain.label_count() < other.domain.label_count()
+ {
+ Ordering::Less
+ } else {
+ Ordering::Greater
+ }
+ },
+ )
+ }
+}
+impl PartialOrd<DomainOnly<'_>> for Adblock<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> {
+ Some(self.cmp_domain_only(other))
+ }
+}
+impl PartialOrd<Hosts<'_>> for Adblock<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> {
+ Some(self.cmp_hosts(other))
+ }
+}
+impl PartialOrd<Wildcard<'_>> for Adblock<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> {
+ Some(self.cmp_wildcard(other))
+ }
+}
+impl<'a> Set for Adblock<'a> {
+ type Elem = Domain<'a>;
+ #[inline]
+ fn bounded_cardinality(&self) -> BoundedCardinality {
+ BoundedCardinality::from_biguint_exact(self.domain_count())
+ }
+ #[inline]
+ fn cardinality(&self) -> Option<Cardinality> {
+ Some(Cardinality::Finite(self.domain_count()))
+ }
+ #[inline]
+ fn contains<Q>(&self, elem: &Q) -> bool
+ where
+ Q: Borrow<Self::Elem> + Eq + ?Sized,
+ {
+ if self.subdomains {
+ let dom2 = elem.borrow();
+ self.domain.label_count() <= dom2.label_count()
+ && if self.domain.flag.eq_ignore_case(dom2.flag) {
+ self.domain.same_labels_ignore_case(dom2)
+ } else {
+ self.domain.same_labels(dom2)
+ }
+ } else {
+ self.domain == *elem.borrow()
+ }
+ }
+ #[inline]
+ fn is_proper_subset(&self, val: &Self) -> bool {
+ // A single domain can never be a proper superset.
+ // Subdomains` cannot be a proper superset
+ // if it has more labels or the same number of labels
+ // as another subdomains.
+ // In all other cases, we need to recursively check from the TLD
+ // that the labels are the same.
+ val.subdomains
+ && match val.domain.label_count().cmp(&self.domain.label_count()) {
+ Ordering::Less => {
+ if self.domain.flag.eq_ignore_case(val.domain.flag) {
+ val.domain.same_labels_ignore_case(&self.domain)
+ } else {
+ val.domain.same_labels(&self.domain)
+ }
+ }
+ Ordering::Equal => {
+ !self.subdomains
+ && if val.domain.flag.eq_ignore_case(self.domain.flag) {
+ val.domain.value.eq_ignore_ascii_case(self.domain.value)
+ || val.domain.same_labels_ignore_case(&self.domain)
+ } else {
+ val.domain.value == self.domain.value
+ || val.domain.same_labels(&self.domain)
+ }
+ }
+ Ordering::Greater => false,
+ }
+ }
+ #[inline]
+ fn is_subset(&self, val: &Self) -> bool {
+ self == val || self.is_proper_subset(val)
+ }
+}
+impl SetOrd for Adblock<'_> {}
+impl<'a> AsRef<Domain<'a>> for Adblock<'a> {
+ #[inline]
+ fn as_ref(&self) -> &Domain<'a> {
+ &self.domain
+ }
+}
+impl<'a> AsRef<str> for Adblock<'a> {
+ #[inline]
+ fn as_ref(&self) -> &'a str {
+ self.as_str()
+ }
+}
+impl<'a> Deref for Adblock<'a> {
+ type Target = Domain<'a>;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &self.domain
+ }
+}
+impl<'a> ParsedDomain<'a> for Adblock<'a> {
+ type Error = DomainErr;
+ #[allow(unsafe_code, clippy::indexing_slicing)]
+ #[inline]
+ fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> {
+ // First remove leading whitepace.
+ // Then check for comments via '#' and '!'.
+ // Return Blank iff empty.
+ // Return Comment iff '#' or '!' is the first character.
+ // Remove trailing whitespace.
+ // Next remove the last byte if it is '^' as well as whitespace before.
+ // Next track and remove '||' at the beginning and any subsequent whitespace.
+ let mut value = val.as_bytes().trim_ascii_start();
+ value.first().map_or_else(
+ || Ok(Value::Blank),
+ |byt| {
+ if *byt == b'#' || *byt == b'!' {
+ // SAFETY:
+ // `value` came from `val` with leading ASCII whitespace removed
+ // which is still valid UTF-8. Since the first byte is '#' or '$'
+ // the remaining bytes is still valid UTF-8.
+ let comment = unsafe { str::from_utf8_unchecked(&value[1..]) };
+ Ok(Value::Comment(comment))
+ } else {
+ value = value.trim_ascii_end();
+ let len = value.len().wrapping_sub(1);
+ value = value.get(len).map_or(value, |byt2| {
+ if *byt2 == b'^' {
+ value[..len].trim_ascii_end()
+ } else {
+ value
+ }
+ });
+ let (subdomains, val2) = value.get(..2).map_or_else(
+ || (false, value),
+ |fst| {
+ if fst == b"||" {
+ (true, value[2..].trim_ascii_start())
+ } else {
+ (false, value)
+ }
+ },
+ );
+ // `Domain`s allow `$`, but we don't want to allow that symbol
+ // for Adblock-style rules.
+ val2.into_iter()
+ .try_fold((), |(), byt2| {
+ if *byt2 == b'$' {
+ Err(DomainErr::InvalidAdblockDomain)
+ } else {
+ Ok(())
+ }
+ })
+ .and_then(|()| {
+ Domain::try_from_slice(val2).map(|domain| {
+ // A domain of length 252 or 253 can't have subdomains
+ // due to there not being enough characters.
+ Value::Domain(Self {
+ subdomains: if domain.len().get() > 251 {
+ false
+ } else {
+ subdomains
+ },
+ domain,
+ })
+ })
+ })
+ }
+ },
+ )
+ }
+ #[inline]
+ fn domain(&self) -> &Domain<'a> {
+ &self.domain
+ }
+ #[inline]
+ fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> {
+ write_rpz_line(&mut writer, self.domain(), action, false).and_then(|()| {
+ if self.subdomains {
+ write_rpz_line(writer, self.domain(), action, true)
+ } else {
+ Ok(())
+ }
+ })
+ }
+}
+/// Domain constructed from a [domains-only rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#domains-only-syntax)
+/// with the requirement that the rule conforms to the following regex:
+///
+/// `^<ws>*<domain><ws>*(#.*)?$`
+///
+/// where `<domain>` conforms to a valid [`Domain`], and `<ws>` is any sequence of
+/// [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace).
+///
+/// Comments are any lines that start with `#` (ignoring whitespace). Any in-line comments
+/// after a valid domain are ignored and will be parsed into a [`Value::Domain`].
+#[derive(Clone, Debug)]
+pub struct DomainOnly<'a> {
+ /// The `Domain`.
+ domain: Domain<'a>,
+}
+impl<'a> DomainOnly<'a> {
+ /// Read [`Adblock::cmp_domain_only`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_adblock(&self, other: &Adblock<'_>) -> Ordering {
+ other.cmp_domain_only(self).reverse()
+ }
+ /// Read [`Domain::cmp`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_hosts(&self, other: &Hosts<'_>) -> Ordering {
+ self.domain.cmp(&other.domain)
+ }
+ /// Read [`Wildcard::cmp_domain_only`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_wildcard(&self, other: &Wildcard<'_>) -> Ordering {
+ other.cmp_domain_only(self).reverse()
+ }
+ /// Same as [`DomainOnly::cardinality`] except
+ /// that a `NonZeroU8` is returned.
+ ///
+ /// The value is always 1.
+ #[allow(unsafe_code)]
+ #[inline]
+ #[must_use]
+ pub const fn domain_count(&self) -> NonZeroU8 {
+ // SAFETY:
+ // 0 < 1 < 256.
+ unsafe { NonZeroU8::new_unchecked(1) }
+ }
+}
+impl PartialEq<DomainOnly<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn eq(&self, other: &DomainOnly<'_>) -> bool {
+ self.domain == other.domain
+ }
+}
+impl PartialEq<Adblock<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn eq(&self, other: &Adblock<'_>) -> bool {
+ other == self
+ }
+}
+impl PartialEq<Hosts<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn eq(&self, other: &Hosts<'_>) -> bool {
+ self.domain == other.domain
+ }
+}
+impl PartialEq<Wildcard<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn eq(&self, other: &Wildcard<'_>) -> bool {
+ !other.proper_subdomains && self.domain == other.domain
+ }
+}
+impl Eq for DomainOnly<'_> {}
+impl Hash for DomainOnly<'_> {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.domain.hash(state);
+ }
+}
+impl PartialOrd<DomainOnly<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl Ord for DomainOnly<'_> {
+ /// Read [`Domain::cmp`].
+ #[inline]
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.domain.cmp(&other.domain)
+ }
+}
+impl PartialOrd<Adblock<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> {
+ Some(self.cmp_adblock(other))
+ }
+}
+impl PartialOrd<Hosts<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> {
+ Some(self.cmp_hosts(other))
+ }
+}
+impl PartialOrd<Wildcard<'_>> for DomainOnly<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> {
+ Some(self.cmp_wildcard(other))
+ }
+}
+impl Display for DomainOnly<'_> {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ self.domain.fmt(f)
+ }
+}
+impl<'a> Set for DomainOnly<'a> {
+ type Elem = Domain<'a>;
+ #[inline]
+ fn bounded_cardinality(&self) -> BoundedCardinality {
+ BoundedCardinality::from_biguint_exact(self.domain_count().get().into())
+ }
+ #[inline]
+ fn cardinality(&self) -> Option<Cardinality> {
+ Some(Cardinality::Finite(self.domain_count().get().into()))
+ }
+ #[inline]
+ fn contains<Q>(&self, elem: &Q) -> bool
+ where
+ Q: Borrow<Self::Elem> + Eq + ?Sized,
+ {
+ self.domain == *elem.borrow()
+ }
+ #[inline]
+ fn is_proper_subset(&self, _: &Self) -> bool {
+ false
+ }
+ #[inline]
+ fn is_subset(&self, val: &Self) -> bool {
+ self == val
+ }
+}
+impl SetOrd for DomainOnly<'_> {}
+impl<'a> AsRef<Domain<'a>> for DomainOnly<'a> {
+ #[inline]
+ fn as_ref(&self) -> &Domain<'a> {
+ &self.domain
+ }
+}
+impl<'a> AsRef<str> for DomainOnly<'a> {
+ #[inline]
+ fn as_ref(&self) -> &'a str {
+ self.as_str()
+ }
+}
+impl<'a> Borrow<Domain<'a>> for DomainOnly<'a> {
+ #[inline]
+ fn borrow(&self) -> &Domain<'a> {
+ &self.domain
+ }
+}
+impl<'a> Deref for DomainOnly<'a> {
+ type Target = Domain<'a>;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &self.domain
+ }
+}
+impl<'a> ParsedDomain<'a> for DomainOnly<'a> {
+ type Error = DomainErr;
+ #[allow(unsafe_code, clippy::indexing_slicing)]
+ #[inline]
+ fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> {
+ let value = val.as_bytes().trim_ascii_start();
+ value.first().map_or_else(
+ || Ok(Value::Blank),
+ |byt| {
+ if *byt == b'#' {
+ // SAFETY:
+ // `value` came from `val` with leading ASCII whitespace removed
+ // which is still valid UTF-8. Since the first byte is '#' or '$'
+ // the remaining bytes is still valid UTF-8.
+ let comment = unsafe { str::from_utf8_unchecked(&value[1..]) };
+ Ok(Value::Comment(comment))
+ } else {
+ Domain::try_from_slice(
+ value[..value
+ .into_iter()
+ .try_fold(0, |i, byt2| if *byt2 == b'#' { Err(i) } else { Ok(i + 1) })
+ .map_or_else(convert::identity, convert::identity)]
+ .trim_ascii_end(),
+ )
+ .map(|domain| Value::Domain(Self { domain }))
+ }
+ },
+ )
+ }
+ #[inline]
+ fn domain(&self) -> &Domain<'a> {
+ &self.domain
+ }
+ #[inline]
+ fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> {
+ write_rpz_line(&mut writer, self.domain(), action, false)
+ }
+}
+/// Domain constructed from a [`hosts(5)`-style rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#etc-hosts-syntax)
+/// with the requirement that the rule conforms to the following extended regex:
+///
+/// `^<ws>*<ip><ws>+<domain><ws>*(#.*)?$`
+///
+/// where `<domain>` conforms to a valid [`Domain`], `<ws>` is any sequence of
+/// [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace), and
+/// `<ip>` is one of the following:
+///
+/// `::`, `::1`, `0.0.0.0`, or `127.0.0.1`.
+///
+/// Comments are any lines that start with `#` (ignoring whitespace). Any in-line comments
+/// after a valid domain are ignored and will be parsed into a [`Value::Domain`].
+#[derive(Clone, Debug)]
+pub struct Hosts<'a> {
+ /// The `Domain`.
+ domain: Domain<'a>,
+}
+impl<'a> Hosts<'a> {
+ /// Read [`Adblock::cmp_hosts`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_adblock(&self, other: &Adblock<'_>) -> Ordering {
+ other.cmp_hosts(self).reverse()
+ }
+ /// Read [`DomainOnly::cmp_hosts`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_domain_only(&self, other: &DomainOnly<'_>) -> Ordering {
+ other.cmp_hosts(self).reverse()
+ }
+ /// Read [`Wildcard::cmp_hosts`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_wildcard(&self, other: &Wildcard<'_>) -> Ordering {
+ other.cmp_hosts(self).reverse()
+ }
+ /// Same as [`Hosts::cardinality`] except
+ /// that a `NonZeroU8` is returned.
+ ///
+ /// The value is always 1.
+ #[allow(unsafe_code)]
+ #[inline]
+ #[must_use]
+ pub const fn domain_count(&self) -> NonZeroU8 {
+ // SAFETY:
+ // 0 < 1 < 256.
+ unsafe { NonZeroU8::new_unchecked(1) }
+ }
+}
+impl PartialEq<Hosts<'_>> for Hosts<'_> {
+ #[inline]
+ fn eq(&self, other: &Hosts<'_>) -> bool {
+ self.domain == other.domain
+ }
+}
+impl PartialEq<Adblock<'_>> for Hosts<'_> {
+ #[inline]
+ fn eq(&self, other: &Adblock<'_>) -> bool {
+ other == self
+ }
+}
+impl PartialEq<DomainOnly<'_>> for Hosts<'_> {
+ #[inline]
+ fn eq(&self, other: &DomainOnly<'_>) -> bool {
+ other == self
+ }
+}
+impl PartialEq<Wildcard<'_>> for Hosts<'_> {
+ #[inline]
+ fn eq(&self, other: &Wildcard<'_>) -> bool {
+ !other.proper_subdomains && self.domain == other.domain
+ }
+}
+impl Eq for Hosts<'_> {}
+impl Hash for Hosts<'_> {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.domain.hash(state);
+ }
+}
+impl PartialOrd<Hosts<'_>> for Hosts<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl Ord for Hosts<'_> {
+ /// Read [`Domain::cmp`].
+ #[inline]
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.domain.cmp(&other.domain)
+ }
+}
+impl PartialOrd<Adblock<'_>> for Hosts<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> {
+ Some(self.cmp_adblock(other))
+ }
+}
+impl PartialOrd<DomainOnly<'_>> for Hosts<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> {
+ Some(self.cmp_domain_only(other))
+ }
+}
+impl PartialOrd<Wildcard<'_>> for Hosts<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> {
+ Some(self.cmp_wildcard(other))
+ }
+}
+impl Display for Hosts<'_> {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ self.domain.fmt(f)
+ }
+}
+impl<'a> Set for Hosts<'a> {
+ type Elem = Domain<'a>;
+ #[inline]
+ fn bounded_cardinality(&self) -> BoundedCardinality {
+ BoundedCardinality::from_biguint_exact(self.domain_count().get().into())
+ }
+ #[inline]
+ fn cardinality(&self) -> Option<Cardinality> {
+ Some(Cardinality::Finite(self.domain_count().get().into()))
+ }
+ #[inline]
+ fn contains<Q>(&self, elem: &Q) -> bool
+ where
+ Q: Borrow<Self::Elem> + Eq + ?Sized,
+ {
+ self.domain == *elem.borrow()
+ }
+ #[inline]
+ fn is_proper_subset(&self, _: &Self) -> bool {
+ false
+ }
+ #[inline]
+ fn is_subset(&self, val: &Self) -> bool {
+ self == val
+ }
+}
+impl SetOrd for Hosts<'_> {}
+impl<'a> AsRef<Domain<'a>> for Hosts<'a> {
+ #[inline]
+ fn as_ref(&self) -> &Domain<'a> {
+ &self.domain
+ }
+}
+impl<'a> AsRef<str> for Hosts<'a> {
+ #[inline]
+ fn as_ref(&self) -> &'a str {
+ self.as_str()
+ }
+}
+impl<'a> Borrow<Domain<'a>> for Hosts<'a> {
+ #[inline]
+ fn borrow(&self) -> &Domain<'a> {
+ &self.domain
+ }
+}
+impl<'a> Deref for Hosts<'a> {
+ type Target = Domain<'a>;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &self.domain
+ }
+}
+impl<'a> ParsedDomain<'a> for Hosts<'a> {
+ type Error = DomainErr;
+ #[allow(unsafe_code, clippy::indexing_slicing)]
+ #[inline]
+ fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> {
+ let mut value = val.as_bytes().trim_ascii_start();
+ value.first().map_or_else(
+ || Ok(Value::Blank),
+ |byt| {
+ if *byt == b'#' {
+ // SAFETY:
+ // `value` came from `val` with leading ASCII whitespace removed
+ // which is still valid UTF-8. Since the first byte is '#' or '$'
+ // the remaining bytes is still valid UTF-8.
+ let comment = unsafe { str::from_utf8_unchecked(&value[1..]) };
+ Ok(Value::Comment(comment))
+ } else {
+ value = value
+ .get(..3)
+ .ok_or(DomainErr::InvalidHostsIP)
+ .and_then(|fst| {
+ if fst == b"::1" {
+ Ok(&value[3..])
+ } else if &value[..2] == b"::" {
+ Ok(&value[2..])
+ } else {
+ value
+ .get(..7)
+ .ok_or(DomainErr::InvalidHostsIP)
+ .and_then(|fst2| {
+ if fst2 == b"0.0.0.0" {
+ Ok(&value[7..])
+ } else {
+ value
+ .get(..9)
+ .ok_or(DomainErr::InvalidHostsIP)
+ .and_then(|fst3| {
+ if fst3 == b"127.0.0.1" {
+ Ok(&value[9..])
+ } else {
+ Err(DomainErr::InvalidHostsIP)
+ }
+ })
+ }
+ })
+ }
+ })?;
+ let len = value.len();
+ value = value.trim_ascii_start();
+ if len == value.len() {
+ // There has to be at least one space or tab between
+ // the IP and domain.
+ Err(DomainErr::InvalidHostsIP)
+ } else {
+ Domain::try_from_slice(
+ value[..value
+ .into_iter()
+ .try_fold(
+ 0,
+ |i, byt2| if *byt2 == b'#' { Err(i) } else { Ok(i + 1) },
+ )
+ .map_or_else(convert::identity, convert::identity)]
+ .trim_ascii_end(),
+ )
+ .map(|domain| Value::Domain(Self { domain }))
+ }
+ }
+ },
+ )
+ }
+ #[inline]
+ fn domain(&self) -> &Domain<'a> {
+ &self.domain
+ }
+ #[inline]
+ fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> {
+ write_rpz_line(&mut writer, self.domain(), action, false)
+ }
+}
+/// Domain constructed from a [wildcard domain rule](https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblock&showintro=0&mimetype=plaintext)
+/// with the requirement that the rule conforms to the following extended regex:
+///
+/// `^<ws>*(\*\.)?<domain><ws>*(#.*)?$`
+///
+/// where `<domain>` conforms to a valid [`Domain`], and `<ws>` is any sequence of
+/// [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace).
+///
+/// If `domain` begins with `*.`, then `domain` must have length less than 252.
+///
+/// Comments are any lines that start with `#` (ignoring whitespace). Any in-line comments
+/// after a valid domain are ignored and will be parsed into a [`Value::Domain`].
+#[derive(Clone, Debug)]
+pub struct Wildcard<'a> {
+ /// The `Domain`.
+ domain: Domain<'a>,
+ /// `true` iff `domain` represents all proper subdomains.
+ /// Note that this does _not_ include `domain` itself.
+ proper_subdomains: bool,
+}
+impl<'a> Wildcard<'a> {
+ /// Returns `true` iff the contained [`Domain`] represents all proper subdomains.
+ /// Note this does _not_ include the `Domain` itself.
+ #[inline]
+ #[must_use]
+ pub const fn is_proper_subdomains(&self) -> bool {
+ self.proper_subdomains
+ }
+ /// Read [`Adblock::cmp_wildcard`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_adblock(&self, other: &Adblock<'_>) -> Ordering {
+ other.cmp_wildcard(self).reverse()
+ }
+ /// Since `DomainOnly` and `Hosts` are treated the same,
+ /// we have this helper function that can be used for both.
+ #[inline]
+ #[must_use]
+ fn cmp_dom(&self, other: &Domain<'_>) -> Ordering {
+ cmp_doms(&self.domain, other).map_or_else(
+ |ord| {
+ if ord == Ordering::Equal && self.proper_subdomains {
+ Ordering::Greater
+ } else {
+ ord
+ }
+ },
+ |()| {
+ // At this point `self` and `other` have different number of labels;
+ // otherwise they would have the same domain which was already
+ // checked for in `cmp_doms`.
+ if self.proper_subdomains {
+ Ordering::Greater
+ } else {
+ self.domain.label_count().cmp(&other.label_count())
+ }
+ },
+ )
+ }
+ /// The total order that is defined follows the following hierarchy:
+ /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs.
+ /// 2. If 1. evaluates as not equivalent, then return the result.
+ /// 3. If `self` represents a single `Domain` (i.e., `!self.is_proper_subdomains()`),
+ /// then return the comparison of label counts.
+ /// 4. Return `self` is greater.
+ ///
+ /// For example, `com` `<` `example.com` `<` `*.example.com` `<` `*.com` `<` `net` `<` `example.net` `<` `*.example.net` `<` `*.net`.
+ #[inline]
+ #[must_use]
+ pub fn cmp_domain_only(&self, other: &DomainOnly<'_>) -> Ordering {
+ self.cmp_dom(&other.domain)
+ }
+ /// Read [`Wildcard::cmp_domain_only`].
+ #[inline]
+ #[must_use]
+ pub fn cmp_hosts(&self, other: &Hosts<'_>) -> Ordering {
+ self.cmp_dom(&other.domain)
+ }
+ /// Same as [`Wildcard::cardinality`] except
+ /// that a `BigUint` is returned.
+ /// Note the count does _not_ include the `Domain` itself
+ /// when `self.is_proper_subdomains()`.
+ ///
+ /// `!self.is_proper_subdomains()` ⇔ `self.domain_count() == BigUint::new(vec![1])`.
+ #[inline]
+ #[must_use]
+ pub fn domain_count(&self) -> BigUint {
+ if self.proper_subdomains {
+ proper_subdomain_count(&self.domain)
+ } else {
+ BigUint::new(vec![1])
+ }
+ }
+}
+impl PartialEq<Wildcard<'_>> for Wildcard<'_> {
+ #[inline]
+ fn eq(&self, other: &Wildcard<'_>) -> bool {
+ self.domain == other.domain && self.proper_subdomains == other.proper_subdomains
+ }
+}
+impl PartialEq<Adblock<'_>> for Wildcard<'_> {
+ #[inline]
+ fn eq(&self, other: &Adblock<'_>) -> bool {
+ other == self
+ }
+}
+impl PartialEq<DomainOnly<'_>> for Wildcard<'_> {
+ #[inline]
+ fn eq(&self, other: &DomainOnly<'_>) -> bool {
+ other == self
+ }
+}
+impl PartialEq<Hosts<'_>> for Wildcard<'_> {
+ #[inline]
+ fn eq(&self, other: &Hosts<'_>) -> bool {
+ other == self
+ }
+}
+impl Eq for Wildcard<'_> {}
+impl Hash for Wildcard<'_> {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.domain.hash(state);
+ }
+}
+impl PartialOrd<Wildcard<'_>> for Wildcard<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl Ord for Wildcard<'_> {
+ /// The total order that is defined follows the following hierarchy:
+ /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs.
+ /// 2. If 1. evaluates as not equivalent, then return the result.
+ /// 3. If both domains represent a single `Domain`, then return the comparison
+ /// of label counts.
+ /// 4. If one domain represents a single `Domain`, then return that that domain is less.
+ /// 5. Return the inverse of the comparison of label counts.
+ ///
+ /// For example, `com` `<` `example.com` `<` `*.example.com` `<` `*.com` `<` `net` `<` `example.net` `<` `*.example.net` `<` `*.net`.
+ #[inline]
+ #[must_use]
+ fn cmp(&self, other: &Self) -> Ordering {
+ cmp_doms(&self.domain, &other.domain).map_or_else(
+ |ord| {
+ if ord == Ordering::Equal {
+ if self.proper_subdomains {
+ if other.proper_subdomains {
+ ord
+ } else {
+ Ordering::Greater
+ }
+ } else if other.proper_subdomains {
+ Ordering::Less
+ } else {
+ ord
+ }
+ } else {
+ ord
+ }
+ },
+ |()| {
+ // At this point `self` and `other` have different number of labels;
+ // otherwise they would have the same domain which was already
+ // checked for in `cmp_doms`.
+ if self.proper_subdomains {
+ if !other.proper_subdomains
+ || self.domain.label_count() < other.domain.label_count()
+ {
+ Ordering::Greater
+ } else {
+ Ordering::Less
+ }
+ } else if other.proper_subdomains
+ || self.domain.label_count() < other.domain.label_count()
+ {
+ Ordering::Less
+ } else {
+ Ordering::Greater
+ }
+ },
+ )
+ }
+}
+impl PartialOrd<Adblock<'_>> for Wildcard<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> {
+ Some(self.cmp_adblock(other))
+ }
+}
+impl PartialOrd<DomainOnly<'_>> for Wildcard<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> {
+ Some(self.cmp_domain_only(other))
+ }
+}
+impl PartialOrd<Hosts<'_>> for Wildcard<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> {
+ Some(self.cmp_hosts(other))
+ }
+}
+impl Display for Wildcard<'_> {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}{}",
+ if self.proper_subdomains { "*." } else { "" },
+ self.domain
+ )
+ }
+}
+impl<'a> Set for Wildcard<'a> {
+ type Elem = Domain<'a>;
+ #[inline]
+ fn bounded_cardinality(&self) -> BoundedCardinality {
+ BoundedCardinality::from_biguint_exact(self.domain_count())
+ }
+ #[inline]
+ fn cardinality(&self) -> Option<Cardinality> {
+ Some(Cardinality::Finite(self.domain_count()))
+ }
+ #[inline]
+ fn contains<Q>(&self, elem: &Q) -> bool
+ where
+ Q: Borrow<Self::Elem> + Eq + ?Sized,
+ {
+ if self.proper_subdomains {
+ let dom2 = elem.borrow();
+ self.domain.label_count() < dom2.label_count()
+ && if self.domain.flag.eq_ignore_case(dom2.flag) {
+ self.domain.same_labels_ignore_case(dom2)
+ } else {
+ self.domain.same_labels(dom2)
+ }
+ } else {
+ self.domain == *elem.borrow()
+ }
+ }
+ #[inline]
+ fn is_proper_subset(&self, val: &Self) -> bool {
+ // A single domain can never be a proper superset.
+ // Proper subdomains cannot be a proper superset
+ // if it has more labels or the same number of labels
+ // as another domain.
+ // In all other cases, we need to recursively check from the TLD
+ // that the labels are the same.
+ val.proper_subdomains
+ && val.domain.label_count() < self.domain.label_count()
+ && if self.domain.flag.eq_ignore_case(val.domain.flag) {
+ val.domain.same_labels_ignore_case(&self.domain)
+ } else {
+ val.domain.same_labels(&self.domain)
+ }
+ }
+ #[inline]
+ fn is_subset(&self, val: &Self) -> bool {
+ self == val || self.is_proper_subset(val)
+ }
+}
+impl SetOrd for Wildcard<'_> {}
+impl<'a> AsRef<Domain<'a>> for Wildcard<'a> {
+ #[inline]
+ fn as_ref(&self) -> &Domain<'a> {
+ &self.domain
+ }
+}
+impl<'a> AsRef<str> for Wildcard<'a> {
+ #[inline]
+ fn as_ref(&self) -> &'a str {
+ self.as_str()
+ }
+}
+impl<'a> Deref for Wildcard<'a> {
+ type Target = Domain<'a>;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &self.domain
+ }
+}
+impl<'a> ParsedDomain<'a> for Wildcard<'a> {
+ type Error = DomainErr;
+ #[allow(unsafe_code, clippy::indexing_slicing)]
+ #[inline]
+ fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> {
+ let value = val.as_bytes().trim_ascii_start();
+ value.first().map_or_else(
+ || Ok(Value::Blank),
+ |byt| {
+ if *byt == b'#' {
+ // SAFETY:
+ // `value` came from `val` with leading ASCII whitespace removed
+ // which is still valid UTF-8. Since the first byte is '#' or '$'
+ // the remaining bytes is still valid UTF-8.
+ let comment = unsafe { str::from_utf8_unchecked(&value[1..]) };
+ Ok(Value::Comment(comment))
+ } else {
+ let (proper_subdomains, val2) = value.get(..2).map_or_else(
+ || (false, value),
+ |fst| {
+ if fst == b"*." {
+ (true, &value[2..])
+ } else {
+ (false, value)
+ }
+ },
+ );
+ Domain::try_from_slice(
+ val2[..val2
+ .into_iter()
+ .try_fold(0, |i, byt2| if *byt2 == b'#' { Err(i) } else { Ok(i + 1) })
+ .map_or_else(convert::identity, convert::identity)]
+ .trim_ascii_end(),
+ )
+ .and_then(|domain| {
+ if proper_subdomains {
+ if domain.len().get() > 251 {
+ Err(DomainErr::InvalidWildcardDomain)
+ } else {
+ Ok(Value::Domain(Self {
+ domain,
+ proper_subdomains: true,
+ }))
+ }
+ } else {
+ Ok(Value::Domain(Self {
+ domain,
+ proper_subdomains,
+ }))
+ }
+ })
+ }
+ },
+ )
+ }
+ #[inline]
+ fn domain(&self) -> &Domain<'a> {
+ &self.domain
+ }
+ #[inline]
+ fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> {
+ write_rpz_line(&mut writer, self.domain(), action, self.proper_subdomains)
+ }
+}
+/// A [`Domain`] in a [response policy zone (RPZ)](https://en.wikipedia.org/wiki/Response_policy_zone)
+/// file.
+#[allow(clippy::exhaustive_enums)]
+#[derive(Clone, Debug)]
+pub enum RpzDomain<'a> {
+ /// An `Adblock` domain.
+ Adblock(Adblock<'a>),
+ /// A `DomainOnly` domain.
+ DomainOnly(DomainOnly<'a>),
+ /// A `Hosts` domain.
+ Hosts(Hosts<'a>),
+ /// A `Wildcard` domain.
+ Wildcard(Wildcard<'a>),
+}
+impl<'a> RpzDomain<'a> {
+ /// Returns `true` iff `self` represents a single [`Domain`].
+ #[inline]
+ #[must_use]
+ pub const fn is_domain(&self) -> bool {
+ match *self {
+ Self::Adblock(ref dom) => !dom.subdomains,
+ Self::DomainOnly(_) | Self::Hosts(_) => true,
+ Self::Wildcard(ref dom) => !dom.proper_subdomains,
+ }
+ }
+ /// Returns `true` iff `self` represents proper subdomains of
+ /// the contained [`Domain`] (i.e., is a [`Wildcard`] such that
+ /// [`Wildcard::is_proper_subdomains`]).
+ #[inline]
+ #[must_use]
+ pub const fn is_proper_subdomains(&self) -> bool {
+ match *self {
+ Self::Adblock(_) | Self::DomainOnly(_) | Self::Hosts(_) => false,
+ Self::Wildcard(ref dom) => dom.proper_subdomains,
+ }
+ }
+ /// Returns `true` iff `self` represents subdomains of
+ /// the contained [`Domain`] (i.e., is an [`Adblock`] such that
+ /// [`Adblock::is_subdomains`]).
+ #[inline]
+ #[must_use]
+ pub const fn is_subdomains(&self) -> bool {
+ match *self {
+ Self::Adblock(ref dom) => dom.subdomains,
+ Self::DomainOnly(_) | Self::Hosts(_) | Self::Wildcard(_) => false,
+ }
+ }
+ /// Returns the count of [`Domain`]s represented by `self`.
+ /// This function is the same as [`RpzDomain::cardinality`]
+ /// except that it returns a `BigUint`.
+ #[inline]
+ #[must_use]
+ pub fn domain_count(&self) -> BigUint {
+ match *self {
+ Self::Adblock(ref dom) => dom.domain_count(),
+ Self::DomainOnly(ref dom) => dom.domain_count().get().into(),
+ Self::Hosts(ref dom) => dom.domain_count().get().into(),
+ Self::Wildcard(ref dom) => dom.domain_count(),
+ }
+ }
+}
+impl PartialEq<RpzDomain<'_>> for RpzDomain<'_> {
+ #[inline]
+ fn eq(&self, other: &RpzDomain<'_>) -> bool {
+ match *self {
+ Self::Adblock(ref dom) => match *other {
+ RpzDomain::Adblock(ref dom2) => dom == dom2,
+ RpzDomain::DomainOnly(ref dom2) => dom == dom2,
+ RpzDomain::Hosts(ref dom2) => dom == dom2,
+ RpzDomain::Wildcard(ref dom2) => dom == dom2,
+ },
+ Self::DomainOnly(ref dom) => match *other {
+ RpzDomain::Adblock(ref dom2) => dom == dom2,
+ RpzDomain::DomainOnly(ref dom2) => dom == dom2,
+ RpzDomain::Hosts(ref dom2) => dom == dom2,
+ RpzDomain::Wildcard(ref dom2) => dom == dom2,
+ },
+ Self::Hosts(ref dom) => match *other {
+ RpzDomain::Adblock(ref dom2) => dom == dom2,
+ RpzDomain::DomainOnly(ref dom2) => dom == dom2,
+ RpzDomain::Hosts(ref dom2) => dom == dom2,
+ RpzDomain::Wildcard(ref dom2) => dom == dom2,
+ },
+ Self::Wildcard(ref dom) => match *other {
+ RpzDomain::Adblock(ref dom2) => dom == dom2,
+ RpzDomain::DomainOnly(ref dom2) => dom == dom2,
+ RpzDomain::Hosts(ref dom2) => dom == dom2,
+ RpzDomain::Wildcard(ref dom2) => dom == dom2,
+ },
+ }
+ }
+}
+impl Eq for RpzDomain<'_> {}
+impl Hash for RpzDomain<'_> {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.domain().hash(state);
+ }
+}
+impl PartialOrd<RpzDomain<'_>> for RpzDomain<'_> {
+ #[inline]
+ fn partial_cmp(&self, other: &RpzDomain<'_>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl Ord for RpzDomain<'_> {
+ /// The total order that is defined follows the following hierarchy:
+ /// 1. Pairwise comparisons of each [`Label`] starting from the TLDs.
+ /// 2. If 1. evaluates as not equivalent, then return the result.
+ /// 3. If both domains represent a single `Domain`, then return the comparison
+ /// of label counts.
+ /// 4. If one domain represents a single `Domain`, then return that that domain is less.
+ /// 5. If the label counts are the same and exactly one domain represents proper subdomains, the other domain is greater.
+ /// 6. Return the inverse of the comparison of label counts.
+ ///
+ /// 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`
+ #[inline]
+ fn cmp(&self, other: &Self) -> Ordering {
+ match *self {
+ Self::Adblock(ref dom) => match *other {
+ Self::Adblock(ref dom2) => dom.cmp(dom2),
+ Self::DomainOnly(ref dom2) => dom.cmp_domain_only(dom2),
+ Self::Hosts(ref dom2) => dom.cmp_hosts(dom2),
+ Self::Wildcard(ref dom2) => dom.cmp_wildcard(dom2),
+ },
+ Self::DomainOnly(ref dom) => match *other {
+ Self::Adblock(ref dom2) => dom.cmp_adblock(dom2),
+ Self::DomainOnly(ref dom2) => dom.cmp(dom2),
+ Self::Hosts(ref dom2) => dom.cmp_hosts(dom2),
+ Self::Wildcard(ref dom2) => dom.cmp_wildcard(dom2),
+ },
+ Self::Hosts(ref dom) => match *other {
+ Self::Adblock(ref dom2) => dom.cmp_adblock(dom2),
+ Self::DomainOnly(ref dom2) => dom.cmp_domain_only(dom2),
+ Self::Hosts(ref dom2) => dom.cmp(dom2),
+ Self::Wildcard(ref dom2) => dom.cmp_wildcard(dom2),
+ },
+ Self::Wildcard(ref dom) => match *other {
+ Self::Adblock(ref dom2) => dom.cmp_adblock(dom2),
+ Self::DomainOnly(ref dom2) => dom.cmp_domain_only(dom2),
+ Self::Hosts(ref dom2) => dom.cmp_hosts(dom2),
+ Self::Wildcard(ref dom2) => dom.cmp(dom2),
+ },
+ }
+ }
+}
+impl Display for RpzDomain<'_> {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Adblock(ref dom) => dom.fmt(f),
+ Self::DomainOnly(ref dom) => dom.fmt(f),
+ Self::Hosts(ref dom) => dom.fmt(f),
+ Self::Wildcard(ref dom) => dom.fmt(f),
+ }
+ }
+}
+impl<'a> Set for RpzDomain<'a> {
+ type Elem = Domain<'a>;
+ #[inline]
+ fn bounded_cardinality(&self) -> BoundedCardinality {
+ BoundedCardinality::from_biguint_exact(self.domain_count())
+ }
+ #[inline]
+ fn cardinality(&self) -> Option<Cardinality> {
+ Some(Cardinality::Finite(self.domain_count()))
+ }
+ #[inline]
+ fn contains<Q>(&self, elem: &Q) -> bool
+ where
+ Q: Borrow<Self::Elem> + Eq + ?Sized,
+ {
+ match *self {
+ Self::Adblock(ref dom) => dom.contains(elem),
+ Self::DomainOnly(ref dom) => dom.contains(elem),
+ Self::Hosts(ref dom) => dom.contains(elem),
+ Self::Wildcard(ref dom) => dom.contains(elem),
+ }
+ }
+ #[inline]
+ fn is_proper_subset(&self, val: &Self) -> bool {
+ /// Helper function that verifies all labels are the same.
+ #[inline]
+ fn helper(left: &Domain<'_>, right: &Domain<'_>) -> bool {
+ left.label_count() <= right.label_count()
+ && if left.flag.eq_ignore_case(right.flag) {
+ left.same_labels_ignore_case(right)
+ } else {
+ left.same_labels(right)
+ }
+ }
+ match *val {
+ Self::Adblock(ref dom) => {
+ dom.subdomains
+ && match *self {
+ Self::Adblock(ref dom2) => {
+ dom.domain.label_count() < dom2.domain.label_count()
+ && helper(&dom.domain, &dom2.domain)
+ }
+ Self::DomainOnly(ref dom2) => {
+ match dom.domain.label_count().cmp(&dom2.domain.label_count()) {
+ Ordering::Less => helper(&dom.domain, &dom2.domain),
+ Ordering::Equal => {
+ dom.domain == dom2.domain || helper(&dom.domain, &dom2.domain)
+ }
+ Ordering::Greater => false,
+ }
+ }
+ Self::Hosts(ref dom2) => {
+ match dom.domain.label_count().cmp(&dom2.domain.label_count()) {
+ Ordering::Less => helper(&dom.domain, &dom2.domain),
+ Ordering::Equal => {
+ dom.domain == dom2.domain || helper(&dom.domain, &dom2.domain)
+ }
+ Ordering::Greater => false,
+ }
+ }
+ Self::Wildcard(ref dom2) => {
+ match dom.domain.label_count().cmp(&dom2.domain.label_count()) {
+ Ordering::Less => helper(&dom.domain, &dom2.domain),
+ Ordering::Equal => {
+ dom.domain == dom2.domain || helper(&dom.domain, &dom2.domain)
+ }
+ Ordering::Greater => false,
+ }
+ }
+ }
+ }
+ Self::DomainOnly(_) | Self::Hosts(_) => false,
+ Self::Wildcard(ref dom) => {
+ dom.proper_subdomains
+ && match *self {
+ Self::Adblock(ref dom2) => {
+ dom.domain.label_count() < dom2.domain.label_count()
+ && helper(&dom.domain, &dom2.domain)
+ }
+ Self::DomainOnly(ref dom2) => {
+ dom.domain.label_count() < dom2.domain.label_count()
+ && helper(&dom.domain, &dom2.domain)
+ }
+ Self::Hosts(ref dom2) => {
+ dom.domain.label_count() < dom2.domain.label_count()
+ && helper(&dom.domain, &dom2.domain)
+ }
+ Self::Wildcard(ref dom2) => {
+ dom.domain.label_count() < dom2.domain.label_count()
+ && helper(&dom.domain, &dom2.domain)
+ }
+ }
+ }
+ }
+ }
+ #[inline]
+ fn is_subset(&self, val: &Self) -> bool {
+ self == val || self.is_proper_subset(val)
+ }
+}
+impl SetOrd for RpzDomain<'_> {}
+impl<'a> AsRef<Domain<'a>> for RpzDomain<'a> {
+ #[inline]
+ fn as_ref(&self) -> &Domain<'a> {
+ self.domain()
+ }
+}
+impl<'a> AsRef<str> for RpzDomain<'a> {
+ #[inline]
+ fn as_ref(&self) -> &'a str {
+ self.as_str()
+ }
+}
+impl<'a> Deref for RpzDomain<'a> {
+ type Target = Domain<'a>;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.domain()
+ }
+}
+impl<'a: 'b, 'b> From<Adblock<'a>> for RpzDomain<'b> {
+ #[inline]
+ fn from(value: Adblock<'a>) -> Self {
+ Self::Adblock(value)
+ }
+}
+impl<'a: 'b, 'b> From<DomainOnly<'a>> for RpzDomain<'b> {
+ #[inline]
+ fn from(value: DomainOnly<'a>) -> Self {
+ Self::DomainOnly(value)
+ }
+}
+impl<'a: 'b, 'b> From<Hosts<'a>> for RpzDomain<'b> {
+ #[inline]
+ fn from(value: Hosts<'a>) -> Self {
+ Self::Hosts(value)
+ }
+}
+impl<'a: 'b, 'b> From<Wildcard<'a>> for RpzDomain<'b> {
+ #[inline]
+ fn from(value: Wildcard<'a>) -> Self {
+ Self::Wildcard(value)
+ }
+}
+impl<'a> ParsedDomain<'a> for RpzDomain<'a> {
+ type Error = DomainErr;
+ #[inline]
+ fn parse_value<'b: 'a>(value: &'b str) -> Result<Value<'a, Self>, Self::Error> {
+ DomainOnly::parse_value(value).map_or_else(
+ |_| {
+ Hosts::parse_value(value).map_or_else(
+ |_| {
+ Wildcard::parse_value(value).map_or_else(
+ |_| {
+ Adblock::parse_value(value).map(|val| match val {
+ Value::Domain(dom) => Value::Domain(Self::Adblock(dom)),
+ Value::Comment(com) => Value::Comment(com),
+ Value::Blank => Value::Blank,
+ })
+ },
+ |val| {
+ Ok(match val {
+ Value::Domain(dom) => Value::Domain(Self::Wildcard(dom)),
+ Value::Comment(com) => Value::Comment(com),
+ Value::Blank => Value::Blank,
+ })
+ },
+ )
+ },
+ |val| {
+ Ok(match val {
+ Value::Domain(dom) => Value::Domain(Self::Hosts(dom)),
+ Value::Comment(com) => Value::Comment(com),
+ Value::Blank => Value::Blank,
+ })
+ },
+ )
+ },
+ |val| {
+ Ok(match val {
+ Value::Domain(dom) => Value::Domain(Self::DomainOnly(dom)),
+ Value::Comment(com) => Value::Comment(com),
+ Value::Blank => Value::Blank,
+ })
+ },
+ )
+ }
+ #[inline]
+ fn domain(&self) -> &Domain<'a> {
+ match *self {
+ Self::Adblock(ref dom) => dom.domain(),
+ Self::DomainOnly(ref dom) => dom.domain(),
+ Self::Hosts(ref dom) => dom.domain(),
+ Self::Wildcard(ref dom) => dom.domain(),
+ }
+ }
+ #[inline]
+ fn write_to_rpz<W: Write>(&self, action: RpzAction, writer: W) -> Result<(), Error> {
+ match *self {
+ Self::Adblock(ref dom) => dom.write_to_rpz(action, writer),
+ Self::DomainOnly(ref dom) => dom.write_to_rpz(action, writer),
+ Self::Hosts(ref dom) => dom.write_to_rpz(action, writer),
+ Self::Wildcard(ref dom) => dom.write_to_rpz(action, writer),
+ }
+ }
+}
+#[cfg(test)]
+mod tests {
+ use super::{
+ Adblock, Domain, DomainErr, DomainOnly, Hosts, ParsedDomain, RpzDomain, Value, Wildcard,
+ };
+ use core::cmp::Ordering;
+ use num_bigint::BigUint;
+ use superset_map::SupersetSet;
+ #[test]
+ fn test_dom_parse() {
+ // Test Ipv4Addr is error.
+ assert!(Domain::try_from("1.1.1.1").map_or_else(|e| e == DomainErr::Ipv4, |_| false));
+ // Test empty is error.
+ assert!(Domain::try_from("").map_or_else(|e| e == DomainErr::Empty, |_| false));
+ // Test empty label is error.
+ assert!(Domain::try_from("a..com").map_or_else(|e| e == DomainErr::EmptyLabel, |_| false));
+ // Test label too long.
+ let val = "www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com";
+ // 4 + 64 + 4
+ assert!(val.len() == 72);
+ assert!(Domain::try_from(val).map_or_else(|e| e == DomainErr::LabelLenExceeds63, |_| false));
+ assert!(Domain::try_from(
+ "www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com"
+ )
+ .map_or(false, |d| d.len().get() == 71));
+ // Test domain too long.
+ assert!(Domain::try_from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").map_or_else(|e| e == DomainErr::LenExceeds253(254), |_| false));
+ assert!(Domain::try_from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").map_or(false, |d| d.len().get() == 253 ));
+ // Test max labels.
+ assert!(Domain::try_from("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.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 == DomainErr::LenExceeds253(255), |_| false));
+ assert!(Domain::try_from("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.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, |d| d.label_count().get() == 127 && d.len().get() == 253));
+ assert!(Domain::try_from("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.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, |d| d.label_count().get() == 127 && d.len().get() == 253));
+ // Test removal of trailing '.'.
+ assert!(Domain::try_from("com").map_or(false, |d| d.value == b"com"));
+ // Test case-insensitivity.
+ assert!(Domain::try_from("wwW.ExAMple.COm")
+ .map_or(false, |d| Domain::try_from("www.example.com")
+ .map_or(false, |d2| d == d2 && d.cmp(&d2) == Ordering::Equal)));
+ // Test valid bytes
+ let mut vec = Vec::new();
+ let mut counter = 0;
+ for i in 0u8..=255 {
+ vec.push(i);
+ match i {
+ b'!'
+ | b'$'
+ | b'&'..=b')'
+ | b'+'..=b'-'
+ | b'0'..=b'9'
+ | b';'
+ | b'='
+ | b'A'..=b'Z'
+ | b'_'..=b'{'
+ | b'}'..=b'~' => {
+ counter += 1;
+ assert!(Domain::try_from_slice(vec.as_slice())
+ .map_or(false, |d| d.value.len() == 1 && d.value[0] == i))
+ }
+ b'.' => {
+ vec.push(i);
+ vec[0] = b'a';
+ assert!(
+ Domain::try_from_slice(vec.as_slice())
+ .map_or(false, |d| d.value.len() == 1 && d.value[0] == b'a')
+ && vec.pop() == Some(i)
+ )
+ }
+ _ => assert!(Domain::try_from_slice(vec.as_slice())
+ .map_or_else(|e| e == DomainErr::InvalidByte(i), |_| false)),
+ }
+ vec.pop();
+ }
+ assert!(counter == 78);
+ }
+ #[test]
+ fn test_dom_into_iter() {
+ assert!(Domain::try_from("www.example.com").map_or(false, |d| {
+ let mut iter = d.into_iter();
+ if iter.len() != 3 {
+ return false;
+ }
+ let Some(l) = iter.next() else {
+ return false;
+ };
+ if l.value != "com" {
+ return false;
+ }
+ if iter.len() != 2 {
+ return false;
+ }
+ let Some(l) = iter.next() else { return false };
+ if l.value != "example" {
+ return false;
+ }
+ if iter.len() != 1 {
+ return false;
+ }
+ let Some(l) = iter.next() else {
+ return false;
+ };
+ if iter.len() != 0 {
+ return false;
+ }
+ if l.value != "www" {
+ return false;
+ }
+ iter.next().is_none()
+ }));
+ assert!(Domain::try_from("www.example.com").map_or(false, |d| {
+ let mut iter = d.into_iter();
+ if iter.len() != 3 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "www" {
+ return false;
+ }
+ if iter.len() != 2 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "example" {
+ return false;
+ }
+ if iter.len() != 1 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "com" {
+ return false;
+ }
+ if iter.len() != 0 {
+ return false;
+ }
+ iter.next_back().is_none()
+ }));
+ assert!(Domain::try_from("www.example.com").map_or(false, |d| {
+ let mut iter = d.into_iter();
+ if iter.len() != 3 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "www" {
+ return false;
+ }
+ if iter.len() != 2 {
+ return false;
+ }
+ let Some(l) = iter.next() else { return false };
+ if l.value != "com" {
+ return false;
+ }
+ if iter.len() != 1 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "example" {
+ return false;
+ }
+ if iter.len() != 0 {
+ return false;
+ }
+ iter.next().is_none() && iter.next_back().is_none()
+ }));
+ }
+ #[test]
+ fn test_dom_iter() {
+ assert!(Domain::try_from("www.example.com").map_or(false, |d| {
+ let mut iter = d.iter();
+ if iter.len() != 3 {
+ return false;
+ }
+ let Some(l) = iter.next() else {
+ return false;
+ };
+ if l.value != "com" {
+ return false;
+ }
+ if iter.len() != 2 {
+ return false;
+ }
+ let Some(l) = iter.next() else { return false };
+ if l.value != "example" {
+ return false;
+ }
+ if iter.len() != 1 {
+ return false;
+ }
+ let Some(l) = iter.next() else {
+ return false;
+ };
+ if iter.len() != 0 {
+ return false;
+ }
+ if l.value != "www" {
+ return false;
+ }
+ iter.next().is_none()
+ }));
+ assert!(Domain::try_from("www.example.com").map_or(false, |d| {
+ let mut iter = d.iter();
+ if iter.len() != 3 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "www" {
+ return false;
+ }
+ if iter.len() != 2 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "example" {
+ return false;
+ }
+ if iter.len() != 1 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "com" {
+ return false;
+ }
+ if iter.len() != 0 {
+ return false;
+ }
+ iter.next_back().is_none()
+ }));
+ assert!(Domain::try_from("www.example.com").map_or(false, |d| {
+ let mut iter = d.iter();
+ if iter.len() != 3 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "www" {
+ return false;
+ }
+ if iter.len() != 2 {
+ return false;
+ }
+ let Some(l) = iter.next() else { return false };
+ if l.value != "com" {
+ return false;
+ }
+ if iter.len() != 1 {
+ return false;
+ }
+ let Some(l) = iter.next_back() else {
+ return false;
+ };
+ if l.value != "example" {
+ return false;
+ }
+ if iter.len() != 0 {
+ return false;
+ }
+ iter.next().is_none() && iter.next_back().is_none()
+ }));
+ }
+ #[test]
+ fn test_adblock_parse() {
+ // Test subdomains.
+ assert!(
+ Adblock::parse_value("||www.example.com").map_or(false, |val| match val {
+ Value::Domain(ref dom) => dom.subdomains && dom.domain.value == b"www.example.com",
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
+ // Test whitespace and '^' removal.
+ assert!(
+ Adblock::parse_value(" \t\t ||\t\t \twww.example.com \t\t ^ \t\t ").map_or(
+ false,
+ |val| match val {
+ Value::Domain(ref dom) =>
+ dom.subdomains && dom.domain.value == b"www.example.com",
+ Value::Comment(_) | Value::Blank => false,
+ }
+ )
+ );
+ assert!(
+ Adblock::parse_value("\t\t \twww.example.com \t\t \t\t ").map_or(false, |val| {
+ match val {
+ Value::Domain(ref dom) => {
+ !dom.subdomains && dom.domain.value == b"www.example.com"
+ }
+ Value::Comment(_) | Value::Blank => false,
+ }
+ })
+ );
+ assert!(Adblock::parse_value("www .example.com")
+ .map_or_else(|err| err == 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,
+ }
+ })
+ );
+ // Test comment
+ assert!(
+ Adblock::parse_value(" \t\t #hi").map_or(false, |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 {
+ 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)));
+ }
+ #[test]
+ fn test_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 {
+ Value::Domain(ref dom) => dom.domain.value == 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 == DomainErr::InvalidByte(b' '), |_| false)
+ );
+ // Test case-insensitivity.
+ assert!(
+ DomainOnly::parse_value("www.ExAMPle.CoM").map_or(false, |val| match val {
+ Value::Domain(ref dom) =>
+ DomainOnly::parse_value("www.example.com").map_or(false, |val2| match val2 {
+ Value::Domain(ref dom2) => dom.cmp(dom2).is_eq(),
+ Value::Comment(_) | Value::Blank => false,
+ }),
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
+ // Test comment.
+ assert!(
+ DomainOnly::parse_value(" \t\t \t\t \t # hi").map_or(false, |val| match val {
+ Value::Comment(com) => com == " hi",
+ Value::Domain(_) | Value::Blank => false,
+ })
+ );
+ // Test blank.
+ assert!(DomainOnly::parse_value(" \t\t \t\t \t ")
+ .map_or(false, |val| matches!(val, Value::Blank)));
+ }
+ #[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.value == 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 == DomainErr::InvalidByte(b' '), |_| false)
+ );
+ assert!(Hosts::parse_value("::1\twww .example.com")
+ .map_or_else(|e| e == DomainErr::InvalidByte(b' '), |_| false));
+ // Test invalid IP
+ assert!(Hosts::parse_value("::2 www.example.com")
+ .map_or_else(|e| e == DomainErr::InvalidHostsIP, |_| false));
+ assert!(Hosts::parse_value(":2 www.example.com")
+ .map_or_else(|e| e == DomainErr::InvalidHostsIP, |_| false));
+ assert!(Hosts::parse_value("www.example.com")
+ .map_or_else(|e| e == DomainErr::InvalidHostsIP, |_| false));
+ assert!(Hosts::parse_value("10.4.2.256 www.example.com")
+ .map_or_else(|e| e == DomainErr::InvalidHostsIP, |_| false));
+ // 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
+ {
+ Value::Domain(ref dom2) => dom.cmp(dom2).is_eq(),
+ Value::Comment(_) | Value::Blank => false,
+ }),
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
+ // Test comment.
+ assert!(
+ Hosts::parse_value(" \t\t \t\t \t # hi").map_or(false, |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)));
+ }
+ #[test]
+ fn test_wildcard_parse_value() {
+ // Test bad asterisk.
+ assert!(Wildcard::parse_value("*")
+ .map_or_else(|e| e == DomainErr::InvalidByte(b'*'), |_| false));
+ assert!(Wildcard::parse_value("www*.example.com")
+ .map_or_else(|e| e == DomainErr::InvalidByte(b'*'), |_| false));
+ assert!(Wildcard::parse_value("www.*.com")
+ .map_or_else(|e| e == DomainErr::InvalidByte(b'*'), |_| false));
+ assert!(
+ Wildcard::parse_value("*..com").map_or_else(|e| e == DomainErr::EmptyLabel, |_| false)
+ );
+ assert!(Wildcard::parse_value("www.com*")
+ .map_or_else(|e| e == DomainErr::InvalidByte(b'*'), |_| false));
+ assert!(Wildcard::parse_value("ww*w.com")
+ .map_or_else(|e| e == DomainErr::InvalidByte(b'*'), |_| false));
+ // Test case-insensitivity.
+ assert!(
+ Wildcard::parse_value("*.wWw.ExamPLE.com").map_or(false, |val| match val {
+ Value::Domain(ref dom) =>
+ Wildcard::parse_value("*.www.example.com").map_or(false, |val2| match val2 {
+ Value::Domain(ref dom2) =>
+ dom.cmp(dom2).is_eq()
+ && dom == dom2
+ && dom.proper_subdomains
+ && dom2.proper_subdomains,
+ Value::Comment(_) | Value::Blank => false,
+ }),
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
+ // Test proper subdomains.
+ assert!(
+ Wildcard::parse_value("*.www.example.com").map_or(false, |val| match val {
+ Value::Domain(ref dom) =>
+ dom.domain.value == b"www.example.com" && dom.proper_subdomains,
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
+ // Test comment.
+ assert!(
+ Wildcard::parse_value(" \t\t \t\t \t*.www.example.com#asdflkj asdf alskdfj ")
+ .map_or(false, |val| match val {
+ Value::Domain(ref dom) =>
+ dom.domain.value == b"www.example.com" && dom.proper_subdomains,
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
+ assert!(
+ Wildcard::parse_value(" \t\t \t\t \twww.example.com #asdflkj asdf alskdfj ")
+ .map_or(false, |val| match val {
+ Value::Domain(ref dom) =>
+ dom.domain.value == b"www.example.com" && !dom.proper_subdomains,
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
+ // Test whitespace removal.
+ assert!(
+ Wildcard::parse_value(" \t\t *.www.example.com \t\t \t ").map_or(false, |val| {
+ match val {
+ Value::Domain(ref dom) => {
+ dom.domain.value == b"www.example.com" && dom.proper_subdomains
+ }
+ Value::Comment(_) | Value::Blank => false,
+ }
+ })
+ );
+ assert!(
+ Wildcard::parse_value("\t\t \twww.example.com \t\t \t\t ").map_or(false, |val| {
+ match val {
+ Value::Domain(ref dom) => {
+ dom.domain.value == b"www.example.com" && !dom.proper_subdomains
+ }
+ Value::Comment(_) | Value::Blank => false,
+ }
+ })
+ );
+ assert!(Wildcard::parse_value("www .example.com")
+ .map_or_else(|e| e == DomainErr::InvalidByte(b' '), |_| false));
+ // 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 == DomainErr::InvalidWildcardDomain, |_| false));
+ // 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 {
+ Value::Domain(ref dom) => dom.domain.label_count().get() == 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 {
+ Value::Comment(com) => com == " hi",
+ Value::Domain(_) | Value::Blank => false,
+ })
+ );
+ // Test blank.
+ assert!(Wildcard::parse_value(" \t\t \t\t \t ")
+ .map_or(false, |val| matches!(val, Value::Blank)));
+ }
+ #[test]
+ fn test_rpz_parse_value() {
+ assert!(
+ RpzDomain::parse_value("*.www.example.com").map_or(false, |val| {
+ let dom = val.unwrap_domain();
+ dom.is_proper_subdomains() && dom.domain().value == b"www.example.com"
+ })
+ );
+ assert!(
+ RpzDomain::parse_value("||www.example.com").map_or(false, |val| {
+ let dom = val.unwrap_domain();
+ dom.is_subdomains() && dom.domain().value == 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().value == b"www.example.com"
+ })
+ );
+ assert!(
+ RpzDomain::parse_value("www.example.com").map_or(false, |val| {
+ let dom = val.unwrap_domain();
+ !(dom.is_subdomains() || dom.is_proper_subdomains())
+ && dom.domain().value == b"www.example.com"
+ })
+ );
+ // Test case-insensitivity.
+ assert!(
+ RpzDomain::parse_value("*.Www.ExaMPle.COm").map_or(false, |val| {
+ let dom = val.unwrap_domain();
+ RpzDomain::parse_value("*.www.example.com").map_or(false, |val2| {
+ let dom2 = val2.unwrap_domain();
+ dom.is_proper_subdomains()
+ && dom2.is_proper_subdomains()
+ && dom == dom2
+ && dom.cmp(&dom2).is_eq()
+ })
+ })
+ );
+ // Test comment.
+ assert!(
+ RpzDomain::parse_value(" \t\t \t\t \t # hi").map_or(false, |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 {
+ Value::Comment(com) => com == " hi",
+ Value::Domain(_) | Value::Blank => false,
+ })
+ );
+ // Test 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> {
+ "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| {
+ let cur = if slice.as_bytes()[0] == b'|' {
+ RpzDomain::Adblock(Adblock::parse_value(slice).expect("Bug in Adblock::parse_value").unwrap_domain())
+ } else {
+ RpzDomain::Wildcard(Wildcard::parse_value(slice).expect("Bug in Wildcard::parse_value").unwrap_domain())
+ };
+ if prev < cur && cur > prev && prev == prev && cur == cur {
+ Ok(cur)
+ } else {
+ Err("PartialEq or Ord are not correctly implemented for RpzDomain.")
+ }
+ }).map(|_| ())
+ }
+ #[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| {
+ doms.insert(if slice.as_bytes()[0] == b'|' {
+ RpzDomain::Adblock(Adblock::parse_value(slice).expect("Bug in Adblock::parse_value").unwrap_domain())
+ } else {
+ RpzDomain::Wildcard(Wildcard::parse_value(slice).expect("Bug in Wildcard::parse_value").unwrap_domain())
+ });
+ doms
+ }).into_iter();
+ assert!(iter.next().map_or(false, |d| {
+ d.domain().value == b"aawww.abc" && d.is_subdomains()
+ }));
+ assert!(iter.next().map_or(false, |d| {
+ d.domain().value == b"abc.abc" && d.is_domain()
+ }));
+ assert!(iter.next().map_or(false, |d| {
+ d.domain().value == b"abc.abc" && d.is_proper_subdomains()
+ }));
+ assert!(iter.next().map_or(false, |d| {
+ d.domain().value == b"com" && d.is_proper_subdomains()
+ }));
+ assert!(iter.next().map_or(false, |d| {
+ d.domain().value == b"NeT" && d.is_proper_subdomains()
+ }));
+ assert!(iter.next().is_none());
+ }
+ #[test]
+ fn test_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.label_count().get() == 125 && dom.domain_count() == BigUint::new(vec![146069]) }));
+ // 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.label_count().get() == 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.
+ // The other check is to ensure that IPv4 address subdomains are not counted.
+ assert!(Wildcard::parse_value("*.a").map_or(false, |val| {
+ let val = val.unwrap_domain().domain_count();
+ val == 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,
+ ]) && Wildcard::parse_value("*.1").map_or(false, |val2| {
+ val2.unwrap_domain().domain_count() == (val - BigUint::new(vec![256u32.pow(3)]))
+ })
+ }));
+ }
+}
diff --git a/src/dom_count_auto_gen.rs b/src/dom_count_auto_gen.rs
@@ -0,0 +1,1532 @@
+use crate::dom::Domain;
+use num_bigint::BigUint;
+/// The count of proper subdomains for both `Adblock` and `Wildcard` `Domain` when subdomains
+/// and proper subdomains are represented respectively.
+#[allow(
+ clippy::arithmetic_side_effects,
+ clippy::as_conversions,
+ clippy::implicit_return,
+ clippy::indexing_slicing,
+ clippy::too_many_lines,
+ clippy::unreadable_literal,
+ clippy::unseparated_literal_suffix
+)]
+#[inline]
+pub fn proper_subdomain_count(dom: &Domain<'_>) -> BigUint {
+ /// Returns how many proper subdomains are IPv4 addresses.
+ /// We need to calculate this so that we can subtract this value
+ /// from the cached cardinalities. Note that we don't have to worry
+ /// about the `Domain` itself since an IPv4 address can't be parsed into
+ /// a `Domain`.
+ #[allow(clippy::cast_lossless)]
+ #[inline]
+ fn ip_count(dom: &Domain<'_>) -> u32 {
+ // `Domain`s that have 4 or more `Label`s can't be an IPv4 address.
+ // Also `Domain`s must have at least one `Label`, so
+ // `0 < 4 - dom.label_count < 4` and `256^3 <= u32::MAX`.
+ if dom.label_count().get() < 4 {
+ dom.into_iter()
+ .try_fold((), |(), label| {
+ // Only a sequence of decimal numbers between 0 and 255 without leading
+ // 0s can be an IPv4 address. For `Domain`s that have such `Label`s,
+ // the total number of IPv4 addresses is simply 256^(4- label count).
+ // Note that `Label`s always have a length of at least 1 and
+ // any `Label` longer than 3 cannot be a valid `u8` without leading
+ // 0s.
+ match label.len() {
+ 1 => match label.as_str().as_bytes()[0] {
+ b'0'..=b'9' => Ok(()),
+ _ => Err(()),
+ },
+ 2 => label.as_str().parse::<u8>().map_or(Err(()), |val| {
+ if val < 10 {
+ Err(())
+ } else {
+ Ok(())
+ }
+ }),
+ 3 => label.as_str().parse::<u8>().map_or(Err(()), |val| {
+ if val < 100 {
+ Err(())
+ } else {
+ Ok(())
+ }
+ }),
+ _ => Err(()),
+ }
+ })
+ .map_or(0, |()| 256u32.pow(4 - dom.label_count().get() as u32))
+ } else {
+ 0
+ }
+ }
+ // The commented out code at the end of the function was used to calculate the cardinalities
+ // for each possible value of domain length allowing IPv4 addresses; however it takes
+ // as much as 16 seconds to calculate for a give value of n, so we cache the results
+ // instead.
+ // The array is ordered based on the descending order of length of `Domain`s
+ // (e.g., index 0 corresponds to the number of subdomains for `Domain`s of length 251 which
+ // is the max length of a `Domain` with at least one proper subdomain).
+ [
+ BigUint::new(vec![52]),
+ BigUint::new(vec![2756]),
+ BigUint::new(vec![146068]),
+ BigUint::new(vec![7738900]),
+ BigUint::new(vec![410018388]),
+ BigUint::new(vec![248542548, 5]),
+ BigUint::new(vec![4180397652, 267]),
+ BigUint::new(vec![2671623764, 14197]),
+ BigUint::new(vec![4117795412, 752210]),
+ BigUint::new(vec![862478932, 39853246]),
+ BigUint::new(vec![1276228180, 2111483772]),
+ BigUint::new(vec![3838587476, 200375265, 26]),
+ BigUint::new(vec![3977409108, 4252552993, 1379]),
+ BigUint::new(vec![2704896596, 3919002822, 73113]),
+ BigUint::new(vec![3882512980, 4014107452, 3873682]),
+ BigUint::new(vec![3242881620, 204873911, 205233436]),
+ BigUint::new(vec![1153331796, 343698866, 2283635595, 2]),
+ BigUint::new(vec![969830996, 2755980681, 572170738, 134]),
+ BigUint::new(vec![3030282836, 2269546517, 2473041289, 7106]),
+ BigUint::new(vec![1847489108, 3629376584, 3732202808, 376516]),
+ BigUint::new(vec![241070676, 1801323295, 550145915, 19948419]),
+ BigUint::new(vec![1230926420, 3223519493, 3638801565, 1056896671]),
+ BigUint::new(vec![3529405012, 3593787233, 3076904220, 161369882, 13]),
+ BigUint::new(vec![2724098644, 2312631537, 1324349926, 3220318693, 690]),
+ BigUint::new(vec![3059642964, 2191098099, 1231948975, 4049114113, 36596]),
+ BigUint::new(vec![106852948, 2265707158, 4078524026, 53383894, 1938960]),
+ BigUint::new(vec![
+ 1449030228, 4120606714, 1266689161, 2876498924, 102728961,
+ ]),
+ BigUint::new(vec![
+ 3596513876, 1375839570, 3073178857, 2030051240, 1147764631, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2347365293, 2334837610, 1737538116, 601157403, 67,
+ ]),
+ BigUint::new(vec![
+ 375288404, 333124604, 2043982089, 2641118257, 749632597, 3557,
+ ]),
+ BigUint::new(vec![
+ 375288404, 1946521181, 65357692, 56864761, 1521603317, 188464,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2577463837, 2311486239, 2856163489, 2140150568, 9985119,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3318986961, 3336800178, 1153613668, 1432641031, 529026360,
+ ]),
+ BigUint::new(vec![
+ 375288404, 1672763489, 1653117627, 2349982024, 1101569468, 2258793175, 6,
+ ]),
+ BigUint::new(vec![
+ 375288404, 1872985649, 1777688160, 1798349612, 2929927110, 3232695082, 345,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3990328753, 2308241830, 964880313, 3479391898, 2089547876, 18318,
+ ]),
+ BigUint::new(vec![
+ 375288404, 4244638193, 2014962046, 1954035381, 2572106657, 1878726949, 970540,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3016518897, 1468302259, 1459760780, 1145312287, 191860765, 51420664,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3918013937, 741117515, 1423741278, 32256809, 296378773, 2724342633,
+ ]),
+ BigUint::new(vec![
+ 375288404, 4113421809, 3220678635, 3913218978, 1104050882, 3913619510, 2605770681, 33,
+ ]),
+ BigUint::new(vec![
+ 375288404, 1022831089, 4149936985, 2644026415, 3253425148, 4171545929, 2287985434, 1780,
+ ]),
+ BigUint::new(vec![
+ 375288404, 797178353, 1019922958, 1674344141, 3250454247, 3816775168, 1072247613, 94335,
+ ]),
+ BigUint::new(vec![
+ 375288404, 151210481, 2544744706, 1216969582, 3194279504, 3075836706, 2933426700,
+ 4998020,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2071579121, 679124811, 24458279, 118705999, 1933531958, 2136634151,
+ 264802508,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3915909617, 137490366, 129739371, 476554331, 2789132808, 1650159256,
+ 1144725629, 3,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2111769073, 3809283612, 3723310513, 888766089, 765431967, 3639728901,
+ 281019137, 173,
+ ]),
+ BigUint::new(vec![
+ 375288404, 4201646577, 3368784016, 2790098399, 2277185150, 153774588, 196277263,
+ 1124283864, 9169,
+ ]),
+ BigUint::new(vec![
+ 375288404, 1880099313, 3892329276, 3689814422, 1420707258, 554100642, 1903824923,
+ 61312066, 485801,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2718173681, 3915736505, 1948017691, 3311844270, 2449773636, 1831131280,
+ 1521446241, 25738454,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2886994417, 2292494843, 1109493842, 1279543652, 1582639857, 944194272,
+ 699053385, 1363661279,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3705932273, 705482963, 76809839, 2528970701, 3527071483, 2583007984,
+ 3796830889, 3529309406, 16,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3529771505, 1276023335, 1558249304, 474250754, 3711984670, 3025890941,
+ 1857748306, 1031405210, 891,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2594441713, 4254079775, 3418696835, 1548693023, 2768765240, 3899935355,
+ 1980342079, 933958820, 47219,
+ ]),
+ BigUint::new(vec![
+ 375288404, 631507441, 4097520258, 1103161534, 2113861360, 1991546256, 3660681902,
+ 2012204487, 3414681798, 2501743,
+ ]),
+ BigUint::new(vec![
+ 375288404, 245631473, 491504859, 3208401305, 1474266946, 2723061964, 2310073485,
+ 1453991315, 2791012792, 132546076,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2661550577, 2406104814, 862968291, 1899078236, 346491611, 1241634869,
+ 4148522640, 573571521, 2727519367, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 849611249, 351847671, 1257821523, 3615257673, 702995988, 5240717,
+ 3567916871, 3159692503, 2694515012, 86,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2191788529, 1679608494, 2906887961, 3277625757, 3033747662, 413021044,
+ 1822896059, 856201021, 2772913513, 4589,
+ ]),
+ BigUint::new(vec![
+ 375288404, 3533965809, 2556505512, 1816528392, 1947652842, 1037141563, 274775137,
+ 1149398125, 2668033105, 838441812, 243166,
+ ]),
+ BigUint::new(vec![
+ 375288404, 1386482161, 1234596285, 804514535, 1131547557, 1232842259, 1405663093,
+ 4235442216, 2871548155, 3106883214, 12883303,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3863765183, 3150246073, 1204630827, 2075041789, 1483442067,
+ 838823512, 295416753, 3293438507, 682576435,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3121791326, 3784088765, 1222191727, 211278684, 4204580296,
+ 1868812834, 1473418029, 2104247738, 1804168085, 8,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 2471685663, 4103126092, 1640721307, 2924547633, 3719386775,
+ 3358136588, 1785095480, 1506810521, 461696225, 446,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3097994655, 2113279511, 1888061798, 1596718373, 3213304653,
+ 152654476, 2978780767, 2083463032, 1742258559, 16363,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1860567763, 1130544223, 2153567102, 625138177, 3206578786,
+ 1106733064, 3948231794, 1002658338, 2817930503, 866822,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 147208035, 1175109684, 660928834, 1730986888, 3083362323,
+ 412094060, 2997340098, 3976540641, 3212428124, 45540257,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1325126451, 3929886196, 1506154326, 283097260, 2162374086,
+ 1083031484, 2385562882, 1083286932, 3864853917, 2393119200,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3546949299, 3466780721, 1591926664, 3866228006, 3769344032,
+ 3294539415, 761827863, 3320756801, 1055962996, 1194022465, 29,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 4239835891, 2374590398, 341723740, 1768865321, 3009735295,
+ 640743363, 760884641, 1286523815, 1425636762, 1404846044, 1538,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1185904115, 3103587562, 1485183809, 1874654482, 3208312786,
+ 2785384481, 313087062, 1389949551, 3463745521, 1039435803, 80821,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 2965606131, 1397412449, 3110808017, 2226053202, 3474793386,
+ 257483838, 2073200290, 2070753250, 1854563799, 2728715379, 4245916,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1130168051, 2123766638, 316945545, 132357966, 3454088252,
+ 3364776445, 2707843191, 452091523, 2613197394, 888667529, 223041660,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 2526860019, 2712686141, 2768201501, 3657483061, 763339927,
+ 2809075408, 4010198047, 1079127981, 404473813, 1744216475, 3125773904, 2,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1186898675, 2387441384, 2136278685, 2507538597, 4291433051,
+ 4120455470, 4126082953, 4124167878, 1816064621, 56141297, 1164585958, 143,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 4136891123, 3213626288, 3178750640, 3483039856, 2585028864,
+ 307761499, 612469337, 2705587210, 2331449101, 2528560486, 2004283004, 7524,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1959162611, 3492710176, 4176981138, 187691056, 2590522248,
+ 1426342635, 2139644698, 2023712095, 4279998285, 2289341587, 1312229383, 395147,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3462181619, 837145225, 2194072164, 1472386366, 1537487790,
+ 439148352, 2717361996, 3390324277, 1458645090, 4049643816, 731583843, 20749520,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 2737025779, 1814181525, 1816957655, 4281341807, 4095807471,
+ 814369634, 1431209630, 3374966462, 3448108709, 31622598, 1983475365, 1089487382,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 236237555, 430037603, 1852070087, 2118228517, 3252136865,
+ 3422495617, 2590246972, 3459047098, 3973588529, 3171827264, 92475618, 1366057699, 13,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 4285838067, 735277699, 3811534046, 3138836190, 397415289,
+ 2582819123, 1510113859, 2843520944, 231283193, 1071216799, 881488229, 734567190, 699,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3219698419, 466853614, 1331826237, 875975020, 1508049190,
+ 2008133206, 1300868705, 3490208720, 2376565918, 2037490725, 1237796068, 4043718151,
+ 36701,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3740840691, 2381286222, 1266084405, 3478844871, 138766328,
+ 2627058549, 1862482887, 3802739663, 3303530586, 670509360, 729738179, 1103040603,
+ 1926447,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1170780915, 2074383492, 1961754613, 1827774001, 916656902,
+ 3259243073, 210875113, 335228650, 3589141125, 431855961, 918438149, 3617060868,
+ 101108337,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 2001253107, 4061558499, 3398113640, 2159180498, 3282472469,
+ 2781426599, 2457937046, 705967069, 1864750529, 2770072726, 4100119564, 3195678530,
+ 1011162313, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1737011955, 1241403666, 1629794265, 1113534036, 219462432,
+ 3724660491, 1632655042, 2458055004, 4216935476, 2686950025, 703127910, 202085749,
+ 3560425265, 64,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1116254963, 876125681, 2080401951, 2417594553, 1281467958,
+ 1222232179, 673709201, 3936590470, 2560633599, 1145420807, 450422830, 2135989143,
+ 2462786277, 3401,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 2340991731, 2737343678, 1289015076, 1347075108, 2512475061,
+ 1412108126, 4100065885, 1354686988, 95090307, 1491981215, 1626720129, 1489450116,
+ 1884072652, 178463,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 3683169011, 3216812981, 2744855154, 4058157834, 3543196209,
+ 4060346256, 3819377805, 880180604, 3698897414, 2155732057, 219486495, 2801311956,
+ 196802852, 9362171,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 4018713331, 378501028, 3741989739, 4071246630, 3223764155,
+ 944505703, 4060511320, 3998749690, 1376306696, 541422658, 3194641855, 1822586890,
+ 2833605965, 491089841,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1065923315, 2272734833, 2112108986, 105514710, 1835282898,
+ 73796845, 594435594, 3623404924, 3134226987, 1565418351, 2713974924, 557744684,
+ 2768639568, 4282534202, 5,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 2408100595, 425311361, 2226869327, 1674710348, 1445832078,
+ 312177116, 2231562669, 3746125130, 3498765241, 1939808910, 1301732400, 854687535,
+ 3104899461, 2200045581, 314,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 260616947, 2859448658, 3702809247, 3320531005, 3151878689,
+ 4207742540, 2040955801, 252969925, 386120380, 1743412687, 3776929748, 2230776619,
+ 1412589968, 2363163963, 16492,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3303796476, 2667360554, 3871609700, 911605988,
+ 27609975, 1290159954, 1842020606, 3449686738, 4185142679, 3778140731, 3809292562,
+ 2284720990, 3785464148, 864751,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2661167083, 3024326089, 3454077871, 3099468518,
+ 2527626308, 1167917430, 1316291304, 2175736981, 3846068744, 1555994873, 2137553745,
+ 2603347602, 3190393459, 45336466,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 940459788, 2102940239, 3591716736, 3834980050,
+ 1875345014, 528139358, 3296012157, 3763402287, 624898860, 1256164751, 3436662455,
+ 2303595502, 90138154, 2376595497,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2601003596, 2817854902, 3738069448, 49753232,
+ 395556631, 4278421792, 1332236424, 2899221004, 2382694996, 1653174695, 1396595153,
+ 2947866156, 2578251457, 15892544, 29,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3767469568, 671287769, 3791280922, 2921453395,
+ 4023886981, 3031459970, 3699531449, 4253680083, 3347977273, 3228548537, 2887358797,
+ 2042847151, 1961985952, 257705967, 1520,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 448122768, 3534770692, 578405276, 433247806,
+ 4175667383, 3340326313, 851853855, 3728964000, 1960633968, 431785542, 1966122388,
+ 1627483431, 2802782821, 2538621092, 79655,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 167469408, 2159686082, 821089061, 2324782401,
+ 578797663, 3194338755, 2071266770, 3465944956, 683610840, 2498722363, 2423090999,
+ 2865272105, 48834160, 912286961, 4173677,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 1946022112, 2246986986, 3745071011, 3383180416,
+ 807758270, 1127886323, 1915334517, 2927471789, 4218978062, 441286161, 2533902413,
+ 89124065, 3692088392, 3505188042, 218658982,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2527376672, 4001711028, 3217742207, 1841952496,
+ 3072670916, 3204261182, 3208730903, 3551501038, 3441960928, 3295757255, 299807985,
+ 1166589037, 2169989204, 3733494551, 2864151189, 2,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 688502816, 1002760441, 377188254, 794405575,
+ 1675507559, 1446917124, 3708509626, 1867858013, 920339131, 2428156058, 2092486826,
+ 3748575832, 1953057757, 2363909823, 2924476765, 139,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 4016976160, 727489525, 65915787, 1183254543,
+ 2888071423, 3049200727, 2690107908, 2198215983, 4119894348, 3875556523, 442037750,
+ 1898863513, 160023236, 989734902, 90609241, 7315,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 4166738208, 2266663227, 673332435, 550627422,
+ 3488007317, 3671482230, 59074521, 4227541302, 2262677408, 1149291744, 921863748,
+ 3112047771, 3399675251, 652952636, 3354949157, 383031,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 351384864, 3565098702, 4155390886, 3220789349,
+ 2608312059, 2103834788, 944768032, 949615699, 4118569665, 1685119122, 980757317,
+ 1694451395, 393065143, 2754423072, 3616955801, 20053644,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3014165792, 796920501, 3672616261, 1130012766,
+ 4077271733, 2971834292, 252963730, 1652648553, 69566497, 1999168979, 3104695664,
+ 710159863, 1735470542, 4064425944, 1573131256, 1049759101,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3209942304, 1679710063, 1755690075, 4251598478,
+ 1049675705, 1717017534, 705215572, 2260440090, 2765904623, 975350201, 1756060381,
+ 1886465482, 2733251981, 3115673342, 472751457, 3404646711, 12,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 1531073824, 2423748678, 1729816840, 1740611885,
+ 2777212627, 2969100597, 501119910, 1886104013, 1931706566, 96118838, 3281936552,
+ 4069997219, 1398231818, 2988777868, 2856575295, 2009829544, 669,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 1719702816, 1119467944, 3767307448, 1949393548,
+ 1966720572, 2729045600, 3753905954, 3937507823, 3731918463, 3146610355, 2967238817,
+ 2505065361, 2485535643, 1893300314, 166365062, 874087629, 35029,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 1536660768, 2051648810, 2202156949, 1371566363,
+ 1821496179, 1684562462, 1200503648, 1234387596, 2182170796, 1165941114, 1382310371,
+ 1755514751, 2474898046, 2611907239, 456112578, 2749340137, 1832576,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 1827181856, 4177292154, 2680738868, 1607480411,
+ 1794618371, 3703803888, 2600970549, 2966298121, 174262665, 1367904220, 905247628,
+ 2766298775, 3949556221, 1103455785, 3652079576, 1227653919, 95856967,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3121124640, 4270361123, 2054576521, 3629448573,
+ 2564600244, 132302420, 4285170993, 2743649285, 133493916, 3207419472, 3048390000,
+ 2806218966, 2081300703, 4181751659, 1789896323, 1910815868, 718209863, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3908867360, 3679284426, 2607521588, 512862844,
+ 2732858915, 2032099596, 1011088742, 4023691910, 2343553854, 2039346656, 791945032,
+ 3298005749, 1732434189, 2233637154, 2006257184, 4156212473, 144011146, 61,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 487363872, 3552686372, 3532184366, 1586939227,
+ 3724812754, 2465159099, 2444171857, 3424835511, 2900544237, 1283399694, 2991378323,
+ 2924594123, 3463506096, 3517618882, 1646903520, 850003639, 3693629995, 3190,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 970757408, 593337656, 2621124778, 2362798218,
+ 1847405156, 3380032365, 3917922903, 1684495821, 3187390150, 3115120277, 3126047712,
+ 1298279344, 3937900583, 3741958231, 1599463652, 2918936608, 2485862381, 166789,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2807862560, 3332862828, 3314859132, 1713311062,
+ 3684236752, 781458918, 1258707760, 795101932, 1642963495, 1566360302, 2027884895,
+ 2599672732, 4134932573, 4218553122, 3065721011, 3586743940, 3098048383, 8716654,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3214710048, 491973160, 2927525970, 3000056542,
+ 3840979468, 354856496, 2429638914, 1493528488, 2621804923, 1074562622, 2337847084,
+ 805113811, 2215452942, 2043738784, 862449625, 3952422953, 2935835032, 455457980,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3936130336, 3810953987, 1542106736, 3855036022,
+ 737849359, 946161952, 3426822660, 140914304, 1709004311, 1784614413, 2398718531,
+ 3365991385, 2536976033, 1995705147, 903783129, 3958112258, 744347273, 2318865108, 5,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2476512544, 2901890493, 1516229638, 4279451811,
+ 1158581914, 3413180859, 2662373916, 138227816, 3500608007, 1021825042, 1542512059,
+ 1657638841, 2546098455, 2167488194, 2639913991, 1190253808, 3657171949, 1517999094,
+ 289,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2744948000, 3663540216, 1529140403, 1719575268,
+ 788302244, 3133780986, 1080231567, 1297291346, 2555205130, 1013378818, 3710964045,
+ 254267420, 169782776, 444582309, 3263263535, 4006521636, 3677731565, 142388726, 15110,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 933008672, 292991406, 2733939278, 3620414616,
+ 610832446, 2435459475, 3940308335, 1824151617, 1013413903, 1529153948, 3841031730,
+ 2270845647, 2514900921, 352319593, 4070392803, 1250818383, 2493419008, 4180057617,
+ 788877,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2275185952, 2069192335, 3442270414, 3613722894,
+ 3792005945, 782718190, 2966897497, 2085152667, 4262162053, 1687190049, 507318413,
+ 3050269090, 4242352422, 3290464898, 312510041, 1161865422, 2075798102, 3170157616,
+ 41177419,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 3617363232, 767481141, 2798605879, 216592806,
+ 3009776831, 2287157616, 746314070, 533365218, 1941679715, 3683198332, 1287225523,
+ 1783505619, 2435916847, 30895875, 2414036950, 3357534644, 2488836996, 1476424431,
+ 2148871451,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 1469879584, 3966110914, 524124560, 152391733,
+ 2511082427, 2824602280, 2073871626, 1879641108, 629469854, 1723285870, 1308591480,
+ 1139848879, 1338115761, 1345716577, 2490757277, 2518816577, 2890283632, 3045945884,
+ 445080315, 26,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3821629204, 1251879183, 2891264873,
+ 862372749, 2730649920, 3946196409, 386054333, 495072142, 3997453427, 1929934700,
+ 2286014611, 3132574989, 3816564976, 3613717384, 137659154, 3503341474, 2723213443,
+ 2543045884, 1361,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3723538515, 1352727828, 2409267632,
+ 1399204016, 963872732, 2078104532, 2376079728, 3106353504, 2941033470, 1552894073,
+ 1150412642, 3250864799, 1200024207, 1381405911, 1845036695, 2091830815, 696155422,
+ 2031249559, 71004,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3994675668, 1490424801, 2018541335,
+ 3386372446, 1887579055, 1070949466, 3015236068, 2125386458, 2576663800, 787844066,
+ 3899955011, 3896043957, 3245195291, 3451998588, 4137134477, 2870460987, 2223995227,
+ 1856577899, 3701804,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 108189908, 1009895052, 2417361073,
+ 3716302432, 1206236872, 1714392538, 2423637157, 1446646222, 2951583407, 381426507,
+ 4171738569, 2486690329, 1225283083, 81479387, 655056393, 1387263240, 3653525540,
+ 158300381, 192941957,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1088623368, 362459840, 1427608929,
+ 4026337049, 17759160, 2131962935, 3078358333, 3350714126, 2005794791, 1986667923,
+ 3069103584, 2589397401, 872859540, 3464091314, 2827312770, 2771039149, 2983482937,
+ 3282652449, 1516545866, 2,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 297759128, 1837823977, 1597754151,
+ 2594200211, 3938329176, 1514863451, 170335670, 4104939771, 2122122959, 204320869,
+ 1514324102, 1749816223, 2472922285, 1345800768, 672452179, 1118197205, 2132458193,
+ 2742243656, 985429928, 123,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1565423976, 1940455119, 2001544857,
+ 3190388342, 2147138011, 3053711670, 4016476330, 2167732712, 1190433567, 1594135970,
+ 291103371, 1626580129, 1301317960, 1559418209, 738610514, 205838008, 1400741198,
+ 4214199197, 796192726, 6453,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 589251816, 2391831009, 1728723516,
+ 181130724, 4285375407, 2929351545, 3233212788, 2309651665, 2730928462, 146568749,
+ 2657314986, 3107049692, 1532662895, 1514403156, 103999350, 3374554676, 3436565642,
+ 2034666958, 2828653903, 337921,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2861969704, 1135425604, 1039070983,
+ 1500008042, 756650173, 1226466646, 2884374739, 2720613726, 4037861095, 1154327514,
+ 948102900, 2402380469, 518831132, 287068554, 455744436, 2132737622, 3684840433,
+ 2753298665, 1829483875, 17694581,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1562870824, 2223563800, 627601685,
+ 3931191767, 1963804481, 2068642021, 3182461511, 3440237650, 3372627476, 3882745516,
+ 155135794, 328106351, 153352687, 532473956, 384060564, 2089202149, 2414435551,
+ 2221820394, 1383076804, 926503403,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 651451688, 2063603536, 4195344935,
+ 628247983, 3694049076, 409781270, 3866516567, 3575005349, 477908172, 1365517798,
+ 4219664552, 2267226689, 1273207489, 715477852, 2130518214, 407639283, 1646907501,
+ 3664713902, 3419206668, 1265920647, 11,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1668631848, 3084215717, 2535623602,
+ 1355370279, 1711209510, 3565276581, 334763960, 1637078921, 1157348908, 2867965050,
+ 4160429863, 1209329262, 1964536455, 3844490096, 3112905541, 3015147941, 609034362,
+ 3114335197, 2289791300, 1525249047, 591,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2873237800, 593475146, 3957752737,
+ 3607753543, 998755275, 2973630508, 3812764564, 729867179, 3117173245, 1205481369,
+ 3859663194, 2382784159, 4166471027, 338216186, 2248990593, 4122863298, 2190421143,
+ 962475347, 3363479543, 644463577, 30960,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2441998632, 1456210221, 3517676631,
+ 2125186749, 2336040486, 1803295599, 497828393, 3354229517, 2315762146, 2390170442,
+ 2471250722, 3262196615, 3636458986, 1948827006, 3805560749, 1978273630, 2198623259,
+ 4237805852, 1489294306, 3466543098, 1620841,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 4002365736, 2699270358, 4035612103,
+ 4236649769, 2481511290, 286754901, 2192344738, 3423993860, 3397041319, 2594484709,
+ 3540687144, 447363625, 488059790, 444324058, 1568636462, 2963210948, 924176437,
+ 1842840823, 23040779, 22811001, 84851830,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2587476264, 531471700, 565661081,
+ 3173396807, 560588409, 2397243561, 2939066677, 1112532089, 4016222432, 4054750870,
+ 1034402982, 1728888132, 2408752621, 3982402714, 3118679989, 1289461419, 1709813332,
+ 2514987870, 1439101459, 4218408052, 146893508, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1562378536, 3984676162, 1533759221,
+ 2265150196, 4077915242, 3191635425, 1391128044, 79630882, 1799432732, 798767144,
+ 788136604, 3877311258, 2344732049, 2771994519, 730903558, 1075120241, 2762438885,
+ 1653779067, 641228852, 4115900764, 587300920, 54,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3532063016, 2106301204, 1161579613,
+ 3977699201, 517035130, 2794580228, 3841970396, 3789755434, 721327799, 1673345532,
+ 77309593, 2039372378, 2275232492, 247383471, 1521519421, 3259734418, 42022609,
+ 2135694052, 1236139276, 2334490590, 3251143850, 2833,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1110966568, 2393056800, 1350110272,
+ 2924655289, 1459090315, 3475237696, 1257511530, 4036949063, 723610430, 1552731592,
+ 1205849493, 3220845583, 3701829803, 2849977890, 1258404014, 238739126, 2442636624,
+ 4014062691, 3433106490, 1901814374, 3462971528, 148325,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3407348008, 1233228594, 3411181417,
+ 2093918876, 1827015250, 1338390636, 4278954490, 2855758896, 2194310770, 3410983728,
+ 620234408, 1826608367, 3617389928, 246027064, 1642755342, 1870814843, 1879774701,
+ 904530615, 3902271887, 3396039084, 4245280487, 7763447,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1217134888, 3078093129, 3061112270,
+ 1278695255, 3616386859, 4024538543, 2988611858, 2589454103, 787801141, 2215111346,
+ 4020818330, 3231249235, 3792889391, 1656781417, 1381049381, 2527801670, 1083965285,
+ 1627715319, 2367340571, 1283593976, 1268495279, 406327846,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2442920232, 45276574, 2032627375,
+ 917984412, 2986861814, 2373466326, 1802961357, 3126051563, 2002628262, 2936766148,
+ 3750548047, 3767161939, 2780915648, 1069487964, 4036604440, 1550559861, 1170372835,
+ 3083458002, 1323016514, 1640149106, 4074778593, 4085980586, 4,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3832283432, 2696280962, 2572683480,
+ 4175448237, 3404610727, 2136661146, 4257763704, 892952018, 2218618404, 1250412368,
+ 2141474832, 700052702, 1861341822, 1903102532, 539549951, 987106772, 2352847513,
+ 2795598340, 142672976, 3553933053, 3432928320, 547528798, 259,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2381054248, 21917494, 2024589020,
+ 3035885024, 1544851454, 3873165476, 52544173, 584620594, 999613054, 53857787,
+ 2953709759, 3669630452, 1795146519, 968325082, 4236363572, 3962694578, 607022979,
+ 1147753971, 3246682774, 1373916352, 3588841297, 3896070368, 13560,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3458990376, 3102212629, 2923606015,
+ 2610526647, 2862495030, 2462184740, 247107426, 2375588414, 1615032415, 2018689308,
+ 23577407, 757254239, 3469952833, 2523407951, 236907216, 2696977587, 3342638617,
+ 3218520741, 1307512362, 1441329142, 1553236610, 3099072064, 709657,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1227620648, 2735822829, 3182207984,
+ 2655874152, 1911959699, 3202269726, 3352265041, 2482043729, 1096361615, 1827680999,
+ 2108667791, 4109372722, 3751633681, 4168743365, 1483427286, 3668772749, 371413044,
+ 891157989, 3614903301, 379159372, 2554584355, 2523739390, 37135930,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1378615592, 2124979467, 3869608993,
+ 1182792460, 2640281798, 94061031, 3072662239, 1765178173, 522921752, 1599801713,
+ 2392193932, 3491954298, 3409559666, 995484266, 287535901, 1813090899, 3858071186,
+ 2932421652, 2727235639, 47972540, 1760330552, 1367320824, 1943234256,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 573309224, 2848462570, 297419517,
+ 784731094, 2768546732, 295310148, 1482184039, 3289378734, 1626845618, 4088238483,
+ 572708640, 1418696534, 4059942084, 984728194, 2831713861, 2810244614, 2425826462,
+ 502741229, 2484597449, 2051110097, 1309436654, 1582624320, 2897210858, 23,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 908853544, 115794502, 3565064611,
+ 4224203971, 3003673416, 2040538988, 3494903392, 2174787101, 730958942, 1587426713,
+ 1216059146, 2222641978, 458956447, 2568862701, 1118079692, 3242301930, 3084300380,
+ 4119311250, 3093156219, 1587649575, 1429127399, 2586105371, 3232553230, 1238,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2251030824, 3012358716, 278114290,
+ 2220419113, 3976552303, 2715134477, 1681099792, 1589972998, 3000442551, 404778394,
+ 832783608, 2396613513, 1673183307, 1942089317, 1845205033, 3482430837, 4084553639,
+ 2323389363, 3896457087, 348002344, 4044436031, 287449663, 3114057219, 64814,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 3593208104, 2945023736, 2227032504,
+ 2193119976, 808267449, 3216385101, 3033836400, 456555884, 1125870240, 464859713,
+ 10507958, 270631227, 2210300992, 1703891855, 551285133, 2264386612, 1922395156,
+ 3034507121, 1040591836, 922801132, 1637967686, 3017872049, 3114302110, 3391169,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 1445724456, 4036055873, 26662430,
+ 2849460476, 126144669, 3134242821, 3267407193, 3385299635, 2084731548, 3086885011,
+ 2933551302, 1118233540, 334662835, 2478562709, 3520965186, 286926085, 2281497960,
+ 4239060620, 2277599706, 456079410, 3755990621, 289475438, 3137271329, 177424049,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1433733179, 1983551259,
+ 3183236740, 3213866899, 4208537401, 2945052152, 3940374272, 3882475903, 1509970502,
+ 3309566084, 1926141956, 3284707559, 3230861295, 3043572573, 2579360888, 3177799575,
+ 1588286429, 932093071, 3711427490, 368598896, 1429105521, 906409877, 692522539, 2,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 156035530, 1133686565,
+ 1743320647, 3375072318, 2012342394, 1153370874, 2481071177, 583451137, 327194059,
+ 397399572, 2128700879, 944339639, 1239680795, 3991450197, 3388101214, 3344426675,
+ 3597872431, 4206908931, 2447851829, 1889574113, 1472740103, 3986860253, 294017406, 113,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 258440619, 2864366690,
+ 1036994977, 1375962384, 623996537, 3277116958, 3199396551, 4141879303, 592996622,
+ 3477310669, 492237602, 337428166, 1626177557, 299025377, 994816793, 779280139,
+ 2761101027, 3415256044, 2363507292, 3483241717, 2337671414, 2257176492, 784913708,
+ 5915,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 3567736939, 1422128831,
+ 1359905647, 3749719229, 127483939, 4223227770, 2169435423, 2138123196, 1126010966,
+ 613271075, 605448982, 3068413273, 1547048905, 2583389829, 1453489243, 8760026,
+ 1648613590, 2555144115, 401564871, 4196029032, 1612167424, 302476558, 550178662,
+ 309445,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 587583775, 3536224817,
+ 2781219922, 2582698859, 2983955949, 913689216, 2656586171, 486487766, 3837012000,
+ 3679195413, 1772417733, 2985095058, 1205369396, 1096725427, 2527158883, 3112953302,
+ 45546555, 685048376, 2320196409, 3593580469, 3481366272, 3387590826, 1954897998,
+ 16187820,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 523158703, 4113109057,
+ 2100971674, 217956614, 596199284, 2380878930, 3063776566, 1398583896, 475510041,
+ 2647065, 569539020, 3670413946, 1728895160, 1053315873, 2514048110, 2778013497,
+ 3821961787, 4267822458, 13820863, 1167343593, 3268996059, 1641040263, 140134694,
+ 846803892,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1118880383, 2310160004,
+ 3019308007, 4008582343, 2896674950, 330609170, 817589387, 2952646134, 1654073678,
+ 8987835, 2423235159, 664912750, 3046216472, 489593826, 2216686240, 1791381573,
+ 2387259047, 2476423337, 3775812603, 4086181528, 2412691423, 184457215, 464564228,
+ 1346638432, 10,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 2976500223, 2979299174,
+ 3407692876, 1015168976, 1405129545, 89483162, 1793466320, 4107506794, 451927044,
+ 3854789239, 2292720344, 3033627639, 3414458444, 4206167043, 3911629651, 1549165729,
+ 2609588033, 1282189107, 3868152888, 2198179607, 187872385, 1757276935, 1970119063,
+ 2103526322, 539,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1701240383, 4150727432,
+ 4229846246, 1491411143, 3448010050, 2748284773, 435433000, 2628046556, 2404309504,
+ 1338368081, 3510713777, 1756851377, 3559685262, 3651796331, 2244954464, 847590960,
+ 1051616462, 2516424913, 980131917, 619425458, 246904961, 3660903002, 4148712762,
+ 2358648193, 28219,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1919189311, 1075985887,
+ 3115672713, 3401657272, 3896084877, 525825118, 876173054, 3168154221, 2976809555,
+ 2149006125, 4178177611, 2236024378, 2590801160, 2938337185, 2230946803, 3406716434,
+ 1896413717, 485546906, 2995780162, 2252419063, 951366215, 1875230279, 3453444322,
+ 2272567594, 1476076,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 2773596735, 887942758,
+ 3663982038, 1807451680, 3462435410, 4176426144, 3909880531, 569807074, 2047733993,
+ 32965839, 1144605978, 2238484561, 243114785, 530662820, 1119792380, 2182736718,
+ 2324124763, 1631774642, 2784602890, 1606266084, 1344911530, 3252479184, 699521876,
+ 2806160939, 77207632,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 2701552191, 3021831563,
+ 3761866094, 1495182712, 3005256748, 1578935760, 729160555, 1248336637, 96469620,
+ 2453445089, 3694966433, 33145778, 582073547, 2108534160, 910164355, 1709948987,
+ 4081974507, 962001181, 1665189504, 3356106364, 2947861254, 1856731644, 1088109335,
+ 571966670, 4038357842,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 434748991, 1126591621,
+ 2683503626, 3186698004, 2540597168, 2833518046, 2921126828, 3105171050, 1989368494,
+ 902639402, 2679724105, 1682425435, 2556927276, 3696061619, 1949936733, 2929994801,
+ 1995337434, 2496165830, 1577335356, 1326658277, 3155215019, 4233228516, 707341872,
+ 1525371270, 770589498, 49,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 3368717887, 651430580,
+ 1331245725, 1870762458, 952297851, 288351487, 3062668627, 1959269072, 110037462,
+ 4211895868, 88728104, 3759138601, 2492810714, 2210040404, 2053569231, 1675388541,
+ 3095602713, 1096209388, 842591810, 4253831717, 1328811468, 2020607979, 1049249342,
+ 1275113326, 1155387700, 2572,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 3701595711, 1944631056,
+ 3913388044, 1972358401, 2211743384, 2468061449, 1825696645, 3950254819, 4175887213,
+ 658190054, 988997182, 79409135, 3369430523, 2182610800, 1656518321, 3430281761,
+ 467517116, 1215120427, 1477681381, 1103183017, 1606806971, 410824471, 966071681,
+ 404550408, 3941253598, 134537,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1778933311, 1533008756,
+ 993702014, 1124262251, 3395509530, 3217741713, 1279240497, 3816440002, 1903989291,
+ 51592826, 788752418, 435974174, 1298746546, 2043470592, 3595479373, 1154431373,
+ 474663849, 2645512080, 1612537241, 2883787901, 2770004941, 3764226311, 820346693,
+ 2815966245, 3411107300, 7036700,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 714513983, 130433633,
+ 43631165, 1061573839, 3636110016, 3105933977, 4119850069, 3850315024, 3307706156,
+ 1888708654, 1089500020, 3483168113, 4246385694, 148388959, 3339203696, 50446275,
+ 3495488798, 1475116976, 3302398184, 3737270005, 335208268, 3241199250, 1916720821,
+ 1631957933, 2590429742, 368036023,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 5086783, 281448113,
+ 2198189083, 2950462223, 1083532748, 999096768, 120317796, 3666454154, 3267420670,
+ 3370080400, 3355726828, 577850252, 948557141, 2836218692, 1427932166, 1569892183,
+ 3940072213, 585643425, 376346774, 2570723472, 3703624351, 241067127, 2244633063,
+ 388840025, 2897967481, 2069179149, 4,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 2254347839, 3919771391,
+ 2083221487, 1248859317, 2584251500, 2309118643, 2966295632, 530227479, 3941029541,
+ 3829792983, 1331460141, 794511141, 4238539339, 3614639971, 3888520003, 671875281,
+ 1318280390, 1017040504, 4102400675, 3908602649, 3081942537, 3325786435, 2657424156,
+ 2789779288, 2421537869, 1739491396, 234,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 721329727, 3396839210,
+ 3681663045, 2164575428, 2501982576, 1849109597, 300912659, 2388520714, 1411624874,
+ 2624693519, 167813227, 3136212958, 3590039522, 366933383, 3827298677, 1462508872,
+ 4104316159, 3408037632, 75945980, 142801927, 110906538, 224733420, 3587144888,
+ 2079024206, 1816483967, 3487529667, 12259,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 3606224447, 2188419014,
+ 1682084083, 3590599894, 890252841, 412438068, 1372266175, 373594701, 3372748264,
+ 2179273181, 4139821800, 3352899463, 3096202992, 4118590511, 1812536094, 933259209,
+ 2398707774, 2707207274, 2998411347, 3811914682, 3520084344, 1714267093, 1063807448,
+ 3971931589, 2091560152, 941864838, 641211,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 889364031, 2352408643,
+ 2785101230, 1733772378, 434784163, 4073566559, 2380432466, 31602380, 4066003450,
+ 4237626852, 40313701, 575299096, 3781620268, 2441227883, 169978061, 469088410,
+ 4035978047, 1815582963, 950034051, 3187464554, 3077778800, 959496553, 627861401,
+ 1054229804, 2989469638, 476513413, 33536656,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1037213247, 3876594009,
+ 2266627829, 1919250987, 537147373, 2107714153, 2288848718, 3541705719, 457011493,
+ 1804491592, 2322151177, 1323959538, 2711719661, 3758789829, 2391880189, 1967683422,
+ 1660874094, 762547319, 2152582589, 3218958934, 3364300115, 2092930734, 112375585,
+ 1053460104, 1303864359, 1010650355, 1754045079,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 592617023, 1467380502,
+ 2920044402, 3863208706, 2968900529, 3891775804, 1937888664, 1517322414, 4079190218,
+ 2261620489, 922271986, 1130329623, 3364875314, 3847148069, 2618495464, 190888988,
+ 3288980521, 966383648, 731142836, 624127045, 1186738324, 1079024412, 4233796097,
+ 3186498315, 2896863071, 2664720291, 1547038600, 21,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 2341641791, 2690557422,
+ 1657533239, 3963446746, 2703859152, 249614421, 2885391040, 1382595792, 1418129263,
+ 3838274635, 366387568, 576270801, 474192704, 531259356, 3296862651, 3613193853,
+ 3267779581, 1895643517, 2825715628, 2202733356, 130729796, 488623509, 1763848835,
+ 2605511812, 2862954514, 2332937230, 894415183, 1117,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1452449343, 1146197058,
+ 2863763601, 93635278, 4064052986, 1491278843, 3759242632, 2357543838, 1663095871,
+ 2905572294, 1392422334, 650761418, 970832320, 478005943, 1896661229, 1639868621,
+ 3674871690, 3321586441, 3192994991, 4009230510, 1248211706, 4222945771, 818509991,
+ 1745442306, 3321513893, 2773719933, 1718321319, 58434,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 3214057023, 1624527164,
+ 3510708953, 572913639, 976867443, 3600176463, 566184589, 202266931, 3799707683,
+ 2676529998, 2657330927, 146625937, 3197015680, 3446603163, 3159890695, 385445176,
+ 985476360, 2726297137, 3136737090, 180444142, 3558697084, 1848158780, 2837294127,
+ 3264483530, 3316877529, 114183317, 1315990856, 3056397,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1335008831, 2025528607,
+ 2551216052, 1231522767, 1577354711, 2011572376, 3223747309, 1201751868, 3525934298,
+ 2868471710, 1932753307, 2293006463, 3175890606, 310909112, 3878142961, 943828189,
+ 3647227586, 2588718047, 722658033, 4186505903, 685808546, 604140331, 4121595882,
+ 798648471, 3597867602, 1023618253, 2393446371, 159867062,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 3818036799, 506128897,
+ 3210177243, 2823203764, 3664221334, 92701100, 3929787299, 3240683251, 2162523887,
+ 241905739, 1168759323, 1987737378, 215876082, 2620200269, 766727543, 1367504002,
+ 256654718, 1602659492, 3580579779, 877599467, 3288208776, 403329079, 3503114788,
+ 1822959735, 1853927691, 2913901605, 2974172155, 4067172038, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 865246783, 2478961165,
+ 2279369525, 1520299851, 1366585487, 1882728022, 4008137538, 1871447639, 3136473970,
+ 3318579424, 872785890, 2718034001, 1178511011, 2797504317, 3859536305, 3499293615,
+ 3699614382, 1622854171, 3883461695, 3549424136, 3601002501, 3866372034, 2873861091,
+ 2935925015, 178042204, 3684726535, 1974077257, 3615893361, 101,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 2207424063, 287650399,
+ 3981042031, 1021320006, 1997542145, 1279380243, 2113901254, 185032623, 2126737977,
+ 2069376527, 3345854844, 944924203, 1381750296, 3143952166, 2542102859, 3968815518,
+ 1505847453, 392713703, 3301576050, 4089524993, 105444836, 137335223, 2482596656,
+ 3198079093, 1239483262, 1952216162, 3413804971, 1291819903, 5327,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 59940415, 1811670372,
+ 4069128060, 323976449, 972848863, 4196783059, 2091578826, 4136797996, 2137688135,
+ 2072447942, 3494899784, 3293304898, 3077389977, 3297643560, 4127008437, 906830581,
+ 2345350258, 3830948402, 181768411, 1490075293, 2758544777, 2659560401, 1217847221,
+ 2331559151, 1117191399, 3989967619, 223309308, 236525387, 278677,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1472287494,
+ 499807712, 3091974659, 4285256010, 3709032953, 2652354345, 1492839133, 3720959829,
+ 3181700988, 243307285, 2635473859, 3928938903, 1318083514, 3873894276, 3864396159,
+ 4074500574, 3372801244, 4206609721, 195422496, 1336336801, 119286259, 377214804,
+ 3067415634, 3479414776, 849565694, 2893846532, 850758156, 14578400,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 2943874277,
+ 3085475042, 1945597866, 333623124, 611451271, 623193460, 401877214, 721271982,
+ 1599632279, 2905205146, 2666546460, 1055507639, 2433365035, 2462715959, 571334422,
+ 2536517743, 2916272350, 2556731527, 1027980980, 3685046324, 1363456080, 2137673941,
+ 4179722900, 4282073441, 721763253, 2564496459, 3570344581, 762666481,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1688935206,
+ 3475125672, 1130811827, 1933921485, 531309505, 2114739632, 4153679106, 3454868205,
+ 3216657819, 3610045576, 200928018, 667211958, 1718836400, 3546350352, 365452608,
+ 4278360604, 1394651330, 1897113925, 2604987881, 393693183, 2422383856, 2173309089,
+ 2039658552, 1552739446, 1806832201, 202993211, 643893967, 1245681714, 9,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 69714342,
+ 3572836939, 917813014, 2872424929, 3320167609, 3915314664, 2158359498, 4096929447,
+ 1758773917, 786611217, 775992986, 2828036016, 1548582321, 3515773534, 1812941543,
+ 2323394627, 2767537218, 1984982431, 1938557130, 2280845712, 2129545450, 33303255,
+ 3422157941, 2567101694, 2032365961, 287136117, 218347635, 205070838, 486,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 937253082,
+ 3145851772, 399354380, 3966343270, 166744482, 268238601, 3920682663, 2684648910,
+ 3319860812, 3123677841, 3438421899, 1745377561, 2255572901, 750005822, 2952830203,
+ 3372110582, 185116763, 2002594349, 2433711601, 4063410951, 2286486142, 1772751515,
+ 3216019658, 4000395493, 4000253346, 789132859, 1524346577, 3799756468, 25430,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 504488298,
+ 3203478147, 623275496, 484461782, 1186077606, 1635005427, 3292075576, 2904368173,
+ 3518426662, 181481468, 1620487576, 425716765, 604055179, 805684953, 805173333,
+ 3762705229, 2028717707, 801243590, 2881187444, 846270972, 3082213948, 1607262303,
+ 1052034558, 157330487, 100016280, 3992222713, 1068806644, 2968059137, 1330568,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1637897530,
+ 1176690152, 2518524412, 1571689290, 564727034, 2889640772, 189920587, 971299436,
+ 197455917, 506450197, 314150642, 2142474017, 1145233556, 2799889119, 149868198,
+ 1878569113, 2059335395, 1444099206, 3839586651, 278212380, 1911724378, 3294879169,
+ 3920817795, 858717088, 3156125105, 3400670886, 2237009409, 266827588, 69615850,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3711670458,
+ 1859501024, 3610388180, 946947779, 188562960, 1743243970, 1574884712, 226401137,
+ 1143881879, 4062190916, 229671045, 2229726790, 999524008, 1316690469, 4167061421,
+ 2498109216, 4190372998, 879299639, 2242805718, 497842390, 1722101840, 3774019732,
+ 4234321243, 3960322840, 2849777158, 2529803791, 1822070296, 4066700810, 3642286032,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 2981418234,
+ 692167403, 1848601396, 1851033287, 3082881006, 3029106926, 1056224599, 1747081078,
+ 2663632879, 154522774, 2209597776, 2772758834, 3547012403, 2334215292, 3375366183,
+ 457373055, 1042267069, 1683395470, 3464887484, 3950738381, 2895002845, 3347042372,
+ 2078125748, 4255125750, 2065510086, 4099872968, 2407024158, 1480172293, 1582961262, 44,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 4125018106,
+ 1266788247, 1827479118, 2740028768, 2177761845, 4240657249, 2664765720, 2699426397,
+ 3614214301, 1454649263, 4133484154, 3036887598, 2683101280, 3074486753, 835341147,
+ 3368320629, 1229459788, 452669746, 56808418, 1624613901, 4220917568, 2053078848,
+ 2242861251, 3442203080, 3939617165, 3443289142, 614889031, 3317401451, 1303453973,
+ 2321,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 4144259322,
+ 510494920, 1764954127, 83312554, 3748961014, 313729141, 1602272849, 1269321234,
+ 2686911170, 490015867, 1664750701, 2562471748, 3259156338, 2516500245, 1735770725,
+ 663832606, 3550903612, 581616448, 2323885727, 1028803259, 1389737432, 2800431785,
+ 4132179859, 1971645827, 1316284844, 589480419, 3424138973, 4047738950, 1015097954,
+ 121446,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 187486458,
+ 3948427707, 2684007304, 2019110728, 297954, 19026412, 2220806674, 2961165664,
+ 2731046581, 2226946501, 99313115, 2011440358, 2223279933, 3825085095, 1243730519,
+ 1520855745, 753516187, 3495541403, 1121681341, 2249905758, 1470892432, 2528006013,
+ 2635297570, 1569210250, 820756082, 4161553654, 3820200787, 2695080773, 1601345601,
+ 6353776,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1594270970,
+ 1659758811, 2150372209, 203424308, 1450821561, 1840071673, 2158310511, 1256614075,
+ 852448241, 2516168341, 1280629025, 3243030782, 866104124, 3526173307, 2529026022,
+ 3647096254, 1307891285, 2747650692, 1894152882, 3020927393, 1257295519, 2541719605,
+ 753092931, 1207937333, 406183594, 3734804116, 4235922774, 294736150, 3803850933,
+ 332411224,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 2138862842,
+ 1291938968, 1063251815, 3170601015, 2671654210, 113890197, 2650092083, 2105084901,
+ 2357022675, 1649775569, 2595302470, 1910508674, 1571126118, 3725528327, 2509219534,
+ 1328702106, 2413808307, 1504672593, 3563655142, 909319039, 3320511195, 778354275,
+ 1567520491, 2609521647, 1419322921, 3424589238, 1666238921, 247329657, 2462539832,
+ 210767815, 4,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 531219706,
+ 593478036, 450119827, 3749065210, 2086394584, 2910876912, 614108214, 4115375801,
+ 44864403, 2006634342, 1680960375, 3812244716, 1231206091, 4257658636, 3572109458,
+ 1925186103, 1437593381, 283293330, 2722021809, 1726475417, 337402759, 2228226360,
+ 2318797623, 3821440697, 3602072373, 2563060900, 3870061838, 2190672447, 3834326569,
+ 3573601848, 211,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1087128826,
+ 981452571, 2880291694, 690450234, 2453641974, 3692566137, 598633614, 3877527001,
+ 768346722, 3382159864, 161950338, 2269297588, 2586046528, 1220588270, 1193232140,
+ 2163563226, 643752406, 1617927903, 3402316016, 682532534, 2135929957, 2020237801,
+ 42718290, 1243975999, 65815584, 2766923586, 3228600988, 508222157, 2234744696,
+ 649167307, 11082,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 2231534842,
+ 2016029098, 2102547670, 3923335417, 1984901852, 350880092, 2844195792, 458613143,
+ 275085070, 4008059670, 925677591, 264491974, 3331668198, 909553008, 1056346490,
+ 4172757287, 2231906487, 3027579414, 2137629704, 3313017339, 3340434766, 3868134082,
+ 3178418324, 1989113004, 2491891383, 3924450077, 1227802997, 1133757557, 4201533026,
+ 1655299608, 579766,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 453608698,
+ 2974240311, 4190540419, 1410368863, 833699122, 3566653284, 1221166785, 2701860513,
+ 4209709410, 134442109, 3051903255, 723203335, 3738552855, 3445694436, 1098450603,
+ 3456666556, 2312759672, 390733580, 1065635372, 3833332957, 1784038691, 365685078,
+ 4203571811, 3960413215, 2644005498, 1308680782, 2528127772, 2922357531, 2538871130,
+ 89406648, 30330440,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1870300410,
+ 3519987679, 1785307871, 2076018399, 2631762895, 3337063364, 2197180164, 1044451958,
+ 3461946801, 2428986168, 3118024345, 1762268358, 231516531, 822067348, 1654625105,
+ 2372345571, 2157667181, 2651495586, 2166886651, 3634216025, 92645252, 1757253515,
+ 193416704, 2676968478, 102624486, 1554339635, 601848384, 2183691411, 83598378,
+ 1020113734, 1586723574,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 265979130,
+ 121455263, 2188672198, 3408550078, 2397184039, 2427373929, 3201791133, 2777668048,
+ 1115407813, 3103231190, 3537501048, 760881182, 1284277906, 3353426689, 1853209179,
+ 396964304, 1239487750, 1277749024, 1370670850, 3779603290, 1520771030, 140592522,
+ 1810338136, 3929439595, 4176243011, 561601281, 3519529757, 3757993485, 3139295079,
+ 1630853870, 1403800759, 19,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3394143482,
+ 2100488124, 3706196543, 4119429252, 943109685, 1644436876, 2936915684, 3287021127,
+ 3957754123, 1028688673, 2578375259, 2520030764, 530159313, 3457850287, 1263475043,
+ 607228571, 1147164734, 3334565589, 2049984080, 2117393090, 224359410, 4143333089,
+ 29920363, 3675525811, 2314357996, 2934312505, 589492290, 890888488, 1586529790,
+ 1388279943, 267009447, 1011,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1029604602,
+ 1300955315, 1830990254, 1970899103, 1916726520, 3486386395, 1053589970, 2175764065,
+ 2379095055, 754945042, 531625132, 4014252696, 3829929750, 2474352983, 189287338,
+ 1723055400, 1365757113, 3099920986, 3432163066, 1799736303, 3216463597, 434456139,
+ 1543423970, 3109762217, 3137941216, 509720093, 1827337688, 720424945, 2707331184,
+ 816691531, 1092968965, 52892,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 2083423482,
+ 2505415884, 3212144860, 710902257, 3320088221, 3147567479, 3091994784, 4201390360,
+ 4143787420, 1074850321, 2060158878, 3635644273, 4179910906, 2722055679, 2939194020,
+ 2466925622, 202688522, 372284267, 3277804064, 3401490865, 1773346774, 506145463,
+ 1388421892, 3493417405, 20233904, 3395525644, 4196390909, 1444793471, 3972498474,
+ 1443159463, 745252462, 2766966,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 2645460218,
+ 2086822908, 2707204293, 1114209013, 825850397, 873488971, 2792723373, 36856421,
+ 3667233282, 1582246548, 651520439, 1928100686, 474104159, 2536694282, 2452371321,
+ 2833948873, 2693133614, 1565755751, 1806845266, 3169331798, 2417037154, 3779543345,
+ 1304854875, 3783283689, 507107699, 299935788, 4261007077, 4249521952, 797303627,
+ 2793347355, 243877443, 144748256,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 770606330,
+ 2264804, 329439611, 2332764211, 3451269867, 1927897295, 3114736445, 3016443594,
+ 1060032281, 82130215, 1783271780, 3996233488, 2763222839, 1195102010, 3973812197,
+ 3274054474, 1182302338, 1246796164, 1177746551, 227103909, 22573863, 514858324,
+ 1220606725, 1982433693, 3507357714, 1424442578, 2527561086, 2442928632, 1529612946,
+ 3953493533, 4285474694, 3277208968, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1223591162,
+ 2982394804, 2895911336, 367523356, 2047351792, 371938123, 1378078781, 873612867,
+ 3202209579, 1262032369, 714144373, 3437733071, 602639567, 3984064747, 612978490,
+ 3471864183, 674481286, 1394271724, 1763826443, 2990163968, 3301989925, 614316342,
+ 37291547, 1513490987, 4181721666, 449071607, 3349264364, 4080788191, 992432549,
+ 110857720, 2412516358, 982433320, 92,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 300844282,
+ 2307493123, 2055145423, 1171144040, 2971339012, 869159436, 4016520557, 2700899514,
+ 1661166827, 1912303342, 936071126, 2875533686, 3868206806, 2282746627, 2149397043,
+ 4175901529, 1080663404, 1982433417, 1398961609, 2644130322, 1226711637, 2742866472,
+ 245919861, 1192438573, 796345551, 1845430816, 4013717915, 1845523693, 219966706,
+ 15237374, 1933961567, 2987494186, 4824,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1643021562,
+ 1920281417, 1446539515, 672005290, 1339606105, 1060185642, 2928538624, 1412353665,
+ 502198486, 1114659620, 2915704511, 2151278059, 2021357877, 3160496898, 2352263002,
+ 1249509765, 4146969416, 1655382877, 4172807454, 20332050, 3769739973, 2432429984,
+ 1945135808, 620977496, 3648234946, 1743994192, 2945771402, 2947990629, 3617215824,
+ 3994616034, 552805990, 3762013631, 252389,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 1978565882,
+ 2524960146, 4193922506, 4076590529, 2516223737, 391695821, 2190762613, 522673139,
+ 1987128608, 440254412, 2807372635, 359959156, 3737477879, 2747000479, 581553201,
+ 2511797953, 702434943, 2652697923, 3695193898, 3613772647, 1816774943, 2722879248,
+ 2302144282, 3030219558, 3287548153, 218079512, 3420566846, 1009458358, 935580230,
+ 1506862800, 4272011209, 2867187996, 13202995,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3320743162,
+ 948343474, 3405636809, 4014767551, 2612885443, 3972612845, 824281737, 4137837656,
+ 414658304, 1027983595, 1814268684, 1190496019, 2283765592, 2254837733, 1402998045,
+ 3229446146, 2921499078, 1872277523, 40810166, 1881592946, 1676856550, 4237240479,
+ 954097323, 3540364155, 2427558438, 4097986023, 1876897108, 2259500003, 410781233,
+ 47810089, 1460850268, 3796693287, 690671804,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 367953146,
+ 1947178011, 857303701, 4252140757, 2685473375, 170237623, 681228584, 2546374475,
+ 647727024, 574155167, 3835998138, 3156977392, 1377964963, 879242026, 2397433252,
+ 3151903944, 321716629, 2915001872, 2034067052, 4197479390, 239762274, 411187050,
+ 1913498586, 1999752094, 3974430733, 3337922549, 3113592958, 2973554659, 262245046,
+ 3390159210, 3748777389, 3567389094, 1770416790, 8,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 2515436794,
+ 1967278043, 2104607690, 2279407792, 4042270982, 2493824756, 2093025952, 3057235161,
+ 1947246360, 2051169369, 148967671, 2639851938, 447496030, 1169299488, 2272991543,
+ 2665184954, 859991147, 2025887591, 1226166406, 1287398010, 3430001396, 89366649,
+ 4067196564, 961555706, 1728104225, 3397127484, 4040801740, 2318882210, 3672174130,
+ 1476555222, 3748652491, 797956256, 236712014, 440,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 3412268069, 1023086602, 2026210508, 2948640365, 1990377411, 3418447020, 2628410977,
+ 1991879622, 3672905839, 1223731781, 2017025521, 2737855981, 2257171014, 890828011,
+ 2761812033, 1866749028, 3636324627, 776987488, 3662393815, 1860335431, 3546671068,
+ 559330021, 3174795765, 244385001, 399604355, 141731703, 1407315466, 3798132614,
+ 2901035943, 3891090695, 2549732229, 3844051172, 23019,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2287539796, 4275088352, 2524788671, 428301997, 3800466684, 3002290381, 589736055,
+ 1142130780, 373738384, 3768739486, 2628080437, 2388674081, 2423831981, 2386510872,
+ 3309501747, 3119841775, 3519820, 4127203326, 2410571999, 3790961085, 393878211,
+ 1807298400, 787387858, 3771609922, 2083085827, 3431083982, 1779441946, 1173778119,
+ 2530463497, 102816871, 3379839254, 3462765990, 1204200,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1761281781, 1175965788, 1954342487, 1084525753, 3257851189, 1650471046, 589323451,
+ 1708627683, 2913630328, 327508870, 65450921, 387930981, 868838718, 1131223535,
+ 2045168060, 2182484972, 1305171510, 3513059695, 2783764003, 1352841236, 4223790765,
+ 2355760065, 1705130248, 2408827385, 1032430657, 2597771791, 2207430797, 1314791031,
+ 2272867397, 3418340483, 4056746188, 2275032614, 62993239,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1809340725, 535748848, 2651279796, 2972702886, 3798816580, 1689558595, 2563380497,
+ 2676026999, 735820630, 506791325, 3000489346, 1796919308, 3526045192, 486243855,
+ 372489664, 1906228610, 3800714730, 1747845295, 2957993825, 668549798, 2621876870,
+ 407136765, 591566264, 2632073022, 1636313827, 891821689, 2281824154, 1923313633,
+ 958388250, 1285318798, 1658461377, 856491293, 3295251525,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2712792809, 3658590250, 1894120586, 698265268, 1162171068, 870321121, 3815695773,
+ 1680991420, 4253709444, 648283645, 1126625410, 3601123345, 2808686883, 1180925142,
+ 807407113, 560604794, 4237259425, 1207497088, 484611426, 2618666933, 3408262350,
+ 2884145631, 1261484694, 700732417, 2424618666, 267590588, 747041969, 769087774,
+ 4178986315, 138609815, 2888997761, 3066983590, 579742411, 40,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 651758713, 3906043406, 2548994640, 2228757432, 2121881214, 472348093, 878530644,
+ 971248989, 2081688077, 1949321364, 2647657092, 1107849908, 3882250508, 1952827114,
+ 859336559, 744223792, 3992135533, 68806754, 2609188276, 4080249809, 297055681,
+ 885074930, 2678708362, 3615821021, 2182633519, 760522724, 3081526858, 2240134876,
+ 3574753748, 1020578834, 4050681264, 124268560, 2177028858, 2099,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 587036233, 3067701272, 1697918617, 3416744348, 957762033, 2784479345, 3660801700,
+ 2341257627, 2870220782, 3127255235, 1850426667, 2590711144, 193256504, 3092252746,
+ 858791940, 3195757850, 2789572728, 2250403466, 4223626301, 883283817, 343136822,
+ 754616125, 4172822213, 377411409, 3910770529, 1514450968, 868753818, 333097290,
+ 2246022828, 2503075601, 462100262, 282524657, 2620007649, 109827,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1716843977, 2406245462, 4100164558, 730871533, 2076274051, 1849094521, 441146666,
+ 463301781, 1449951415, 2543043405, 2506092364, 2186797030, 4026578175, 3937378246,
+ 698864271, 604408052, 870457054, 3903374973, 1022854923, 4281001692, 279067829,
+ 3748974629, 1724584816, 814693658, 848422032, 2238904762, 3671458914, 4166573676,
+ 2052797806, 200140248, 1816868384, 1300920824, 3180657116, 5745209,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1266702857, 1726163715, 1868411348, 3369952439, 3242131435, 1534222815, 2135626846,
+ 3416782314, 2087636967, 1971732998, 3841794106, 322082412, 2445768432, 604649625,
+ 1736128197, 2376764098, 2774390933, 1146681592, 677359293, 1813892062, 968415609,
+ 1576242251, 3798010013, 3827678188, 902520545, 2549131665, 3909779088, 1506236052,
+ 2938795369, 485390229, 2269504120, 2792821107, 3274905154, 300538757,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2249628937, 685687495, 2193556864, 849482254, 1892955482, 2678790792, 3215816948,
+ 3418405555, 4263405829, 2982365918, 3251176925, 1827020223, 3699084302, 4097605129,
+ 1965684458, 4076326152, 1772127055, 3933922126, 479474035, 1840427833, 3012899581,
+ 3831069482, 3298156499, 4038576278, 2408364303, 4056410551, 2104476906, 193784559,
+ 3125211841, 1505789080, 2696864045, 827610944, 2518243838, 2836651081, 3,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 4184643081, 1410986433, 1843600019, 2400675340, 929523537, 2459688753, 947275637,
+ 3237179039, 4130345007, 3653734165, 2190755476, 4082624876, 758859577, 215491460,
+ 3989305297, 2894966395, 1376384020, 4106882930, 292746535, 2711172166, 2768244740,
+ 146454730, 2700634288, 2811878475, 406039235, 3376346153, 2204857609, 1752048742,
+ 2908338330, 2827562179, 2659032057, 2206054502, 795376852, 2075947017, 191,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1298712073, 2201636989, 3613796443, 3750533912, 813399020, 2846080568, 232014587,
+ 2397968910, 1395889404, 2894036997, 2880261938, 2445503214, 1309873853, 705309998,
+ 3769805381, 3352712840, 1793952063, 2039983215, 4181620852, 3563741840, 3416348297,
+ 3870361186, 3297272487, 740951942, 3216429413, 493979526, 3749505290, 2611250942,
+ 2114114653, 832062692, 3217332227, 580286736, 2603067450, 3236710367, 10016,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 3390642697, 3721599337, 871664873, 2911612331, 1048817226, 2119214535, 3867031815,
+ 713033506, 1637429796, 146757919, 604782956, 3611789462, 1105349601, 8293620,
+ 1235958732, 4107004201, 22662698, 4227745661, 615330606, 3555602145, 2758006042,
+ 1871034164, 1089875898, 3010016080, 3984027314, 1148724871, 2505171071, 2916506843,
+ 2063038348, 1635052446, 1554832576, 3305290580, 1432708148, 3316934573, 523990,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 757328393, 3614386048, 612921981, 3671275341, 229647166, 922873307, 4264649519,
+ 1719555435, 2266736286, 1533889711, 2441905738, 280079152, 828611854, 3201843831,
+ 3602591324, 280090900, 1469826922, 1875299088, 3069200698, 3724633479, 1990993253,
+ 4183411048, 2801573192, 3331946271, 3218951071, 3335901655, 2490262384, 3495191922,
+ 3995041812, 142479205, 912525419, 1349100142, 3879969713, 1156441875, 27410757,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2670148105, 4062893077, 1020178806, 3590763106, 340483064, 735422676, 2016484003,
+ 123165102, 335678566, 2404537076, 3105061293, 262388713, 346139379, 4279497998,
+ 4229372948, 1689322293, 2741425721, 679181659, 3127270803, 4076916912, 754865685,
+ 3412746642, 1580171686, 983324276, 1665980275, 3530216836, 4061821633, 665902558,
+ 4257628544, 1810364691, 4287621734, 682959171, 1988619710, 3911294335, 1433901286,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 3859134985, 335331068, 49729055, 2180659011, 2028716409, 698050378, 1054422637,
+ 118763064, 3248361728, 4170950490, 1271907245, 2427996554, 3295652926, 3972266060,
+ 417838330, 3974975679, 196215687, 1069744590, 3392617455, 3081697941, 2439553383,
+ 3952135723, 1301176694, 3409091030, 1929659121, 429183607, 3113036387, 2212349291,
+ 2869725706, 2012444045, 9727018, 4144199701, 3596901873, 584228079, 1995418814, 17,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1944320521, 1623163986, 628812511, 3164503401, 3895298003, 1054018629, 340174001,
+ 3153318879, 3928889269, 885285768, 3997011857, 1566203560, 1265094257, 2802058726,
+ 1331675844, 2827468672, 3060230221, 948837327, 1325629880, 2200958864, 1888334393,
+ 625826929, 2768815320, 2019796144, 3120134802, 1259302831, 758014572, 4152415859,
+ 4121411433, 4104878577, 4050987119, 4003867353, 365502017, 3906849072, 2599511236, 913,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2855991817, 3606270125, 3181925397, 1257739756, 1915489712, 4000844533, 2935459934,
+ 3199937223, 585493537, 2183742943, 4127321345, 2262180642, 3815400108, 2289695961,
+ 2815189647, 4136674591, 4257830634, 1089901505, 2531093721, 2877560724, 3445987330,
+ 1090531247, 2485278853, 443621013, 801806268, 1637665528, 206526733, 142888530,
+ 215304120, 562513453, 1670416775, 769471011, 2404374923, 1720644795, 2109662988, 47792,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2232154633, 1896409237, 2365622837, 3265658089, 2304950344, 629781221, 2808696483,
+ 3607626459, 1613690854, 3240893670, 2034809217, 3701333687, 4019267560, 2934788607,
+ 1000006471, 841934072, 3804698306, 1028929358, 37860002, 2667482229, 2989744713,
+ 1734489179, 3783538269, 3966691719, 1370314139, 3520981926, 4001410774, 3344358977,
+ 422292633, 3797056842, 4224698104, 2178099822, 2363633118, 840586894, 2027155544,
+ 2500125,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 19659273, 3220260405, 219408304, 2653515606, 2382656772, 1175565998, 4236323019,
+ 945581179, 2684487750, 2728069737, 1760710078, 3383293733, 225823767, 2592633266,
+ 2575153034, 1843036524, 2188424523, 2242423936, 1053404805, 1099379566, 3683175185,
+ 2856152948, 560129007, 2365107635, 4126904878, 2478475286, 3985790097, 2156013108,
+ 4114005084, 3855494087, 1718783362, 2995569012, 3215050506, 3538134199, 1735144505,
+ 130787134,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2854222345, 327624114, 3930972968, 3338609940, 4160100491, 1369247557, 336580958,
+ 3829711641, 2260926043, 3169241416, 1890583300, 1600428045, 2513137876, 2569532300,
+ 1153079777, 2076762262, 1991046151, 2097142034, 3767179424, 2482412768, 1617290924,
+ 1831653476, 2536667053, 2459756649, 3342948645, 1411557415, 1984219451, 1838558342,
+ 2101966663, 4061975165, 3145853166, 3246339919, 1163935729, 1811615434, 902266163,
+ 2546816526, 1,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 842005001, 355176350, 2114974833, 494686742, 2433365255, 3877574949, 2315539152,
+ 876546272, 600231499, 611489188, 1562884203, 1619233555, 238670040, 278329366,
+ 253541034, 2893307126, 3022623747, 3858635470, 3361253054, 1047785714, 760632267,
+ 3451717120, 2363692180, 1104748202, 887485704, 4216544507, 666010480, 340335913,
+ 2196648833, 1519847578, 3079887219, 2197423964, 2161700369, 828284167, 2924785644,
+ 1428498487, 83,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 654309897, 1694660860, 53644162, 719830500, 1346144126, 673702875, 1861379378,
+ 1248111153, 88483473, 1214871104, 760018784, 748015845, 3920112330, 1451106470,
+ 2416521600, 139673472, 1631116243, 3635739447, 2140639545, 747719032, 4103149476,
+ 2448347463, 1918631311, 1045764051, 3937222717, 3527506827, 3791575540, 1130257547,
+ 1111955208, 3781906601, 1655764382, 546384510, 540931477, 692596877, 4183948736,
+ 1491597320, 4359,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2222979593, 4061094889, 953466330, 4164056915, 2681813943, 3111113686, 3324352345,
+ 1740781125, 871329561, 3511837688, 2785070824, 2608723999, 167310482, 3357531027,
+ 3336475205, 1743224291, 1506125517, 1464703126, 3570815192, 1479890140, 1467987273,
+ 2535695435, 4011119207, 34118114, 2909809094, 2708212011, 3619957842, 3819607067,
+ 551243015, 4243601098, 2181644241, 4199986351, 3438670995, 1047519114, 3252714832,
+ 2109756729, 228049,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1019214345, 3495330614, 3276646235, 2917694430, 202635504, 3127232740, 3096620825,
+ 2422150918, 2598217057, 2312710030, 1794242995, 3114292296, 4187839285, 2679606467,
+ 2363551913, 2720666805, 2294334399, 3171297205, 2597289631, 4007201490, 241513290,
+ 487844326, 159657130, 2759576089, 3694698832, 1216640832, 4275866767, 2977309375,
+ 1545701229, 3060587272, 1164198153, 2925687791, 153485005, 3909822127, 756091994,
+ 2803779685, 11929927,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 2814376457, 2640877314, 546262917, 2038320337, 4134503216, 6312622, 3742317570,
+ 2818695184, 1883758307, 1936304904, 940198419, 2574351302, 4202998030, 2096070289,
+ 1919640665, 3365064531, 2402066125, 3392759355, 808613323, 3281727309, 4218506968,
+ 1288863240, 2317106034, 1550598984, 3398356838, 2885063453, 967415299, 1655572686,
+ 311025610, 1003926595, 2301518401, 335703777, 1059867251, 2301363583, 1843601789,
+ 1172249251, 624090442,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 3502242313, 1804007453, 1735714352, 613223925, 1030198935, 952721098, 2082843195,
+ 2734934294, 3842351295, 831495393, 1110138176, 466303268, 3623141358, 1647759366,
+ 1128907386, 3234636962, 3317881823, 661273616, 2610082853, 2328028526, 481010401,
+ 3758025887, 3642284429, 2122902860, 1662465428, 3875062690, 2381397737, 1723696361,
+ 1080501446, 2606552252, 1487821436, 58750251, 3599280126, 2261211288, 4119403670,
+ 2613793840, 2583351593, 7,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 3770677769, 4049516327, 1450027359, 4002593979, 3413012441, 1624438220, 898545018,
+ 2498016501, 4137840039, 1065606392, 1492184036, 3226447526, 944013522, 3559656857,
+ 1179750270, 2389232650, 3467392352, 2728744532, 1465630338, 1488576798, 54611393,
+ 3518420558, 2772448600, 1309221855, 2829832393, 1606212912, 2057808156, 2116635731,
+ 3963120029, 2007695148, 1941388729, 1486623309, 1416380476, 510649109, 571816553,
+ 2275852204, 2826859509, 397,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 1958738441, 4284300983, 3080186296, 2744787675, 1702526358, 374933851, 2837853614,
+ 1077815519, 2215259023, 145245666, 549730322, 1889984953, 2172093180, 1553664732,
+ 1369083690, 2494515454, 3176957800, 2566989312, 2807639888, 3857504457, 3760142116,
+ 1535230380, 2027375774, 999441160, 2895617276, 2294641768, 3499880006, 3499376604,
+ 3713739526, 970936659, 4088926661, 3785432868, 3449522887, 1980098243, 2319665041,
+ 2860149894, 3536548067, 20802,
+ ]),
+ BigUint::new(vec![
+ 375288404, 2460223985, 1334358771, 2543621408, 2519466280, 1133682239, 3589178618,
+ 3300915721, 115578348, 1790222711, 1858434925, 4115884344, 1661122603, 3566611261,
+ 540903953, 3108944241, 3649809669, 1068275229, 486997643, 886790309, 1831002139,
+ 2594785146, 23978661, 576512999, 3811470156, 3874159424, 2484542501, 3745592469,
+ 329497784, 2078580168, 2837461864, 3992415299, 91937857, 415278725, 1129439873,
+ 2753976754, 3981038028, 4127738104, 4170126886, 3452333906, 3051709874, 3061052331,
+ 1878810143, 2918565786, 1088266,
+ ]),
+ 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,
+ ]),
+ ][251 - dom.len().get() as usize]
+ .clone()
+ - BigUint::new(vec![ip_count(dom)])
+ // #![feature(int_roundings)]
+ // use num_bigint::BigUint;
+ // use std::fs::File;
+ // use std::io::{BufWriter, Write};
+ // fn main() {
+ // let mut writer = BufWriter::new(
+ // File::options()
+ // .read(false)
+ // .write(true)
+ // .create_new(true)
+ // .open("/home/zack/cards")
+ // .unwrap(),
+ // );
+ // writer.write_all(b" [").unwrap();
+ // let mut count = BigUint::new(Vec::new());
+ // // We only count proper subdomains which means
+ // // the max length of a domain is 252.
+ // // We dont count proper subdomains of the root domain,
+ // // so the minimum length is 2.
+ // for i in 2usize..=252 {
+ // for j in i.div_ceil(64)..=i / 2 {
+ // count += a_restricted_comp(i, j);
+ // }
+ // writer
+ // .write_fmt(format_args!(
+ // "BigUint::new(vec!{:?}), ",
+ // count.to_u32_digits()
+ // ))
+ // .unwrap();
+ // }
+ // writer
+ // .write_all(b"][251 - dom.len().get() as usize].clone()")
+ // .unwrap();
+ // }
+ // fn poly_expand(left: &[BigUint], right: &[BigUint], n: usize) -> Vec<BigUint> {
+ // let mut prod = vec![BigUint::new(Vec::new()); usize::min(n + 1, left.len() + right.len() - 1)];
+ // for i in 0..usize::min(left.len(), n + 1) {
+ // for j in 0..usize::min(right.len(), n + 1 - i) {
+ // prod[i + j] += &left[i] * &right[j];
+ // }
+ // }
+ // prod
+ // }
+ // fn a_restricted_comp(n: usize, k: usize) -> BigUint {
+ // let init = {
+ // let start = usize::max(2, (n + 64).saturating_sub(64 * k));
+ // let end = usize::min(64, n + 2 - (2 * k));
+ // let mut tmp = Vec::with_capacity(end + 1);
+ // for _ in 0..start {
+ // tmp.push(BigUint::new(Vec::new()));
+ // }
+ // for _ in start..=end {
+ // tmp.push(BigUint::new(vec![1]));
+ // }
+ // tmp
+ // };
+ // let mut two_power = 1;
+ // let mut two_powers = Vec::<(Vec<BigUint>, usize)>::with_capacity(7);
+ // let mut k_copy = k;
+ // while k_copy > 0 {
+ // if (k_copy & two_power) == two_power {
+ // let (mut two_power_start, mut two) = two_powers
+ // .last()
+ // .map_or_else(|| (init.clone(), 1), |t| (t.0.clone(), t.1));
+ // while two < two_power {
+ // two_power_start =
+ // poly_expand(two_power_start.as_slice(), two_power_start.as_slice(), n);
+ // two <<= 1;
+ // }
+ // two_powers.push((two_power_start, two_power));
+ // k_copy -= two_power;
+ // }
+ // two_power <<= 1;
+ // }
+ // let mut iter = two_powers.into_iter();
+ // let init = iter.next().unwrap().0;
+ // iter.fold(init, |prev, cur| {
+ // poly_expand(cur.0.as_slice(), prev.as_slice(), n)
+ // })
+ // .into_iter()
+ // .skip(n)
+ // .next()
+ // .unwrap()
+ // * BigUint::new(vec![52]).pow((n - k) as u32)
+ // }
+}
diff --git a/src/file.rs b/src/file.rs
@@ -0,0 +1,952 @@
+#![allow(
+ clippy::exhaustive_structs,
+ clippy::implicit_return,
+ clippy::into_iter_on_ref,
+ clippy::missing_trait_methods,
+ clippy::multiple_unsafe_ops_per_block,
+ clippy::question_mark_used,
+ clippy::ref_patterns,
+ clippy::single_char_lifetime_names,
+ clippy::wildcard_enum_match_arm
+)]
+extern crate alloc;
+use crate::dom::{Adblock, DomainErr, DomainOnly, Hosts, ParsedDomain, RpzDomain, Value, Wildcard};
+use alloc::string::FromUtf8Error;
+use core::borrow::Borrow;
+use core::fmt::{self, Display, Formatter};
+use core::hash::Hash;
+use core::ops::Deref;
+use core::time::Duration;
+use reqwest::Client;
+use serde::de::{self, Deserialize, Deserializer, Unexpected, Visitor};
+use std::collections::{HashMap, HashSet};
+use std::error::Error;
+use std::fs;
+use std::io::{self, ErrorKind};
+use std::path::{Path, PathBuf};
+use superset_map::SupersetSet;
+use tokio::task::{JoinError, JoinSet};
+use tokio::time::{self, error::Elapsed};
+use url::Url;
+/// Wrapper around an absolute [`PathBuf`] to a directory or file depending on `IS_DIR`.
+///
+/// Note that `IS_DIR` iff the wrapped `PathBuf` ends with `/`.
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct AbsFilePath<const IS_DIR: bool> {
+ /// The file or directory.
+ path: PathBuf,
+}
+impl<const IS_DIR: bool> AbsFilePath<IS_DIR> {
+ /// Returns `true` iff `val` was appended to `self`.
+ #[inline]
+ pub fn append(&mut self, val: &str) -> bool {
+ let bytes = val.as_bytes();
+ if IS_DIR {
+ self.path.as_mut_os_string().push(val);
+ bytes.last().map_or(true, |byt| {
+ if *byt != b'/' {
+ self.path.as_mut_os_string().push("/");
+ }
+ true
+ })
+ // When `!IS_DIR`, we have to verify `val` does not end
+ // 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| {
+ *byt == b'/'
+ || bytes
+ .get(bytes.len().wrapping_sub(2)..)
+ .map_or(false, |byts| {
+ byts == b".."
+ && bytes
+ .get(bytes.len().wrapping_sub(3))
+ .map_or(true, |byt2| *byt2 == b'/')
+ })
+ }) {
+ false
+ } else {
+ self.path.as_mut_os_string().push(val);
+ true
+ }
+ }
+ /// Returns `self` as a [`Path`] reference.
+ #[inline]
+ #[must_use]
+ pub fn as_path(&self) -> &Path {
+ self.path.as_path()
+ }
+ /// Returns an `AbsFilePath` iff `val` conforms to the following:
+ /// * `PathBuf::from(val).is_absolute()`.
+ /// * `PathBuf::from(val).as_bytes().last().unwrap() == b'/'` ⇒ `IS_DIR`.
+ /// * `PathBuf::from(val).file_name().is_none()` ⇒ `IS_DIR`.
+ ///
+ /// If `IS_DIR` and `PathBuf::from(val).as_bytes().last().unwrap() != b'/'`, `val`
+ /// will have `/` appended to it.
+ #[allow(clippy::option_if_let_else)]
+ #[inline]
+ #[must_use]
+ pub fn from_string(val: String) -> Option<Self> {
+ match val.as_bytes().last() {
+ Some(byt) => {
+ let last = *byt;
+ let mut path = PathBuf::from(val);
+ if path.is_absolute() {
+ if last == b'/' {
+ IS_DIR.then_some(Self { path })
+ } else if IS_DIR {
+ path.as_mut_os_string().push("/");
+ Some(Self { path })
+ } else {
+ path.file_name().is_some().then_some(Self { path })
+ }
+ } else {
+ None
+ }
+ }
+ None => None,
+ }
+ }
+}
+impl<const IS_DIR: bool> AsRef<Path> for AbsFilePath<IS_DIR> {
+ #[inline]
+ fn as_ref(&self) -> &Path {
+ self.as_path()
+ }
+}
+impl<const IS_DIR: bool> Borrow<Path> for AbsFilePath<IS_DIR> {
+ #[inline]
+ fn borrow(&self) -> &Path {
+ self.as_path()
+ }
+}
+impl<const IS_DIR: bool> Deref for AbsFilePath<IS_DIR> {
+ type Target = Path;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.as_path()
+ }
+}
+impl<const IS_DIR: bool> From<AbsFilePath<IS_DIR>> for PathBuf {
+ #[inline]
+ fn from(value: AbsFilePath<IS_DIR>) -> Self {
+ value.path
+ }
+}
+impl<'de, const IS_DIR: bool> Deserialize<'de> for AbsFilePath<IS_DIR> {
+ #[allow(clippy::too_many_lines)]
+ #[inline]
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ /// `Visitor` for `AbsFilePath`.
+ struct FilePathVisitor<const IS_DIR: bool>;
+ impl<'de, const IS_DIR: bool> Visitor<'de> for FilePathVisitor<IS_DIR> {
+ type Value = AbsFilePath<IS_DIR>;
+ #[inline]
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ formatter.write_str("struct AbsFilePath")
+ }
+ #[allow(clippy::arithmetic_side_effects, clippy::min_ident_chars)]
+ #[inline]
+ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ v.as_bytes().last().map_or_else(
+ || {
+ Err(E::invalid_value(
+ Unexpected::Str(v),
+ &"an absolute file path",
+ ))
+ },
+ |byt| {
+ if *byt == b'/' {
+ if IS_DIR {
+ let mut path = PathBuf::with_capacity(v.len());
+ path.push(v);
+ if path.is_absolute() {
+ Ok(AbsFilePath { path })
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(v),
+ &"an absolute file path to a directory",
+ ))
+ }
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(v),
+ &"an absolute file path to a file",
+ ))
+ }
+ } else if IS_DIR {
+ let mut path = PathBuf::with_capacity(v.len() + 1);
+ path.push(v);
+ path.as_mut_os_string().push("/");
+ if path.is_absolute() {
+ Ok(AbsFilePath { path })
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(v),
+ &"an absolute file path to a directory",
+ ))
+ }
+ } else {
+ let mut path = PathBuf::with_capacity(v.len());
+ path.push(v);
+ if path.is_absolute() && path.file_name().is_some() {
+ Ok(AbsFilePath { path })
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(v),
+ &"an absolute file path to a file",
+ ))
+ }
+ }
+ },
+ )
+ }
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ match v.as_bytes().last() {
+ Some(byt) => {
+ if *byt == b'/' {
+ if IS_DIR {
+ let path = PathBuf::from(v);
+ if path.is_absolute() {
+ Ok(AbsFilePath { path })
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(path.to_string_lossy().as_ref()),
+ &"an absolute file path to a directory",
+ ))
+ }
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(v.as_str()),
+ &"an absolute file path to a file",
+ ))
+ }
+ } else if IS_DIR {
+ let mut path = PathBuf::from(v);
+ path.as_mut_os_string().push("/");
+ if path.is_absolute() {
+ Ok(AbsFilePath { path })
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(path.to_string_lossy().as_ref()),
+ &"an absolute file path to a directory",
+ ))
+ }
+ } else {
+ let path = PathBuf::from(v);
+ if path.is_absolute() && path.file_name().is_some() {
+ Ok(AbsFilePath { path })
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Str(path.to_string_lossy().as_ref()),
+ &"an absolute file path to a file",
+ ))
+ }
+ }
+ }
+ None => Err(E::invalid_value(
+ Unexpected::Str(v.as_str()),
+ &"an absolute file path",
+ )),
+ }
+ }
+ }
+ deserializer.deserialize_string(FilePathVisitor)
+ }
+}
+impl<const IS_DIR: bool> Display for AbsFilePath<IS_DIR> {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.write_str(&self.path.to_string_lossy())
+ }
+}
+/// Wrapper around an absolute HTTP(S) [`Url`].
+#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub struct HttpUrl {
+ /// The HTTP(S) `Url`.
+ url: Url,
+}
+impl HttpUrl {
+ /// Returns `self` as a [`Url`] reference.
+ #[inline]
+ #[must_use]
+ pub const fn as_url(&self) -> &Url {
+ &self.url
+ }
+ /// Returns an `HttpUrl` iff `url` has a host and HTTP(S) scheme.
+ #[inline]
+ #[must_use]
+ pub fn from_url(url: Url) -> Option<Self> {
+ (url.has_host() && (url.scheme() == "http" || url.scheme() == "https"))
+ .then_some(Self { url })
+ }
+}
+impl AsRef<Url> for HttpUrl {
+ #[inline]
+ fn as_ref(&self) -> &Url {
+ self.as_url()
+ }
+}
+impl Borrow<Url> for HttpUrl {
+ #[inline]
+ fn borrow(&self) -> &Url {
+ self.as_url()
+ }
+}
+impl Deref for HttpUrl {
+ type Target = Url;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.as_url()
+ }
+}
+impl Display for HttpUrl {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ self.url.fmt(f)
+ }
+}
+impl From<HttpUrl> for Url {
+ #[inline]
+ fn from(value: HttpUrl) -> Self {
+ value.url
+ }
+}
+impl<'de> Deserialize<'de> for HttpUrl {
+ #[inline]
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ /// `Visitor` for `HttpUrl`.
+ struct UrlVisitor;
+ impl<'d> Visitor<'d> for UrlVisitor {
+ type Value = HttpUrl;
+ #[inline]
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ formatter.write_str("struct HttpUrl")
+ }
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Url::parse(v).map_or_else(
+ |_| {
+ Err(E::invalid_type(
+ Unexpected::Str(v),
+ &"an absolute URL with HTTP(S) scheme",
+ ))
+ },
+ |url| {
+ if url.has_host() && (url.scheme() == "http" || url.scheme() == "https") {
+ Ok(HttpUrl { url })
+ } else {
+ Err(E::invalid_value(
+ Unexpected::Other(v),
+ &"an absolute URL with HTTP(S) scheme",
+ ))
+ }
+ },
+ )
+ }
+ }
+ deserializer.deserialize_str(UrlVisitor)
+ }
+}
+/// Represents the kind of [`ParsedDomain`]s a [`File`]
+/// contains.
+#[allow(clippy::exhaustive_enums)]
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum Kind {
+ /// [`Adblock`] domains.
+ Adblock,
+ /// [`DomainOnly`] domains.
+ DomainOnly,
+ /// [`Hosts`] domains.
+ Hosts,
+ /// [`Wildcard`] domains.
+ Wildcard,
+}
+impl Display for Kind {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Adblock => f.write_str("Adblock"),
+ Self::DomainOnly => f.write_str("Domain-only"),
+ Self::Hosts => f.write_str("Hosts"),
+ Self::Wildcard => f.write_str("Wildcard"),
+ }
+ }
+}
+/// The name where a [`File`] was sourced from.
+#[allow(clippy::exhaustive_enums)]
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum Name {
+ /// The `File` came from the contained `AbsFilePath`.
+ Path(AbsFilePath<false>),
+ /// The `File` came from the contained `HttpUrl`.
+ Url(HttpUrl),
+}
+impl Display for Name {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Path(ref path) => path.fmt(f),
+ Self::Url(ref url) => url.fmt(f),
+ }
+ }
+}
+/// 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.
+ pub name: Name,
+ /// The contents of the file.
+ pub data: String,
+}
+impl Display for File {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ self.name.fmt(f)
+ }
+}
+/// A summary of a [`File`] after it has been parsed
+/// into [`Value`]s.
+#[derive(Clone, Debug)]
+pub struct Summary<'a, E: Eq + Hash> {
+ /// The `File` that was parsed.
+ pub file: &'a File,
+ /// The kind of file.
+ pub kind: Kind,
+ /// The quantity of domains parsed.
+ pub domain_count: usize,
+ /// The quantity of comments parsed.
+ pub comment_count: usize,
+ /// The quantity of blank lines parsed.
+ pub blank_count: usize,
+ /// Parsing errors and their counts.
+ pub errors: HashMap<E, usize>,
+}
+impl<E: Display + Eq + Hash> Display for Summary<'_, E> {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "({}) {} - domains parsed: {}, comments parsed: {}, blanks parsed: {}, parsing errors: {}",
+ self.kind, self.file, self.domain_count, self.comment_count, self.blank_count, self.errors.values().sum::<usize>()
+ ).and_then(|()| {
+ if self.errors.is_empty() {
+ Ok(())
+ } else {
+ f.write_str(", errors: [").and_then(|()| {
+ self.errors.iter().try_fold((), |(), tup| {
+ write!(f, "{}: {}, ", tup.0, tup.1)
+ })
+ }).and_then(|()| f.write_str("]"))
+ }
+ })
+ }
+}
+impl<E: Eq + Hash> PartialEq<Summary<'_, E>> for Summary<'_, E> {
+ #[inline]
+ fn eq(&self, other: &Summary<'_, E>) -> bool {
+ self.file == other.file
+ && self.kind == other.kind
+ && self.domain_count == other.domain_count
+ && self.comment_count == other.comment_count
+ && self.blank_count == other.blank_count
+ && self.errors == other.errors
+ }
+}
+impl<E: Eq + Hash> Eq for Summary<'_, E> {}
+/// Container of [`Adblock`], [`DomainOnly`], [`Hosts`], and [`Wildcard`]
+/// files.
+#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub struct Files {
+ /// [`Adblock`] files.
+ pub adblock: Vec<File>,
+ /// [`DomainOnly`] files.
+ pub domain: Vec<File>,
+ /// [`Hosts`] files.
+ pub hosts: Vec<File>,
+ /// [`Wildcard`] files.
+ pub wildcard: Vec<File>,
+}
+/// Helper trait to track what kind of file.
+trait Helper {
+ /// Returns the `Kind`.
+ fn kind() -> Kind;
+}
+impl Helper for Adblock<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::Adblock
+ }
+}
+impl Helper for DomainOnly<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::DomainOnly
+ }
+}
+impl Helper for Hosts<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::Hosts
+ }
+}
+impl Helper for Wildcard<'_> {
+ #[inline]
+ fn kind() -> Kind {
+ Kind::Wildcard
+ }
+}
+impl Files {
+ /// Attempts to convert an `ExternalFiles` into a `Files`.
+ ///
+ /// # Errors
+ ///
+ /// Returns `ExtFileErr` iff any of the files in `ExternalFiles`
+ /// errors when downloading them and transforming them into a [`String`].
+ #[inline]
+ pub async fn from_external(ext: ExternalFiles) -> Result<Self, ExtFileErr> {
+ /// `await`s each `Result` in `set` propagating any errors; otherwise
+ /// the `String` is `push`ed into `files`.
+ ///
+ /// If an error occurs, then the function immediately returns.
+ async fn add_file(
+ files: &mut Vec<File>,
+ mut set: JoinSet<Result<Result<File, ExtFileErr>, Elapsed>>,
+ ) -> Result<(), ExtFileErr> {
+ while let Some(val) = set.join_next().await {
+ files.push(val???);
+ }
+ Ok(())
+ }
+ let mut files = Self {
+ adblock: Vec::with_capacity(ext.adblock.len()),
+ domain: Vec::with_capacity(ext.domain.len()),
+ hosts: Vec::with_capacity(ext.hosts.len()),
+ wildcard: Vec::with_capacity(ext.wildcard.len()),
+ };
+ add_file(&mut files.adblock, ext.adblock).await?;
+ add_file(&mut files.domain, ext.domain).await?;
+ add_file(&mut files.hosts, ext.hosts).await?;
+ add_file(&mut files.wildcard, ext.wildcard).await?;
+ Ok(files)
+ }
+ /// Creates an empty instance of `Files`.
+ #[inline]
+ #[must_use]
+ pub const fn new() -> Self {
+ Self {
+ adblock: Vec::new(),
+ domain: Vec::new(),
+ hosts: Vec::new(),
+ wildcard: Vec::new(),
+ }
+ }
+ /// Reads each [`String`] from each field and attempts
+ /// to transform each line into an `RpzDomain` before adding
+ /// the domain into `doms`.
+ ///
+ /// Returns a `Vec` containing `Summary` information for each
+ /// [`File`] that was parsed.
+ #[allow(clippy::arithmetic_side_effects)]
+ #[inline]
+ pub fn add_to_superset<'a: 'b, 'b>(
+ &'a self,
+ doms: &mut SupersetSet<RpzDomain<'b>>,
+ ) -> Vec<Summary<'a, DomainErr>> {
+ /// Iterates each `String` from `files` and transforms each line
+ /// into `T` before adding it as an `RpzDomain` into `doms`.
+ #[inline]
+ fn insert<
+ 'a,
+ 'b: 'a,
+ T: Into<RpzDomain<'a>> + ParsedDomain<'a, Error = DomainErr> + Helper,
+ >(
+ doms: &mut SupersetSet<RpzDomain<'a>>,
+ files: &'b [File],
+ summaries: &mut Vec<Summary<'b, DomainErr>>,
+ ) {
+ let kind = T::kind();
+ files.into_iter().fold((), |(), file| {
+ let mut summary = Summary {
+ file,
+ kind,
+ domain_count: 0,
+ comment_count: 0,
+ blank_count: 0,
+ errors: HashMap::new(),
+ };
+ file.data
+ .lines()
+ .fold((), |(), line| match T::parse_value(line) {
+ Ok(val) => match val {
+ Value::Domain(dom) => {
+ summary.domain_count = summary.domain_count.saturating_add(1);
+ doms.insert(dom.into());
+ }
+ Value::Comment(_) => {
+ summary.comment_count = summary.comment_count.saturating_add(1);
+ }
+ Value::Blank => {
+ summary.blank_count = summary.blank_count.saturating_add(1);
+ }
+ },
+ Err(err) => {
+ let count = summary.errors.entry(err).or_insert(0);
+ *count = count.saturating_add(1);
+ }
+ });
+ summaries.push(summary);
+ });
+ }
+ let mut summaries = Vec::with_capacity(
+ self.adblock.len() + self.domain.len() + self.hosts.len() + self.wildcard.len(),
+ );
+ insert::<Adblock>(doms, self.adblock.as_slice(), &mut summaries);
+ insert::<DomainOnly>(doms, self.domain.as_slice(), &mut summaries);
+ insert::<Hosts>(doms, self.hosts.as_slice(), &mut summaries);
+ insert::<Wildcard>(doms, self.wildcard.as_slice(), &mut summaries);
+ summaries
+ }
+ /// Returns `true` iff there are no files.
+ #[inline]
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.adblock.is_empty()
+ && self.domain.is_empty()
+ && self.hosts.is_empty()
+ && self.wildcard.is_empty()
+ }
+}
+/// Block and unblock [`Files`] stored on the local file system.
+#[derive(Debug)]
+pub struct LocalFiles {
+ /// Local file system files containing domains to not block.
+ pub unblock: Files,
+ /// Local file system files containing domains to block.
+ pub block: Files,
+}
+impl LocalFiles {
+ /// Reads all block and unblock files in the `adblock/`, `domain/`, `hosts/`, and `wildcard/` directories
+ /// under `dir/block/` and `dir/unblock/` respectively.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`io::Error`] iff reading said files causes an error. Note that
+ /// it is _not_ an error if a directory does not exist.
+ #[inline]
+ pub fn from_path(dir: AbsFilePath<true>) -> Result<Option<Self>, io::Error> {
+ /// Checks if `path` exists.
+ #[inline]
+ fn exists<P: AsRef<Path>>(path: P) -> Result<bool, io::Error> {
+ fs::metadata(path).map_or_else(
+ |err| match err.kind() {
+ ErrorKind::NotFound => Ok(false),
+ _ => Err(err),
+ },
+ |_| Ok(true),
+ )
+ }
+ /// Reads all files stored in `adblock/`, `domain/`, `hosts/`, and `wildcard/`
+ /// directories under `dir/name/`.
+ #[inline]
+ fn get_files(
+ mut dir: PathBuf,
+ files: &mut Files,
+ name: &str,
+ ) -> Result<PathBuf, io::Error> {
+ /// Reads all files under `dir/name/`.
+ #[inline]
+ fn get_file(
+ files: &mut Vec<File>,
+ mut dir: PathBuf,
+ name: &str,
+ ) -> Result<PathBuf, io::Error> {
+ dir.push(name);
+ if exists(dir.as_path())? {
+ for entry in fs::read_dir(dir.as_path())? {
+ let file = entry?;
+ if !file.file_type()?.is_dir() {
+ let path = file.path();
+ files.push(fs::read_to_string(path.as_path()).map(|data| File {
+ name: Name::Path(AbsFilePath::<false> { path }),
+ data,
+ })?);
+ }
+ }
+ }
+ dir.pop();
+ Ok(dir)
+ }
+ dir.push(name);
+ if exists(dir.as_path())? {
+ get_file(&mut files.adblock, dir, "adblock/")
+ .and_then(|dir2| get_file(&mut files.domain, dir2, "domain/"))
+ .and_then(|dir2| get_file(&mut files.hosts, dir2, "hosts/"))
+ .and_then(|dir2| get_file(&mut files.wildcard, dir2, "wildcard/"))
+ .map(|mut dir2| {
+ dir2.pop();
+ dir2
+ })
+ } else {
+ dir.pop();
+ Ok(dir)
+ }
+ }
+ let mut unblock = Files::new();
+ let mut block = Files::new();
+ get_files(PathBuf::from(dir), &mut unblock, "unblock/").and_then(|dir2| {
+ get_files(dir2, &mut block, "block/").map(|_| {
+ if unblock.is_empty() && block.is_empty() {
+ None
+ } else {
+ Some(Self { unblock, block })
+ }
+ })
+ })
+ }
+}
+/// Error returned when downloading text files from HTTP(S) servers.
+#[allow(clippy::exhaustive_enums)]
+#[derive(Debug)]
+pub enum ExtFileErr {
+ /// Error when a task exceeds the specified timeout.
+ Timeout(Elapsed),
+ /// HTTP(S) error when attempting to download a file.
+ Http(reqwest::Error),
+ /// Error when a task fails to complete.
+ Join(JoinError),
+ /// Error when a file is not valid UTF-8.
+ InvalidUtf8(FromUtf8Error),
+}
+impl Display for ExtFileErr {
+ #[allow(clippy::min_ident_chars)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Timeout(ref err) => err.fmt(f),
+ Self::Http(ref err) => err.fmt(f),
+ Self::Join(ref err) => err.fmt(f),
+ Self::InvalidUtf8(ref err) => err.fmt(f),
+ }
+ }
+}
+impl From<Elapsed> for ExtFileErr {
+ #[inline]
+ fn from(value: Elapsed) -> Self {
+ Self::Timeout(value)
+ }
+}
+impl From<reqwest::Error> for ExtFileErr {
+ #[inline]
+ fn from(value: reqwest::Error) -> Self {
+ Self::Http(value)
+ }
+}
+impl From<JoinError> for ExtFileErr {
+ #[inline]
+ fn from(value: JoinError) -> Self {
+ Self::Join(value)
+ }
+}
+impl From<FromUtf8Error> for ExtFileErr {
+ #[inline]
+ fn from(value: FromUtf8Error) -> Self {
+ Self::InvalidUtf8(value)
+ }
+}
+impl Error for ExtFileErr {}
+/// Tasks of downloaded files from HTTP(S) servers.
+#[derive(Debug)]
+pub struct ExternalFiles {
+ /// [`Adblock`]-based files.
+ pub adblock: JoinSet<Result<Result<File, ExtFileErr>, Elapsed>>,
+ /// [`DomainOnly`]-based files.
+ pub domain: JoinSet<Result<Result<File, ExtFileErr>, Elapsed>>,
+ /// [`Hosts`]-based files.
+ pub hosts: JoinSet<Result<Result<File, ExtFileErr>, Elapsed>>,
+ /// [`Wildcard`]-based files.
+ pub wildcard: JoinSet<Result<Result<File, ExtFileErr>, Elapsed>>,
+}
+impl ExternalFiles {
+ /// The maximum timeout for a task.
+ pub const MAX_TIMEOUT: Duration = Duration::from_secs(3600);
+ /// The minimum timeout for a task.
+ pub const MIN_TIMEOUT: Duration = Duration::from_secs(1);
+ /// Returns `ExternalFiles` containing files downloaded
+ /// from the `HashSet`s with a timeout of [`Self::MAX_TIMEOUT`]
+ /// for each task.
+ #[inline]
+ #[must_use]
+ pub fn new_with_urls(
+ client: &'static Client,
+ adblock_urls: HashSet<HttpUrl>,
+ domain_urls: HashSet<HttpUrl>,
+ hosts_urls: HashSet<HttpUrl>,
+ wildcard_urls: HashSet<HttpUrl>,
+ ) -> Self {
+ Self::new_with_urls_and_timeout(
+ client,
+ Self::MAX_TIMEOUT,
+ adblock_urls,
+ domain_urls,
+ hosts_urls,
+ wildcard_urls,
+ )
+ }
+ /// Returns `ExternalFiles` containing files downloaded
+ /// from the `HashSet`s with a timeout for each task set to `timeout`.
+ ///
+ /// Note that when `timeout` is `<` [`Self::MIN_TIMEOUT`], it is set to
+ /// `MIN_TIMEOUT`; similarly when it is `>` [`Self::MAX_TIMEOUT`], it is set
+ /// to `MAX_TIMEOUT`.
+ #[inline]
+ #[must_use]
+ pub fn new_with_urls_and_timeout(
+ client: &'static Client,
+ timeout: Duration,
+ adblock_urls: HashSet<HttpUrl>,
+ domain_urls: HashSet<HttpUrl>,
+ hosts_urls: HashSet<HttpUrl>,
+ wildcard_urls: HashSet<HttpUrl>,
+ ) -> Self {
+ let mut val = Self::new();
+ val.add_adblock(client, timeout, adblock_urls);
+ val.add_domain(client, timeout, domain_urls);
+ val.add_hosts(client, timeout, hosts_urls);
+ val.add_wildcard(client, timeout, wildcard_urls);
+ val
+ }
+ /// Returns an empty `ExternalFiles`.
+ #[inline]
+ #[must_use]
+ pub fn new() -> Self {
+ Self {
+ adblock: JoinSet::new(),
+ domain: JoinSet::new(),
+ hosts: JoinSet::new(),
+ wildcard: JoinSet::new(),
+ }
+ }
+ /// Downloads the [`Adblock`] files from `urls` and adds them to [`ExternalFiles::adblock`]
+ /// with a timeout for each task set to `timeout`.
+ ///
+ /// Note that when `timeout` is `<` [`Self::MIN_TIMEOUT`], it is set to
+ /// `MIN_TIMEOUT`; similarly when it is `>` [`Self::MAX_TIMEOUT`], it is set
+ /// to `MAX_TIMEOUT`.
+ #[inline]
+ pub fn add_adblock(
+ &mut self,
+ client: &'static Client,
+ timeout: Duration,
+ urls: HashSet<HttpUrl>,
+ ) {
+ Self::get_external_files(&mut self.adblock, client, timeout, urls);
+ }
+ /// Downloads the [`DomainOnly`] files from `urls` and adds them to [`ExternalFiles::domain`]
+ /// with a timeout for each task set to `timeout`.
+ ///
+ /// Note that when `timeout` is `<` [`Self::MIN_TIMEOUT`], it is set to
+ /// `MIN_TIMEOUT`; similarly when it is `>` [`Self::MAX_TIMEOUT`], it is set
+ /// to `MAX_TIMEOUT`.
+ #[inline]
+ pub fn add_domain(
+ &mut self,
+ client: &'static Client,
+ timeout: Duration,
+ urls: HashSet<HttpUrl>,
+ ) {
+ Self::get_external_files(&mut self.domain, client, timeout, urls);
+ }
+ /// Downloads the [`Hosts`] files from `urls` and adds them to [`ExternalFiles::hosts`]
+ /// with a timeout for each task set to `timeout`.
+ ///
+ /// Note that when `timeout` is `<` [`Self::MIN_TIMEOUT`], it is set to
+ /// `MIN_TIMEOUT`; similarly when it is `>` [`Self::MAX_TIMEOUT`], it is set
+ /// to `MAX_TIMEOUT`.
+ #[inline]
+ pub fn add_hosts(
+ &mut self,
+ client: &'static Client,
+ timeout: Duration,
+ urls: HashSet<HttpUrl>,
+ ) {
+ Self::get_external_files(&mut self.hosts, client, timeout, urls);
+ }
+ /// Downloads the [`Wildcard`] files from `urls` and adds them to [`ExternalFiles::wildcard`]
+ /// with a timeout for each task set to `timeout`.
+ ///
+ /// Note that when `timeout` is `<` [`Self::MIN_TIMEOUT`], it is set to
+ /// `MIN_TIMEOUT`; similarly when it is `>` [`Self::MAX_TIMEOUT`], it is set
+ /// to `MAX_TIMEOUT`.
+ #[inline]
+ pub fn add_wildcard(
+ &mut self,
+ client: &'static Client,
+ timeout: Duration,
+ urls: HashSet<HttpUrl>,
+ ) {
+ Self::get_external_files(&mut self.wildcard, client, timeout, urls);
+ }
+ /// Downloads the files from `urls` and converts them to `String`s adding the
+ /// tasks to `set`.
+ #[inline]
+ fn get_external_files(
+ set: &mut JoinSet<Result<Result<File, ExtFileErr>, Elapsed>>,
+ client: &'static Client,
+ mut timeout: Duration,
+ urls: HashSet<HttpUrl>,
+ ) {
+ timeout = timeout.clamp(Self::MIN_TIMEOUT, Self::MAX_TIMEOUT);
+ for url in Into::<HashSet<HttpUrl>>::into(urls) {
+ let url_clone = url.clone();
+ set.spawn(time::timeout(timeout, async {
+ let resp = client
+ .get::<Url>(url_clone.into())
+ .send()
+ .await?
+ .error_for_status()?;
+ resp.bytes().await.map_or_else(
+ |err| Err(ExtFileErr::Http(err)),
+ |bytes| {
+ String::from_utf8(bytes.into())
+ .map_err(ExtFileErr::InvalidUtf8)
+ .map(|data| File {
+ name: Name::Url(url),
+ data,
+ })
+ },
+ )
+ }));
+ }
+ }
+}
+impl Default for ExternalFiles {
+ #[inline]
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -0,0 +1,45 @@
+//! # `rpz`
+//!
+//! The primary module is [`mod@dom`] which contains types which can parse
+//! [`str`]s into different kinds of domains.
+//!
+//! [`mod@file`] contains types that can read UTF-8 files on the local file system
+//! or hosted on HTTP(S) servers.
+//!
+//! The purpose of these types is to make fetching, parsing, and transforming
+//! ad-blocking files into a [response policy zone (RPZ)](https://en.wikipedia.org/wiki/Response_policy_zone)
+//! file easier.
+#![feature(addr_parse_ascii)]
+#![feature(btree_cursors)]
+#![feature(byte_slice_trim_ascii)]
+#![feature(io_error_more)]
+#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
+#![deny(
+ unsafe_code,
+ unused,
+ warnings,
+ clippy::all,
+ clippy::cargo,
+ clippy::complexity,
+ clippy::correctness,
+ clippy::nursery,
+ clippy::pedantic,
+ clippy::perf,
+ clippy::restriction,
+ clippy::style,
+ clippy::suspicious
+)]
+#![allow(
+ clippy::blanket_clippy_restriction_lints,
+ clippy::multiple_crate_versions,
+ clippy::single_call_fn
+)]
+/// Module for hostname-like domains including parsing [`str`]s
+/// from a variety of formats.
+pub mod dom;
+/// Contains a single function, `proper_subdomain_count`, that has a lot of lines
+/// that were pre-generated.
+mod dom_count_auto_gen;
+/// Module for fetching and parsing local and HTTP(S) files into
+/// [`dom::RpzDomain`]s.
+pub mod file;
diff --git a/src/main.rs b/src/main.rs
@@ -0,0 +1,546 @@
+//! # `rpz`
+//!
+//! Consult [`README.md`](https://crates.io/crates/rpz).
+#![feature(never_type)]
+#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
+#![deny(
+ unsafe_code,
+ unused,
+ warnings,
+ clippy::all,
+ clippy::cargo,
+ clippy::complexity,
+ clippy::correctness,
+ clippy::nursery,
+ clippy::pedantic,
+ clippy::perf,
+ clippy::restriction,
+ clippy::style,
+ clippy::suspicious
+)]
+#![allow(
+ clippy::blanket_clippy_restriction_lints,
+ clippy::implicit_return,
+ clippy::min_ident_chars,
+ clippy::missing_trait_methods,
+ clippy::multiple_crate_versions,
+ clippy::question_mark_used,
+ clippy::single_call_fn,
+ clippy::unseparated_literal_suffix
+)]
+/// Contains a wrapper of block and unblock `RpzDomain`s
+/// which can be used to write to a `File` or `stdout`.
+mod app;
+/// Module for reading and parsing passed arguments.
+mod args;
+/// Module for the TOML config file.
+mod config;
+#[allow(clippy::doc_markdown)]
+/// Contains functions for `pledge(2)` and `unveil(2)` on OpenBSD platforms when compiled
+/// with the `priv_sep` feature; otherwise almost all functions are no-ops.
+mod priv_sep;
+use crate::app::Domains;
+use crate::args::{ArgsErr, ConfigPath, Opts};
+use crate::config::Config;
+use core::fmt::{self, Display, Formatter};
+use core::time::Duration;
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+use priv_sep::UnveilErr;
+use reqwest::Client;
+use rpz::dom::DomainErr;
+use rpz::file::{AbsFilePath, ExtFileErr, ExternalFiles, Files, HttpUrl, LocalFiles, Summary};
+use std::collections::HashSet;
+use std::error::Error;
+use std::fs;
+use std::io::{self, Read, Write};
+use std::sync::OnceLock;
+use tokio::runtime::Builder;
+use toml::{self, de};
+/// The HTTP(S) client that is used to download all files.
+/// It is initialized exactly once in `main` before being used.
+static CLIENT: OnceLock<Client> = OnceLock::new();
+/// The output printed to `stdout` when `-h`/`--help` are passed
+/// to the program.
+const HELP: &str = "Response policy zone (RPZ) file generator
+
+Usage: rpz [OPTIONS]
+
+Options:
+ -V, --version Print version info and exit
+ -h, --help Print help
+ -q, --quiet Do not print messages
+ -v, --verbose Print summary parsing information about each file
+ -f, --file <CONFIG_FILE> Required option to pass '-' for stdin or the absolute path to the config file";
+/// The output printed to `stdout` when `-v`/`--version` are passed
+/// to the program.
+const VERSION: &str = concat!("rpz ", env!("CARGO_PKG_VERSION"));
+/// The User-Agent header value sent to HTTP(S) servers.
+const USER_AGENT: &str = concat!("rpz/", env!("CARGO_PKG_VERSION"));
+/// Error returned from the program.
+#[allow(clippy::exhaustive_enums)]
+enum E {
+ /// Variant for errors due to incorrect arguments being passed.
+ 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(UnveilErr),
+ /// 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,
+}
+impl fmt::Debug for E {
+ #[allow(clippy::ref_patterns)]
+ #[inline]
+ 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),
+ }
+ }
+}
+impl Display for E {
+ #[allow(clippy::ref_patterns)]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ 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"),
+ }
+ }
+}
+impl Error for E {}
+impl From<ArgsErr> for E {
+ #[inline]
+ fn from(value: ArgsErr) -> Self {
+ Self::Args(value)
+ }
+}
+impl From<de::Error> for E {
+ #[inline]
+ fn from(value: de::Error) -> Self {
+ Self::Config(value)
+ }
+}
+impl From<io::Error> for E {
+ #[inline]
+ fn from(value: io::Error) -> Self {
+ Self::Io(value)
+ }
+}
+impl From<ExtFileErr> for E {
+ #[inline]
+ fn from(value: ExtFileErr) -> Self {
+ Self::ExtFile(value)
+ }
+}
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+impl From<UnveilErr> for E {
+ #[inline]
+ fn from(value: UnveilErr) -> Self {
+ Self::Unveil(value)
+ }
+}
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+impl From<!> for E {
+ #[inline]
+ fn from(value: !) -> Self {
+ value
+ }
+}
+/// Reads `Config` from `conf`.
+#[inline]
+fn get_config(conf: ConfigPath) -> Result<Config, E> {
+ toml::from_str::<Config>(
+ match conf {
+ ConfigPath::Stdin => {
+ let mut file = String::new();
+ io::stdin().lock().read_to_string(&mut file)?;
+ file
+ }
+ ConfigPath::Path(path) => {
+ priv_sep::unveil_read_file(path.as_path())?;
+ let file = fs::read_to_string(path.as_path())?;
+ priv_sep::unveil_none(path)?;
+ file
+ }
+ }
+ .as_str(),
+ )
+ .map_err(E::Config)
+}
+/// Gets `LocalFiles` from `local_dir`.
+#[inline]
+fn get_local_files(local_dir: Option<AbsFilePath<true>>) -> Result<Option<LocalFiles>, E> {
+ local_dir.map_or_else(
+ || Ok(None),
+ |dir| {
+ priv_sep::unveil_read_dir(dir.as_path())
+ .map_err(E::from)
+ .and_then(|exists| {
+ if exists {
+ LocalFiles::from_path(dir.clone())
+ .map_err(E::Io)
+ .and_then(|files| {
+ priv_sep::unveil_none(dir).map_err(E::from).map(|()| files)
+ })
+ } else {
+ Ok(None)
+ }
+ })
+ },
+ )
+}
+/// Downloads block files from HTTP(S) servers.
+#[allow(clippy::unwrap_used)]
+#[inline]
+fn get_external_files(
+ timeout: Duration,
+ adblock: HashSet<HttpUrl>,
+ domain: HashSet<HttpUrl>,
+ hosts: HashSet<HttpUrl>,
+ wildcard: HashSet<HttpUrl>,
+) -> Result<Files, E> {
+ Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .map_or_else(
+ |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();
+ let client = CLIENT.get().unwrap();
+ files.add_adblock(client, timeout, adblock);
+ files.add_domain(client, timeout, domain);
+ files.add_hosts(client, timeout, hosts);
+ files.add_wildcard(client, timeout, wildcard);
+ files
+ })
+ .await
+ .map_err(E::ExtFile)
+ })
+ },
+ )
+}
+/// Verbosity of what is written to `stdout`.
+#[derive(Clone, Copy)]
+enum Verbosity {
+ /// Suppress all summary info.
+ None,
+ /// Write normal amount of info.
+ Normal,
+ /// Write verbose information.
+ High,
+}
+/// Writes to `stdout` the summary information in the event the quiet
+/// option was not passed.
+#[inline]
+fn write_summary(
+ summaries: Vec<Summary<'_, DomainErr>>,
+ verbose: bool,
+ unblock_count: usize,
+ block_count: usize,
+) -> Result<(), io::Error> {
+ let mut stdout = io::stdout().lock();
+ let mut domain_count = 0usize;
+ let mut comment_count = 0usize;
+ let mut blank_count = 0usize;
+ let mut error_count = 0usize;
+ if verbose {
+ summaries.into_iter().try_fold((), |(), summary| {
+ domain_count = domain_count.saturating_add(summary.domain_count);
+ comment_count = comment_count.saturating_add(summary.comment_count);
+ blank_count = blank_count.saturating_add(summary.blank_count);
+ error_count = error_count.saturating_add(summary.errors.values().sum());
+ writeln!(&mut stdout, "{summary}")
+ })?;
+ } else {
+ summaries.into_iter().fold((), |(), summary| {
+ domain_count = domain_count.saturating_add(summary.domain_count);
+ comment_count = comment_count.saturating_add(summary.comment_count);
+ blank_count = blank_count.saturating_add(summary.blank_count);
+ error_count = error_count.saturating_add(summary.errors.values().sum());
+ });
+ }
+ writeln!(
+ stdout,
+ "unblock count written: {}\nblock count written: {}\ntotal lines written: {}\ndomains parsed: {}\ncomments parsed: {}\nblanks parsed: {}\nparsing errors: {}",
+ unblock_count,
+ block_count,
+ unblock_count.saturating_add(block_count),
+ domain_count,
+ comment_count,
+ blank_count,
+ error_count,
+ )
+}
+#[allow(clippy::arithmetic_side_effects, clippy::unwrap_used)]
+fn main() -> Result<(), E> {
+ let mut promises = priv_sep::pledge_init()?;
+ priv_sep::veil_all()?;
+ let (conf, verbosity) = match Opts::from_args()? {
+ Opts::Help => return writeln!(io::stdout().lock(), "{HELP}").map_err(E::Io),
+ Opts::Version => return writeln!(io::stdout().lock(), "{VERSION}").map_err(E::Io),
+ Opts::Config(path) => (path, Verbosity::Normal),
+ Opts::ConfigQuiet(path) => (path, Verbosity::None),
+ Opts::ConfigVerbose(path) => (path, Verbosity::High),
+ Opts::Verbose | Opts::Quiet => return Err(E::Args(ArgsErr::ConfigPathNotPassed)),
+ Opts::None => return Err(E::Args(ArgsErr::NoArgs)),
+ };
+ let config = get_config(conf)?;
+ // We use a temp file to write to that way we can avoid overwriting
+ // a file just for the process to error. Once the temp file is written to,
+ // we rename it to the desired name.
+ let tmp_rpz = config.rpz.as_ref().map_or_else(
+ || {
+ priv_sep::pledge_away_create_write(&mut promises)
+ .map_err(E::from)
+ .map(|()| None)
+ },
+ |file| {
+ priv_sep::unveil_create(file.as_path())
+ .and_then(|()| {
+ let mut rpz = file.clone();
+ rpz.append("tmp");
+ priv_sep::unveil_create_read_write(rpz.as_path()).map(|()| Some(rpz))
+ })
+ .map_err(E::from)
+ },
+ )?;
+ let local_files = get_local_files(config.local_dir)?;
+ let (mut domains, mut summaries) = if let Some(files) = local_files.as_ref() {
+ Domains::new_with(files)
+ } else {
+ (
+ Domains::new(),
+ Vec::with_capacity(
+ config.adblock.len()
+ + config.domain.len()
+ + config.hosts.len()
+ + config.wildcard.len(),
+ ),
+ )
+ };
+ let ext_files = if config.adblock.is_empty()
+ && config.domain.is_empty()
+ && config.hosts.is_empty()
+ && config.wildcard.is_empty()
+ {
+ if domains.block().is_empty() {
+ return Err(E::NoBlockEntries);
+ }
+ priv_sep::pledge_away_net(&mut promises)
+ .and_then(|()| priv_sep::pledge_away_unveil(&mut promises).map(|()| Files::new()))
+ .map_err(E::from)
+ } else {
+ priv_sep::unveil_https().map_err(E::from).and_then(|()| {
+ priv_sep::pledge_away_unveil(&mut promises)
+ .map_err(E::from)
+ .and_then(|()| {
+ get_external_files(
+ config.timeout.unwrap_or(ExternalFiles::MAX_TIMEOUT),
+ config.adblock,
+ config.domain,
+ config.hosts,
+ config.wildcard,
+ )
+ .and_then(|files| {
+ priv_sep::pledge_away_net(&mut promises)
+ .map_err(E::from)
+ .map(|()| files)
+ })
+ })
+ })
+ }?;
+ domains.add_block_files(&ext_files, &mut summaries);
+ if domains.block().is_empty() {
+ return Err(E::NoBlockEntries);
+ }
+ let (unblock_count, block_count) =
+ domains.write(config.rpz.map(|file| (file, tmp_rpz.unwrap())))?;
+ if matches!(verbosity, Verbosity::None) {
+ Ok(())
+ } else {
+ priv_sep::pledge_away_all_but_stdio(&mut promises)?;
+ write_summary(
+ summaries,
+ matches!(verbosity, Verbosity::High),
+ unblock_count,
+ block_count,
+ )
+ .map_err(E::Io)
+ }
+}
+#[cfg(test)]
+pub(crate) mod test_prog {
+ use std::fs;
+ use std::process::Command;
+ /// The path to the program dir with no block subdirectories.
+ pub const PROG_DIR_NO_SUB: &str = "/home/zack/projects/rpz/target/";
+ /// The path to the program dir with block subdirectories.
+ pub const PROG_DIR: &str = "/home/zack/projects/rpz/target/release/";
+ /// The path to the program.
+ const PROG: &str = "/home/zack/projects/rpz/target/release/rpz";
+ /// Message to append to the appropriate variable above if an issue occurs.
+ pub const ERR_MSG: &str = " does not exist, so program testing cannot occur";
+ /// Verify the correct directories and files exist.
+ pub fn verify_files() {
+ if !fs::metadata(PROG)
+ .expect(format!("{PROG}{ERR_MSG}").as_str())
+ .is_file()
+ {
+ panic!("{PROG} is not an executable file")
+ } else if !fs::metadata(PROG_DIR)
+ .expect(format!("{PROG_DIR}{ERR_MSG}").as_str())
+ .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())
+ .is_dir()
+ {
+ panic!("{PROG_DIR_NO_SUB} is not a directory")
+ } else {
+ let mut files = String::from(PROG_DIR);
+ 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())
+ .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(),
+ )
+ .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(),
+ )
+ .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(),
+ )
+ .is_file()
+ {
+ panic!("{files} is not a file");
+ }
+ } else {
+ panic!("{files} is not a file");
+ }
+ } else {
+ panic!("{files} is not a file");
+ }
+ } else {
+ panic!("{files} is not a file");
+ }
+ }
+ }
+ pub fn get_command() -> Command {
+ Command::new(PROG)
+ }
+}
+#[cfg(test)]
+mod tests {
+ use crate::{test_prog, E};
+ use std::io::Write;
+ use std::process::Stdio;
+ use std::thread;
+ #[test]
+ #[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)
+ })
+ .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())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .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).as_bytes())
+ .map_or(false, |_| true)
+ })
+ .join()
+ .map_or(false, |v| v)
+ }) && cmd.wait_with_output().map_or(false, |output| {
+ output.status.success()
+ && output.stdout
+ == b"www.bar.com CNAME rpz-passthru.
+bar.com CNAME .
+*.bar.com CNAME .
+www.example.com CNAME .
+(Hosts) /home/zack/projects/rpz/target/release/unblock/hosts/foo - domains parsed: 1, comments parsed: 0, blanks parsed: 0, parsing errors: 0
+(Wildcard) /home/zack/projects/rpz/target/release/unblock/wildcard/foo - domains parsed: 1, comments parsed: 0, blanks parsed: 0, parsing errors: 0
+(Adblock) /home/zack/projects/rpz/target/release/block/adblock/foo - domains parsed: 1, comments parsed: 0, blanks parsed: 0, parsing errors: 0
+(Domain-only) /home/zack/projects/rpz/target/release/block/domain/foo - domains parsed: 1, comments parsed: 0, blanks parsed: 0, parsing errors: 0
+unblock count written: 1
+block count written: 3
+total lines written: 4
+domains parsed: 4
+comments parsed: 0
+blanks parsed: 0
+parsing errors: 0\n"
+ })
+ }));
+ }
+}
diff --git a/src/priv_sep.rs b/src/priv_sep.rs
@@ -0,0 +1,220 @@
+#![allow(clippy::implicit_return, clippy::pub_use, clippy::ref_patterns)]
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+pub use priv_sep::UnveilErr;
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+use priv_sep::{self, Permissions, Promise, Promises};
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+use std::env;
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+use std::fs;
+use std::io::{Error, ErrorKind};
+use std::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 struct Zst;
+/// Calls `pledge` with only the sys calls necessary for a minimal application
+/// to run. Specifically, the `Promise`s `Cpath`, `Inet`, `Rpath`, `Stdio`, `Unveil`, and `Wpath`
+/// are passed.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn pledge_init() -> Result<Promises<6>, Error> {
+ let promises = Promises::new([
+ Promise::Cpath,
+ Promise::Inet,
+ Promise::Rpath,
+ Promise::Stdio,
+ Promise::Unveil,
+ Promise::Wpath,
+ ]);
+ match promises.pledge() {
+ Ok(()) => Ok(promises),
+ Err(e) => Err(e),
+ }
+}
+/// No-op that always returns `Ok`.
+#[allow(clippy::unnecessary_wraps)]
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[inline]
+pub const fn pledge_init() -> Result<Zst, !> {
+ Ok(Zst)
+}
+/// Removes `Cpath` and `Wpath` `Promise`s.
+///
+/// This should only be called when `stdout` is written to
+/// instead of an RPZ file.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn pledge_away_create_write(promises: &mut Promises<6>) -> Result<(), Error> {
+ promises.remove_promises([Promise::Cpath, Promise::Wpath]);
+ promises.pledge()
+}
+/// No-op that always returns `Ok`.
+#[allow(clippy::unnecessary_wraps)]
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[inline]
+pub fn pledge_away_create_write(_: &mut Zst) -> Result<(), !> {
+ Ok(())
+}
+/// Removes `Promise::Unveil`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn pledge_away_unveil(promises: &mut Promises<6>) -> Result<(), Error> {
+ promises.remove(Promise::Unveil);
+ promises.pledge()
+}
+/// No-op that always returns `Ok`.
+#[allow(clippy::unnecessary_wraps)]
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[inline]
+pub fn pledge_away_unveil(_: &mut Zst) -> Result<(), !> {
+ Ok(())
+}
+/// Removes `Promise::Inet`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn pledge_away_net(promises: &mut Promises<6>) -> Result<(), Error> {
+ promises.remove(Promise::Inet);
+ promises.pledge()
+}
+/// No-op that always returns `Ok`.
+#[allow(clippy::unnecessary_wraps)]
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[inline]
+pub fn pledge_away_net(_: &mut Zst) -> Result<(), !> {
+ Ok(())
+}
+/// Removes all `Promise`s except `Stdio`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn pledge_away_all_but_stdio(promises: &mut Promises<6>) -> Result<(), Error> {
+ promises.retain([Promise::Stdio]);
+ promises.pledge()
+}
+/// No-op that always returns `Ok`.
+#[allow(clippy::unnecessary_wraps)]
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[inline]
+pub 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"))]
+#[inline]
+pub fn unveil_none<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
+ Permissions::NONE.unveil(path)
+}
+/// No-op that always returns `Ok`.
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[allow(clippy::unnecessary_wraps)]
+#[inline]
+pub fn unveil_none<P: AsRef<Path>>(_: P) -> Result<(), !> {
+ Ok(())
+}
+/// Calls `unveil_none` on `/`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn veil_all() -> Result<(), UnveilErr> {
+ unveil_none("/")
+}
+/// No-op that always returns `Ok`.
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[allow(clippy::unnecessary_wraps)]
+#[inline]
+pub const fn veil_all() -> Result<(), !> {
+ Ok(())
+}
+/// Calls `unveil`_on `path` with `Permissions::CREATE`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn unveil_create<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
+ Permissions::CREATE.unveil(path)
+}
+/// No-op that always returns `Ok`.
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[allow(clippy::unnecessary_wraps)]
+#[inline]
+pub fn unveil_create<P: AsRef<Path>>(_: P) -> Result<(), !> {
+ Ok(())
+}
+/// Calls `unveil`_on `path` with `Permissions::READ` and returns
+/// `true` iff `path` exists.
+#[allow(clippy::wildcard_enum_match_arm)]
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, UnveilErr> {
+ Permissions::READ.unveil(path).map_or_else(
+ |err| match err {
+ UnveilErr::Io(ref err2) => match err2.kind() {
+ ErrorKind::NotFound => Ok(false),
+ _ => Err(err),
+ },
+ UnveilErr::Nul(_) => Err(err),
+ },
+ |()| Ok(true),
+ )
+}
+/// Returns `true` iff `path` exists
+#[allow(clippy::wildcard_enum_match_arm)]
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[inline]
+pub fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, Error> {
+ fs::metadata(path).map_or_else(
+ |err| match err.kind() {
+ ErrorKind::NotFound => Ok(false),
+ _ => Err(err),
+ },
+ |dir| Ok(dir.is_dir()),
+ )
+}
+/// Calls `unveil`_on `path` with `Permissions::READ`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn unveil_read_file<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
+ Permissions::READ.unveil(path)
+}
+/// No-op that always returns `Ok`.
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[allow(clippy::unnecessary_wraps)]
+#[inline]
+pub fn unveil_read_file<P: AsRef<Path>>(_: P) -> Result<(), !> {
+ Ok(())
+}
+/// Calls `unveil` on all files necessary for HTTP(S) with `Permissions::READ`
+/// 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/`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn unveil_https() -> Result<(), UnveilErr> {
+ /// The path to the root certificate store.
+ const CERTS: &str = "/etc/ssl/cert.pem";
+ unveil_read_file("/dev/urandom").and_then(|()| {
+ unveil_read_file("/etc/resolv.conf")
+ .and_then(|()| unveil_read_file(CERTS).map(|()| env::set_var("SSL_CERT_FILE", CERTS)))
+ })
+}
+/// No-op that always returns `Ok`.
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[allow(clippy::unnecessary_wraps)]
+#[inline]
+pub const fn unveil_https() -> Result<(), !> {
+ Ok(())
+}
+/// Calls `unveil`_on `path` with create, read, and write `Permissions`.
+#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
+#[inline]
+pub fn unveil_create_read_write<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
+ let mut perms = Permissions::ALL;
+ perms.execute = false;
+ perms.unveil(path)
+}
+/// No-op that always returns `Ok`.
+#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
+#[allow(clippy::unnecessary_wraps)]
+#[inline]
+pub fn unveil_create_read_write<P: AsRef<Path>>(_: P) -> Result<(), !> {
+ Ok(())
+}