dom.rs (68143B)
1 use crate::dom_count_auto_gen::proper_subdomain_count; 2 use ascii_domain::{ 3 char_set::{ASCII_FIREFOX, AllowedAscii}, 4 dom::{Domain, DomainErr, DomainOrdering}, 5 }; 6 use core::{ 7 borrow::Borrow, 8 cmp::Ordering, 9 convert, 10 fmt::{self, Display, Formatter}, 11 hash::{Hash, Hasher}, 12 num::NonZeroU8, 13 ops::Deref, 14 str, 15 }; 16 use num_bigint::BigUint; 17 use std::{ 18 error, 19 io::{Error, Write}, 20 }; 21 use superset_map::SetOrd; 22 use zfc::{BoundedCardinality, Cardinality, Set}; 23 /// Unit tests. 24 #[cfg(test)] 25 mod tests; 26 /// One. 27 const ONE: NonZeroU8 = NonZeroU8::new(1).unwrap(); 28 /// Error returned when an invalid string is passed to [`Adblock::parse_value`], [`DomainOnly::parse_value`], 29 /// [`Hosts::parse_value`], [`Wildcard::parse_value`], or [`RpzDomain::parse_value`]. 30 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 31 pub enum FirefoxDomainErr { 32 /// The domain is invalid based on [`Domain`] using [`ASCII_FIREFOX`]. 33 InvalidDomain(DomainErr), 34 /// The domain had a TLD that was not all letters nor length of at least five beginning with `b"xn--"`. 35 InvalidTld, 36 /// The string passed to [`Adblock::parse_value`] contained `$`. 37 InvalidAdblockDomain, 38 /// The string passed to [`Hosts::parse_value`] did not conform 39 /// to the required [`Hosts`] format. 40 InvalidHostsIP, 41 /// The length of the non-wildcard portion of the string passed to 42 /// [`Wildcard::parse_value`] was at least 252 which means there are 43 /// no proper subdomains. 44 InvalidWildcardDomain, 45 } 46 impl Display for FirefoxDomainErr { 47 #[inline] 48 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 49 match *self { 50 Self::InvalidDomain(err) => err.fmt(f), 51 Self::InvalidTld => f.write_str("domain had a TLD that was not all letters nor at least five characters long starting with 'xn--'"), 52 Self::InvalidAdblockDomain => f.write_str("Adblock-style domain contained a '$'"), 53 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"), 54 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"), 55 } 56 } 57 } 58 impl error::Error for FirefoxDomainErr {} 59 /// The ASCII we allow domains to have. 60 const CHARS: &AllowedAscii<[u8; 78]> = &ASCII_FIREFOX; 61 /// Parses a `[u8]` into a `Domain` using `CHARS` with the added restriction that the `Domain` has a TLD 62 /// that is either all letters or has length of at least five and begins with `b"xn--"`. 63 #[expect(clippy::indexing_slicing, reason = "we verify manually")] 64 fn domain_icann_tld<'a: 'b, 'b>(val: &'a [u8]) -> Result<Domain<&'b str>, FirefoxDomainErr> { 65 Domain::try_from_bytes(val, CHARS) 66 .map_err(FirefoxDomainErr::InvalidDomain) 67 .and_then(|dom| { 68 let tld = dom.tld(); 69 // `tld.as_bytes()[..4]` won't panic since we check before that that the length is at least 5. 70 if tld.is_alphabetic() || (tld.len().get() > 4 && tld.as_bytes()[..4] == *b"xn--") { 71 Ok(dom.into()) 72 } else { 73 Err(FirefoxDomainErr::InvalidTld) 74 } 75 }) 76 } 77 /// Action taken by a DNS server when a domain matches. 78 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 79 pub enum RpzAction { 80 /// Send `NXDOMAIN` reply. 81 Nxdomain, 82 /// Send `NODATA` reply. 83 Nodata, 84 /// Do nothing; continue as normal. 85 Passthru, 86 /// Drop the query. 87 Drop, 88 /// Answer over TCP. 89 TcpOnly, 90 } 91 impl Display for RpzAction { 92 #[inline] 93 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 94 match *self { 95 Self::Nxdomain => f.write_str("NXDOMAIN"), 96 Self::Nodata => f.write_str("NODATA"), 97 Self::Passthru => f.write_str("PASSTHRU"), 98 Self::Drop => f.write_str("DROP"), 99 Self::TcpOnly => f.write_str("TCP-Only"), 100 } 101 } 102 } 103 impl PartialEq<&Self> for RpzAction { 104 #[inline] 105 fn eq(&self, other: &&Self) -> bool { 106 *self == **other 107 } 108 } 109 impl PartialEq<RpzAction> for &RpzAction { 110 #[inline] 111 fn eq(&self, other: &RpzAction) -> bool { 112 **self == *other 113 } 114 } 115 /// Writes the following line with `writer` based on `action`: 116 /// * `RpzAction::Nxdomain`: `<dom> CNAME .`. 117 /// * `RpzAction::Nodata`: `<dom> CNAME *.`. 118 /// * `RpzAction::Passthru`: `<dom> CNAME rpz-passthru.`. 119 /// * `RpzAction::Drop`: `<dom> CNAME rpz-drop.`. 120 /// * `RpzAction::TcpOnly`: `<dom> CNAME rpz-tcp-only.`. 121 /// 122 /// `*.` is prepended to `<dom>` iff `wildcard`. 123 /// 124 /// # Errors 125 /// 126 /// Returns [`Error`] iff [`writeln`] does. 127 #[inline] 128 pub fn write_rpz_line<W: Write, T>( 129 mut writer: W, 130 dom: &Domain<T>, 131 action: RpzAction, 132 wildcard: bool, 133 ) -> Result<(), Error> 134 where 135 Domain<T>: Display, 136 { 137 writeln!( 138 writer, 139 "{}{} CNAME {}.", 140 if wildcard { "*." } else { "" }, 141 dom, 142 match action { 143 RpzAction::Nxdomain => "", 144 RpzAction::Nodata => "*", 145 RpzAction::Passthru => "rpz-passthru", 146 RpzAction::Drop => "rpz-drop", 147 RpzAction::TcpOnly => "rpz-tcp-only", 148 } 149 ) 150 } 151 /// Type that can be returned by [`Domain`]-like parsers (e.g., [`Adblock`]). 152 #[derive(Clone, Copy, Debug)] 153 pub enum Value<'a, T: ParsedDomain<'a>> { 154 /// The parsed value is a domain. 155 Domain(T), 156 /// The parsed value is a comment. 157 Comment(&'a str), 158 /// The parsed value is blank or just [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace). 159 Blank, 160 } 161 impl<'a, T: ParsedDomain<'a>> Value<'a, T> { 162 /// Returns `true` iff `self` is a [`Self::Domain`]. 163 #[inline] 164 pub const fn is_domain(&self) -> bool { 165 match *self { 166 Self::Domain(_) => true, 167 Self::Comment(_) | Self::Blank => false, 168 } 169 } 170 /// Returns `true` iff `self` is a [`Self::Comment`]. 171 #[inline] 172 pub const fn is_comment(&self) -> bool { 173 match *self { 174 Self::Comment(_) => true, 175 Self::Domain(_) | Self::Blank => false, 176 } 177 } 178 /// Returns `true` iff `self` is a [`Self::Blank`]. 179 #[inline] 180 pub const fn is_blank(&self) -> bool { 181 matches!(*self, Value::Blank) 182 } 183 /// Returns the contained [`Self::Domain`] value. 184 /// 185 /// # Panics 186 /// 187 /// Panics iff `self` is [`Self::Comment`] or [`Self::Blank`]. 188 #[expect(clippy::panic, reason = "bug if called incorrectly")] 189 #[inline] 190 pub fn unwrap_domain(self) -> T { 191 match self { 192 Self::Domain(dom) => dom, 193 Self::Comment(_) | Self::Blank => { 194 panic!("called `ParsedDomain::unwrap_domain()` on a `Comment` or `Blank` value") 195 } 196 } 197 } 198 /// Returns the contained [`prim@str`] in [`Self::Comment`]. 199 /// 200 /// # Panics 201 /// 202 /// Panics iff `self` is [`Self::Domain`] or [`Self::Blank`]. 203 #[expect(clippy::panic, reason = "bug if called incorrectly")] 204 #[inline] 205 pub fn unwrap_comment(self) -> &'a str { 206 match self { 207 Self::Comment(com) => com, 208 Self::Domain(_) | Self::Blank => { 209 panic!("called `ParsedDomain::unwrap_comment()` on a `Domain` or `Blank` value") 210 } 211 } 212 } 213 /// Returns [`unit`] when `self` is [`Self::Blank`]. 214 /// 215 /// # Panics 216 /// 217 /// Panics iff `self` is [`Self::Domain`] or [`Self::Comment`]. 218 #[expect(clippy::panic, reason = "bug if called incorrectly")] 219 #[inline] 220 pub fn unwrap_blank(self) { 221 match self { 222 Self::Blank => {} 223 Self::Domain(_) | Self::Comment(_) => { 224 panic!("called `ParsedDomain::unwrap_blank()` on a `Domain` or `Comment` value") 225 } 226 } 227 } 228 } 229 /// Structure of a [`Domain`]-like type that can parse [`prim@str`]s into [`Value`]s. 230 /// 231 /// When parsed into a [`Value::Domain`], the domain can be written to a 232 /// [response policy zone (RPZ)](https://en.wikipedia.org/wiki/Response_policy_zone) file. 233 pub trait ParsedDomain<'a>: Sized { 234 /// The error returned from [`Self::parse_value`]. 235 type Error; 236 /// Parses a `str` into a `Value`. 237 /// # Errors 238 /// 239 /// Errors iff `val` is unable to be parsed into a `Value`. 240 fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error>; 241 /// Reference to the contained `Domain`. 242 fn domain(&self) -> &Domain<&'a str>; 243 /// Writes `self` as RPZ lines via `writer`. 244 /// 245 /// # Errors 246 /// 247 /// Errors iff `writer` errors. 248 fn write_to_rpz<W: Write>(&self, action: RpzAction, writer: W) -> Result<(), Error>; 249 } 250 /// Domain constructed from an 251 /// [Adblock-style rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#adblock-style-syntax). 252 /// 253 /// Specifically the domain must conform to the following extended regex: 254 /// 255 /// `^<ws>*(\|\|)?<ws>*<domain><ws>*\^?<ws>*$` 256 /// 257 /// where `<domain>` conforms to a valid [`Domain`] based on [`ASCII_FIREFOX`] with the added requirement that it 258 /// does not contain `$`, the TLD is either all letters or at least length five and begins with `xn--`, and `<ws>` is any sequence of 259 /// [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace). 260 /// 261 /// Comments are any lines that start with `!` or `#` (ignoring whitespace). Any in-line comments after a valid 262 /// domain are ignored and will be parsed into a [`Value::Domain`]. 263 /// 264 /// Note that this means some valid Adblock-style rules are not considered valid since such rules often contain 265 /// path information or modifiers (e.g., “third-party”), but this only considers domain-only rules. 266 #[derive(Clone, Debug)] 267 pub struct Adblock<'a> { 268 /// The `Domain`. 269 domain: Domain<&'a str>, 270 /// `true` iff `domain` represents all subdomains. Note that this includes `domain` itself. 271 subdomains: bool, 272 } 273 impl Adblock<'_> { 274 /// Returns `true` iff the contained [`Domain`] represents all subdomains. Note this includes the 275 /// `Domain` itself. 276 #[inline] 277 #[must_use] 278 pub const fn is_subdomains(&self) -> bool { 279 self.subdomains 280 } 281 /// Since `DomainOnly` and `Hosts` are treated the same, we have this helper function that can be used 282 /// for both. 283 #[must_use] 284 fn cmp_dom(&self, other: &Domain<&str>) -> Ordering { 285 match self.domain.cmp_by_domain_ordering(other) { 286 DomainOrdering::Less => Ordering::Less, 287 DomainOrdering::Shorter => { 288 if self.subdomains { 289 Ordering::Greater 290 } else { 291 Ordering::Less 292 } 293 } 294 DomainOrdering::Equal => { 295 if self.subdomains { 296 Ordering::Greater 297 } else { 298 Ordering::Equal 299 } 300 } 301 DomainOrdering::Longer | DomainOrdering::Greater => Ordering::Greater, 302 } 303 } 304 /// The total order that is defined follows the following hierarchy: 305 /// 1. Pairwise comparisons of each [`ascii_domain::dom::Label`] starting from the TLDs. 306 /// 2. If 1. evaluates as not equivalent, then return the result. 307 /// 3. If `self` represents a single `Domain` (i.e., `!self.is_subdomains()`), 308 /// then return the comparison of label counts. 309 /// 4. `self` is greater. 310 /// 311 /// For example, `com` `<` `example.com` `<` `||example.com` `<` `||com` `<` `net` `<` `example.net` `<` `||example.net` `<` `||net`. 312 #[inline] 313 #[must_use] 314 pub fn cmp_domain_only(&self, other: &DomainOnly<'_>) -> Ordering { 315 self.cmp_dom(&other.domain) 316 } 317 /// Same as [`Adblock::cmp_domain_only`]. 318 #[inline] 319 #[must_use] 320 pub fn cmp_hosts(&self, other: &Hosts<'_>) -> Ordering { 321 self.cmp_dom(&other.domain) 322 } 323 /// The total order that is defined follows the following hierarchy: 324 /// 1. Pairwise comparisons of each [`ascii_domain::dom::Label`] starting from the TLDs. 325 /// 2. If 1. evaluates as not equivalent, then return the result. 326 /// 3. If both domains represent a single `Domain`, then return the comparison 327 /// of label counts. 328 /// 4. If one domain represents a single `Domain`, then return that that domain is less. 329 /// 5. If the label counts are the same, `self` is greater. 330 /// 6. Return the inverse of the comparison of label counts. 331 /// 332 /// For example the following is a sequence of domains in 333 /// ascending order: 334 /// 335 /// `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` 336 #[inline] 337 #[must_use] 338 pub fn cmp_wildcard(&self, other: &Wildcard<'_>) -> Ordering { 339 match self.domain.cmp_by_domain_ordering(&other.domain) { 340 DomainOrdering::Less => Ordering::Less, 341 DomainOrdering::Shorter => { 342 if self.subdomains { 343 Ordering::Greater 344 } else { 345 Ordering::Less 346 } 347 } 348 DomainOrdering::Equal => { 349 if self.subdomains { 350 Ordering::Greater 351 } else if other.proper_subdomains { 352 Ordering::Less 353 } else { 354 Ordering::Equal 355 } 356 } 357 DomainOrdering::Longer => { 358 if self.subdomains { 359 if other.proper_subdomains { 360 Ordering::Less 361 } else { 362 Ordering::Greater 363 } 364 } else if other.proper_subdomains { 365 Ordering::Less 366 } else { 367 Ordering::Greater 368 } 369 } 370 DomainOrdering::Greater => Ordering::Greater, 371 } 372 } 373 /// Same as [`Adblock::cardinality`] except that a `BigUint` is returned. Note the count _includes_ 374 /// the `Domain` itself when `self.is_subdomains()`. 375 /// 376 /// `!self.is_subdomains()` ⇔ `self.domain_count() == BigUint::new(vec![1])`. 377 #[expect(clippy::arithmetic_side_effects, reason = "arbitrary-sized arithmetic")] 378 #[inline] 379 #[must_use] 380 pub fn domain_count(&self) -> BigUint { 381 if self.subdomains { 382 proper_subdomain_count(&self.domain) + BigUint::new(vec![1]) 383 } else { 384 BigUint::new(vec![1]) 385 } 386 } 387 } 388 impl Display for Adblock<'_> { 389 #[inline] 390 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 391 write!( 392 f, 393 "{}{}", 394 if self.subdomains { "||" } else { "" }, 395 self.domain 396 ) 397 } 398 } 399 impl PartialEq<Adblock<'_>> for Adblock<'_> { 400 #[inline] 401 fn eq(&self, other: &Adblock<'_>) -> bool { 402 self.domain == other.domain && self.subdomains == other.subdomains 403 } 404 } 405 impl PartialEq<&Adblock<'_>> for Adblock<'_> { 406 #[inline] 407 fn eq(&self, other: &&Adblock<'_>) -> bool { 408 *self == **other 409 } 410 } 411 impl PartialEq<Adblock<'_>> for &Adblock<'_> { 412 #[inline] 413 fn eq(&self, other: &Adblock<'_>) -> bool { 414 **self == *other 415 } 416 } 417 impl PartialEq<DomainOnly<'_>> for Adblock<'_> { 418 #[inline] 419 fn eq(&self, other: &DomainOnly<'_>) -> bool { 420 !self.subdomains && self.domain == other.domain 421 } 422 } 423 impl PartialEq<&DomainOnly<'_>> for Adblock<'_> { 424 #[inline] 425 fn eq(&self, other: &&DomainOnly<'_>) -> bool { 426 *self == **other 427 } 428 } 429 impl PartialEq<DomainOnly<'_>> for &Adblock<'_> { 430 #[inline] 431 fn eq(&self, other: &DomainOnly<'_>) -> bool { 432 **self == *other 433 } 434 } 435 impl PartialEq<&Adblock<'_>> for DomainOnly<'_> { 436 #[inline] 437 fn eq(&self, other: &&Adblock<'_>) -> bool { 438 *self == **other 439 } 440 } 441 impl PartialEq<Adblock<'_>> for &DomainOnly<'_> { 442 #[inline] 443 fn eq(&self, other: &Adblock<'_>) -> bool { 444 **self == *other 445 } 446 } 447 impl PartialEq<Hosts<'_>> for Adblock<'_> { 448 #[inline] 449 fn eq(&self, other: &Hosts<'_>) -> bool { 450 !self.subdomains && self.domain == other.domain 451 } 452 } 453 impl PartialEq<&Hosts<'_>> for Adblock<'_> { 454 #[inline] 455 fn eq(&self, other: &&Hosts<'_>) -> bool { 456 *self == **other 457 } 458 } 459 impl PartialEq<Hosts<'_>> for &Adblock<'_> { 460 #[inline] 461 fn eq(&self, other: &Hosts<'_>) -> bool { 462 **self == *other 463 } 464 } 465 impl PartialEq<&Adblock<'_>> for Hosts<'_> { 466 #[inline] 467 fn eq(&self, other: &&Adblock<'_>) -> bool { 468 *self == **other 469 } 470 } 471 impl PartialEq<Adblock<'_>> for &Hosts<'_> { 472 #[inline] 473 fn eq(&self, other: &Adblock<'_>) -> bool { 474 **self == *other 475 } 476 } 477 impl PartialEq<Wildcard<'_>> for Adblock<'_> { 478 #[expect(clippy::suspicious_operation_groupings, reason = "false positive")] 479 #[inline] 480 fn eq(&self, other: &Wildcard<'_>) -> bool { 481 !(self.subdomains || other.proper_subdomains) && self.domain == other.domain 482 } 483 } 484 impl PartialEq<&Wildcard<'_>> for Adblock<'_> { 485 #[inline] 486 fn eq(&self, other: &&Wildcard<'_>) -> bool { 487 *self == **other 488 } 489 } 490 impl PartialEq<Wildcard<'_>> for &Adblock<'_> { 491 #[inline] 492 fn eq(&self, other: &Wildcard<'_>) -> bool { 493 **self == *other 494 } 495 } 496 impl PartialEq<&Adblock<'_>> for Wildcard<'_> { 497 #[inline] 498 fn eq(&self, other: &&Adblock<'_>) -> bool { 499 *self == **other 500 } 501 } 502 impl PartialEq<Adblock<'_>> for &Wildcard<'_> { 503 #[inline] 504 fn eq(&self, other: &Adblock<'_>) -> bool { 505 **self == *other 506 } 507 } 508 impl Eq for Adblock<'_> {} 509 impl Hash for Adblock<'_> { 510 #[inline] 511 fn hash<H: Hasher>(&self, state: &mut H) { 512 self.domain.hash(state); 513 } 514 } 515 impl PartialOrd<Adblock<'_>> for Adblock<'_> { 516 #[inline] 517 fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> { 518 Some(self.cmp(other)) 519 } 520 } 521 impl Ord for Adblock<'_> { 522 /// The total order that is defined follows the following hierarchy: 523 /// 1. Pairwise comparisons of each [`ascii_domain::dom::Label`] starting from the TLDs. 524 /// 2. If 1. evaluates as not equivalent, then return the result. 525 /// 3. If both domains represent a single `Domain`, then return the comparison 526 /// of label counts. 527 /// 4. If one domain represents a single `Domain`, then return that that domain is less. 528 /// 5. Return the inverse of the comparison of label counts. 529 /// 530 /// For example, `com` `<` `example.com` `<` `||example.com` `<` `||com` `<` `net` `<` `example.net` `<` `||example.net` `<` `||net`. 531 #[inline] 532 fn cmp(&self, other: &Self) -> Ordering { 533 match self.domain.cmp_by_domain_ordering(&other.domain) { 534 DomainOrdering::Less => Ordering::Less, 535 DomainOrdering::Shorter => { 536 if self.subdomains { 537 Ordering::Greater 538 } else { 539 Ordering::Less 540 } 541 } 542 DomainOrdering::Equal => { 543 if self.subdomains { 544 if other.subdomains { 545 Ordering::Equal 546 } else { 547 Ordering::Greater 548 } 549 } else if other.subdomains { 550 Ordering::Less 551 } else { 552 Ordering::Equal 553 } 554 } 555 DomainOrdering::Longer => { 556 if self.subdomains { 557 if other.subdomains { 558 Ordering::Less 559 } else { 560 Ordering::Greater 561 } 562 } else if other.subdomains { 563 Ordering::Less 564 } else { 565 Ordering::Greater 566 } 567 } 568 DomainOrdering::Greater => Ordering::Greater, 569 } 570 } 571 } 572 impl PartialOrd<DomainOnly<'_>> for Adblock<'_> { 573 #[inline] 574 fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> { 575 Some(self.cmp_domain_only(other)) 576 } 577 } 578 impl PartialOrd<Hosts<'_>> for Adblock<'_> { 579 #[inline] 580 fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> { 581 Some(self.cmp_hosts(other)) 582 } 583 } 584 impl PartialOrd<Wildcard<'_>> for Adblock<'_> { 585 #[inline] 586 fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> { 587 Some(self.cmp_wildcard(other)) 588 } 589 } 590 impl<'a> Set for Adblock<'a> { 591 type Elem = Domain<&'a str>; 592 #[inline] 593 fn bounded_cardinality(&self) -> BoundedCardinality { 594 BoundedCardinality::from_biguint_exact(self.domain_count()) 595 } 596 #[inline] 597 fn cardinality(&self) -> Option<Cardinality> { 598 Some(Cardinality::Finite(self.domain_count())) 599 } 600 #[inline] 601 fn contains<Q>(&self, elem: &Q) -> bool 602 where 603 Q: Borrow<Self::Elem> + Eq + ?Sized, 604 { 605 if self.subdomains { 606 matches!( 607 self.domain.cmp_by_domain_ordering(elem.borrow()), 608 DomainOrdering::Shorter 609 ) 610 } else { 611 self.domain == *elem.borrow() 612 } 613 } 614 #[inline] 615 fn is_proper_subset(&self, val: &Self) -> bool { 616 // A single domain can never be a proper superset. Subdomains` cannot be a proper superset if it has 617 // more labels or the same number of labels as another subdomains. In all other cases, we need to 618 // recursively check from the TLD that the labels are the same. 619 val.subdomains 620 && match val.domain.cmp_by_domain_ordering(&self.domain) { 621 DomainOrdering::Less | DomainOrdering::Longer | DomainOrdering::Greater => false, 622 DomainOrdering::Shorter => true, 623 DomainOrdering::Equal => !self.subdomains, 624 } 625 } 626 #[inline] 627 fn is_subset(&self, val: &Self) -> bool { 628 self == val || self.is_proper_subset(val) 629 } 630 } 631 impl SetOrd for Adblock<'_> {} 632 impl<'a> Deref for Adblock<'a> { 633 type Target = Domain<&'a str>; 634 #[inline] 635 fn deref(&self) -> &Self::Target { 636 &self.domain 637 } 638 } 639 impl<'a> ParsedDomain<'a> for Adblock<'a> { 640 type Error = FirefoxDomainErr; 641 #[expect( 642 unsafe_code, 643 clippy::indexing_slicing, 644 reason = "we carefully verify what we are doing" 645 )] 646 #[inline] 647 fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> { 648 // First remove leading whitepace. Then check for comments via '#' and '!'. Return Blank iff empty. 649 // Return Comment iff '#' or '!' is the first character. Remove trailing whitespace. Next remove the 650 // last byte if it is '^' as well as whitespace before. Next track and remove '||' at the beginning 651 // and any subsequent whitespace. 652 let mut value = val.as_bytes().trim_ascii_start(); 653 value.first().map_or_else( 654 || Ok(Value::Blank), 655 |byt| { 656 if *byt == b'#' || *byt == b'!' { 657 // SAFETY: 658 // `value` came from `val` with leading ASCII whitespace removed which is still valid UTF-8 659 // since the first byte is '#' or '$' the remaining bytes is still valid UTF-8. 660 let comment = unsafe { str::from_utf8_unchecked(&value[1..]) }; 661 Ok(Value::Comment(comment)) 662 } else { 663 value = value.trim_ascii_end(); 664 let len = value.len().wrapping_sub(1); 665 value = value.get(len).map_or(value, |byt2| { 666 if *byt2 == b'^' { 667 value[..len].trim_ascii_end() 668 } else { 669 value 670 } 671 }); 672 let (subdomains, val2) = value.get(..2).map_or_else( 673 || (false, value), 674 |fst| { 675 if fst == b"||" { 676 (true, value[2..].trim_ascii_start()) 677 } else { 678 (false, value) 679 } 680 }, 681 ); 682 // `Domain`s allow `$`, but we don't want to allow that symbol for Adblock-style rules. 683 val2.iter() 684 .try_fold((), |(), byt2| { 685 if *byt2 == b'$' { 686 Err(FirefoxDomainErr::InvalidAdblockDomain) 687 } else { 688 Ok(()) 689 } 690 }) 691 .and_then(|()| { 692 domain_icann_tld(val2).map(|domain| { 693 // A domain of length 252 or 253 can't have subdomains due to there not being enough 694 // characters. 695 Value::Domain(Self { 696 subdomains: if domain.len().get() > 251 { 697 false 698 } else { 699 subdomains 700 }, 701 domain, 702 }) 703 }) 704 }) 705 } 706 }, 707 ) 708 } 709 #[inline] 710 fn domain(&self) -> &Domain<&'a str> { 711 &self.domain 712 } 713 #[inline] 714 fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> { 715 write_rpz_line(&mut writer, self.domain(), action, false).and_then(|()| { 716 if self.subdomains { 717 write_rpz_line(writer, self.domain(), action, true) 718 } else { 719 Ok(()) 720 } 721 }) 722 } 723 } 724 /// Domain constructed from a 725 /// [domains-only rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#domains-only-syntax). 726 /// 727 /// Specifically the domain must conform to the following extended regex: 728 /// 729 /// `^<ws>*<domain><ws>*(#.*)?$` 730 /// 731 /// where `<domain>` conforms to a valid [`Domain`] based on [`ASCII_FIREFOX`], the TLD is either all letters 732 /// or at least length five and begins with `xn--`, and `<ws>` is any sequence of [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace). 733 /// 734 /// Comments are any lines that start with `#` (ignoring whitespace). Any in-line comments after a valid domain 735 /// are ignored and will be parsed into a [`Value::Domain`]. 736 #[derive(Clone, Debug)] 737 pub struct DomainOnly<'a> { 738 /// The `Domain`. 739 domain: Domain<&'a str>, 740 } 741 impl DomainOnly<'_> { 742 /// Read [`Adblock::cmp_domain_only`]. 743 #[inline] 744 #[must_use] 745 pub fn cmp_adblock(&self, other: &Adblock<'_>) -> Ordering { 746 other.cmp_domain_only(self).reverse() 747 } 748 /// Read [`Domain::cmp`]. 749 #[inline] 750 #[must_use] 751 pub fn cmp_hosts(&self, other: &Hosts<'_>) -> Ordering { 752 self.domain.cmp(&other.domain) 753 } 754 /// Read [`Wildcard::cmp_domain_only`]. 755 #[inline] 756 #[must_use] 757 pub fn cmp_wildcard(&self, other: &Wildcard<'_>) -> Ordering { 758 other.cmp_domain_only(self).reverse() 759 } 760 /// Same as [`DomainOnly::cardinality`] except that a `NonZeroU8` is returned. 761 /// 762 /// The value is always 1. 763 #[inline] 764 #[must_use] 765 pub const fn domain_count(&self) -> NonZeroU8 { 766 ONE 767 } 768 } 769 impl PartialEq<DomainOnly<'_>> for DomainOnly<'_> { 770 #[inline] 771 fn eq(&self, other: &DomainOnly<'_>) -> bool { 772 self.domain == other.domain 773 } 774 } 775 impl PartialEq<DomainOnly<'_>> for &DomainOnly<'_> { 776 #[inline] 777 fn eq(&self, other: &DomainOnly<'_>) -> bool { 778 **self == *other 779 } 780 } 781 impl PartialEq<&DomainOnly<'_>> for DomainOnly<'_> { 782 #[inline] 783 fn eq(&self, other: &&DomainOnly<'_>) -> bool { 784 *self == **other 785 } 786 } 787 impl PartialEq<Adblock<'_>> for DomainOnly<'_> { 788 #[inline] 789 fn eq(&self, other: &Adblock<'_>) -> bool { 790 other == self 791 } 792 } 793 impl PartialEq<Hosts<'_>> for DomainOnly<'_> { 794 #[inline] 795 fn eq(&self, other: &Hosts<'_>) -> bool { 796 self.domain == other.domain 797 } 798 } 799 impl PartialEq<&Hosts<'_>> for DomainOnly<'_> { 800 #[inline] 801 fn eq(&self, other: &&Hosts<'_>) -> bool { 802 *self == **other 803 } 804 } 805 impl PartialEq<Hosts<'_>> for &DomainOnly<'_> { 806 #[inline] 807 fn eq(&self, other: &Hosts<'_>) -> bool { 808 **self == *other 809 } 810 } 811 impl PartialEq<&DomainOnly<'_>> for Hosts<'_> { 812 #[inline] 813 fn eq(&self, other: &&DomainOnly<'_>) -> bool { 814 *self == **other 815 } 816 } 817 impl PartialEq<DomainOnly<'_>> for &Hosts<'_> { 818 #[inline] 819 fn eq(&self, other: &DomainOnly<'_>) -> bool { 820 **self == *other 821 } 822 } 823 impl PartialEq<Wildcard<'_>> for DomainOnly<'_> { 824 #[inline] 825 fn eq(&self, other: &Wildcard<'_>) -> bool { 826 !other.proper_subdomains && self.domain == other.domain 827 } 828 } 829 impl PartialEq<&Wildcard<'_>> for DomainOnly<'_> { 830 #[inline] 831 fn eq(&self, other: &&Wildcard<'_>) -> bool { 832 *self == **other 833 } 834 } 835 impl PartialEq<Wildcard<'_>> for &DomainOnly<'_> { 836 #[inline] 837 fn eq(&self, other: &Wildcard<'_>) -> bool { 838 **self == *other 839 } 840 } 841 impl PartialEq<&DomainOnly<'_>> for Wildcard<'_> { 842 #[inline] 843 fn eq(&self, other: &&DomainOnly<'_>) -> bool { 844 *self == **other 845 } 846 } 847 impl PartialEq<DomainOnly<'_>> for &Wildcard<'_> { 848 #[inline] 849 fn eq(&self, other: &DomainOnly<'_>) -> bool { 850 **self == *other 851 } 852 } 853 impl Eq for DomainOnly<'_> {} 854 impl Hash for DomainOnly<'_> { 855 #[inline] 856 fn hash<H: Hasher>(&self, state: &mut H) { 857 self.domain.hash(state); 858 } 859 } 860 impl PartialOrd<DomainOnly<'_>> for DomainOnly<'_> { 861 #[inline] 862 fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> { 863 Some(self.cmp(other)) 864 } 865 } 866 impl Ord for DomainOnly<'_> { 867 /// Read [`Domain::cmp`]. 868 #[inline] 869 fn cmp(&self, other: &Self) -> Ordering { 870 self.domain.cmp(&other.domain) 871 } 872 } 873 impl PartialOrd<Adblock<'_>> for DomainOnly<'_> { 874 #[inline] 875 fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> { 876 Some(self.cmp_adblock(other)) 877 } 878 } 879 impl PartialOrd<Hosts<'_>> for DomainOnly<'_> { 880 #[inline] 881 fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> { 882 Some(self.cmp_hosts(other)) 883 } 884 } 885 impl PartialOrd<Wildcard<'_>> for DomainOnly<'_> { 886 #[inline] 887 fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> { 888 Some(self.cmp_wildcard(other)) 889 } 890 } 891 impl Display for DomainOnly<'_> { 892 #[inline] 893 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 894 self.domain.fmt(f) 895 } 896 } 897 impl<'a> Set for DomainOnly<'a> { 898 type Elem = Domain<&'a str>; 899 #[inline] 900 fn bounded_cardinality(&self) -> BoundedCardinality { 901 BoundedCardinality::from_biguint_exact(self.domain_count().get().into()) 902 } 903 #[inline] 904 fn cardinality(&self) -> Option<Cardinality> { 905 Some(Cardinality::Finite(self.domain_count().get().into())) 906 } 907 #[inline] 908 fn contains<Q>(&self, elem: &Q) -> bool 909 where 910 Q: Borrow<Self::Elem> + Eq + ?Sized, 911 { 912 self.domain == *elem.borrow() 913 } 914 #[inline] 915 fn is_proper_subset(&self, _: &Self) -> bool { 916 false 917 } 918 #[inline] 919 fn is_subset(&self, val: &Self) -> bool { 920 self == val 921 } 922 } 923 impl SetOrd for DomainOnly<'_> {} 924 impl<'a> Deref for DomainOnly<'a> { 925 type Target = Domain<&'a str>; 926 #[inline] 927 fn deref(&self) -> &Self::Target { 928 &self.domain 929 } 930 } 931 impl<'a> ParsedDomain<'a> for DomainOnly<'a> { 932 type Error = FirefoxDomainErr; 933 #[expect( 934 unsafe_code, 935 clippy::arithmetic_side_effects, 936 clippy::indexing_slicing, 937 reason = "we verify all use is correct" 938 )] 939 #[inline] 940 fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> { 941 let value = val.as_bytes().trim_ascii_start(); 942 value.first().map_or_else( 943 || Ok(Value::Blank), 944 |byt| { 945 if *byt == b'#' { 946 // SAFETY: 947 // `value` came from `val` with leading ASCII whitespace removed which is still valid UTF-8 948 // since the first byte is '#' or '$' the remaining bytes are still valid UTF-8. 949 let comment = unsafe { str::from_utf8_unchecked(&value[1..]) }; 950 Ok(Value::Comment(comment)) 951 } else { 952 domain_icann_tld( 953 value[..value 954 .iter() 955 .try_fold(0, |i, byt2| if *byt2 == b'#' { Err(i) } else { Ok(i + 1) }) 956 .unwrap_or_else(convert::identity)] 957 .trim_ascii_end(), 958 ) 959 .map(|domain| Value::Domain(Self { domain })) 960 } 961 }, 962 ) 963 } 964 #[inline] 965 fn domain(&self) -> &Domain<&'a str> { 966 &self.domain 967 } 968 #[inline] 969 fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> { 970 write_rpz_line(&mut writer, self.domain(), action, false) 971 } 972 } 973 /// Domain constructed from a 974 /// [`hosts(5)`-style rule](https://adguard-dns.io/kb/general/dns-filtering-syntax/#etc-hosts-syntax). 975 /// 976 /// Specifically the domain must conform to the following extended regex: 977 /// 978 /// `^<ws>*<ip><ws>+<domain><ws>*(#.*)?$` 979 /// 980 /// where `<domain>` conforms to a valid [`Domain`] based on [`ASCII_FIREFOX`], the TLD is either all letters 981 /// or at least length five and begins with `xn--`, `<ws>` is any sequence of 982 /// [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace), and `<ip>` is one of the following: 983 /// 984 /// `::`, `::1`, `0.0.0.0`, or `127.0.0.1`. 985 /// 986 /// Comments are any lines that start with `#` (ignoring whitespace). Any in-line comments after a valid domain 987 /// are ignored and will be parsed into a [`Value::Domain`]. 988 #[derive(Clone, Debug)] 989 pub struct Hosts<'a> { 990 /// The `Domain`. 991 domain: Domain<&'a str>, 992 } 993 impl Hosts<'_> { 994 /// Read [`Adblock::cmp_hosts`]. 995 #[inline] 996 #[must_use] 997 pub fn cmp_adblock(&self, other: &Adblock<'_>) -> Ordering { 998 other.cmp_hosts(self).reverse() 999 } 1000 /// Read [`DomainOnly::cmp_hosts`]. 1001 #[inline] 1002 #[must_use] 1003 pub fn cmp_domain_only(&self, other: &DomainOnly<'_>) -> Ordering { 1004 other.cmp_hosts(self).reverse() 1005 } 1006 /// Read [`Wildcard::cmp_hosts`]. 1007 #[inline] 1008 #[must_use] 1009 pub fn cmp_wildcard(&self, other: &Wildcard<'_>) -> Ordering { 1010 other.cmp_hosts(self).reverse() 1011 } 1012 /// Same as [`Hosts::cardinality`] except that a `NonZeroU8` is returned. 1013 /// 1014 /// The value is always 1. 1015 #[inline] 1016 #[must_use] 1017 pub const fn domain_count(&self) -> NonZeroU8 { 1018 ONE 1019 } 1020 } 1021 impl PartialEq<Hosts<'_>> for Hosts<'_> { 1022 #[inline] 1023 fn eq(&self, other: &Hosts<'_>) -> bool { 1024 self.domain == other.domain 1025 } 1026 } 1027 impl PartialEq<Hosts<'_>> for &Hosts<'_> { 1028 #[inline] 1029 fn eq(&self, other: &Hosts<'_>) -> bool { 1030 **self == *other 1031 } 1032 } 1033 impl PartialEq<&Hosts<'_>> for Hosts<'_> { 1034 #[inline] 1035 fn eq(&self, other: &&Hosts<'_>) -> bool { 1036 *self == **other 1037 } 1038 } 1039 impl PartialEq<Adblock<'_>> for Hosts<'_> { 1040 #[inline] 1041 fn eq(&self, other: &Adblock<'_>) -> bool { 1042 other == self 1043 } 1044 } 1045 impl PartialEq<DomainOnly<'_>> for Hosts<'_> { 1046 #[inline] 1047 fn eq(&self, other: &DomainOnly<'_>) -> bool { 1048 other == self 1049 } 1050 } 1051 impl PartialEq<Wildcard<'_>> for Hosts<'_> { 1052 #[inline] 1053 fn eq(&self, other: &Wildcard<'_>) -> bool { 1054 !other.proper_subdomains && self.domain == other.domain 1055 } 1056 } 1057 impl PartialEq<&Wildcard<'_>> for Hosts<'_> { 1058 #[inline] 1059 fn eq(&self, other: &&Wildcard<'_>) -> bool { 1060 *self == **other 1061 } 1062 } 1063 impl PartialEq<Wildcard<'_>> for &Hosts<'_> { 1064 #[inline] 1065 fn eq(&self, other: &Wildcard<'_>) -> bool { 1066 **self == *other 1067 } 1068 } 1069 impl PartialEq<&Hosts<'_>> for Wildcard<'_> { 1070 #[inline] 1071 fn eq(&self, other: &&Hosts<'_>) -> bool { 1072 *self == **other 1073 } 1074 } 1075 impl PartialEq<Hosts<'_>> for &Wildcard<'_> { 1076 #[inline] 1077 fn eq(&self, other: &Hosts<'_>) -> bool { 1078 **self == *other 1079 } 1080 } 1081 impl Eq for Hosts<'_> {} 1082 impl Hash for Hosts<'_> { 1083 #[inline] 1084 fn hash<H: Hasher>(&self, state: &mut H) { 1085 self.domain.hash(state); 1086 } 1087 } 1088 impl PartialOrd<Hosts<'_>> for Hosts<'_> { 1089 #[inline] 1090 fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> { 1091 Some(self.cmp(other)) 1092 } 1093 } 1094 impl Ord for Hosts<'_> { 1095 /// Read [`Domain::cmp`]. 1096 #[inline] 1097 fn cmp(&self, other: &Self) -> Ordering { 1098 self.domain.cmp(&other.domain) 1099 } 1100 } 1101 impl PartialOrd<Adblock<'_>> for Hosts<'_> { 1102 #[inline] 1103 fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> { 1104 Some(self.cmp_adblock(other)) 1105 } 1106 } 1107 impl PartialOrd<DomainOnly<'_>> for Hosts<'_> { 1108 #[inline] 1109 fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> { 1110 Some(self.cmp_domain_only(other)) 1111 } 1112 } 1113 impl PartialOrd<Wildcard<'_>> for Hosts<'_> { 1114 #[inline] 1115 fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> { 1116 Some(self.cmp_wildcard(other)) 1117 } 1118 } 1119 impl Display for Hosts<'_> { 1120 #[inline] 1121 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1122 self.domain.fmt(f) 1123 } 1124 } 1125 impl<'a> Set for Hosts<'a> { 1126 type Elem = Domain<&'a str>; 1127 #[inline] 1128 fn bounded_cardinality(&self) -> BoundedCardinality { 1129 BoundedCardinality::from_biguint_exact(self.domain_count().get().into()) 1130 } 1131 #[inline] 1132 fn cardinality(&self) -> Option<Cardinality> { 1133 Some(Cardinality::Finite(self.domain_count().get().into())) 1134 } 1135 #[inline] 1136 fn contains<Q>(&self, elem: &Q) -> bool 1137 where 1138 Q: Borrow<Self::Elem> + Eq + ?Sized, 1139 { 1140 self.domain == *elem.borrow() 1141 } 1142 #[inline] 1143 fn is_proper_subset(&self, _: &Self) -> bool { 1144 false 1145 } 1146 #[inline] 1147 fn is_subset(&self, val: &Self) -> bool { 1148 self == val 1149 } 1150 } 1151 impl SetOrd for Hosts<'_> {} 1152 impl<'a> Deref for Hosts<'a> { 1153 type Target = Domain<&'a str>; 1154 #[inline] 1155 fn deref(&self) -> &Self::Target { 1156 &self.domain 1157 } 1158 } 1159 impl<'a> ParsedDomain<'a> for Hosts<'a> { 1160 type Error = FirefoxDomainErr; 1161 #[expect( 1162 unsafe_code, 1163 clippy::arithmetic_side_effects, 1164 clippy::indexing_slicing, 1165 reason = "carefully verified use is correct" 1166 )] 1167 #[inline] 1168 fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> { 1169 let mut value = val.as_bytes().trim_ascii_start(); 1170 value.first().map_or_else( 1171 || Ok(Value::Blank), 1172 |byt| { 1173 if *byt == b'#' { 1174 // SAFETY: 1175 // `value` came from `val` with leading ASCII whitespace removed which is still valid UTF-8 1176 // since the first byte is '#' or '$' the remaining bytes is still valid UTF-8. 1177 let comment = unsafe { str::from_utf8_unchecked(&value[1..]) }; 1178 Ok(Value::Comment(comment)) 1179 } else { 1180 value = value 1181 .get(..3) 1182 .ok_or(FirefoxDomainErr::InvalidHostsIP) 1183 .and_then(|fst| { 1184 if fst == b"::1" { 1185 Ok(&value[3..]) 1186 } else if &value[..2] == b"::" { 1187 Ok(&value[2..]) 1188 } else { 1189 value 1190 .get(..7) 1191 .ok_or(FirefoxDomainErr::InvalidHostsIP) 1192 .and_then(|fst2| { 1193 if fst2 == b"0.0.0.0" { 1194 Ok(&value[7..]) 1195 } else { 1196 value 1197 .get(..9) 1198 .ok_or(FirefoxDomainErr::InvalidHostsIP) 1199 .and_then(|fst3| { 1200 if fst3 == b"127.0.0.1" { 1201 Ok(&value[9..]) 1202 } else { 1203 Err(FirefoxDomainErr::InvalidHostsIP) 1204 } 1205 }) 1206 } 1207 }) 1208 } 1209 })?; 1210 let len = value.len(); 1211 value = value.trim_ascii_start(); 1212 if len == value.len() { 1213 // There has to be at least one space or tab between the IP and domain. 1214 Err(FirefoxDomainErr::InvalidHostsIP) 1215 } else { 1216 domain_icann_tld( 1217 value[..value 1218 .iter() 1219 .try_fold( 1220 0, 1221 |i, byt2| if *byt2 == b'#' { Err(i) } else { Ok(i + 1) }, 1222 ) 1223 .unwrap_or_else(convert::identity)] 1224 .trim_ascii_end(), 1225 ) 1226 .map(|domain| Value::Domain(Self { domain })) 1227 } 1228 } 1229 }, 1230 ) 1231 } 1232 #[inline] 1233 fn domain(&self) -> &Domain<&'a str> { 1234 &self.domain 1235 } 1236 #[inline] 1237 fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> { 1238 write_rpz_line(&mut writer, self.domain(), action, false) 1239 } 1240 } 1241 /// Domain constructed from a 1242 /// [wildcard domain rule](https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblock&showintro=0&mimetype=plaintext). 1243 /// 1244 /// Specifically the domain must conform to the following extended regex: 1245 /// 1246 /// `^<ws>*(\*\.)?<domain><ws>*(#.*)?$` 1247 /// 1248 /// where `<domain>` conforms to a valid [`Domain`] based on [`ASCII_FIREFOX`], the TLD is either all letters 1249 /// or at least length five and begins with `xn--`, and `<ws>` is any sequence of 1250 /// [ASCII whitespace](https://infra.spec.whatwg.org/#ascii-whitespace). 1251 /// 1252 /// If `domain` begins with `*.`, then `domain` must have length less than 252. 1253 /// 1254 /// Comments are any lines that start with `#` (ignoring whitespace). Any in-line comments after a valid domain 1255 /// are ignored and will be parsed into a [`Value::Domain`]. 1256 #[derive(Clone, Debug)] 1257 pub struct Wildcard<'a> { 1258 /// The `Domain`. 1259 domain: Domain<&'a str>, 1260 /// `true` iff `domain` represents all proper subdomains. Note that this does _not_ include `domain` itself. 1261 proper_subdomains: bool, 1262 } 1263 impl Wildcard<'_> { 1264 /// Returns `true` iff the contained [`Domain`] represents all proper subdomains. Note this does _not_ 1265 /// include the `Domain` itself. 1266 #[inline] 1267 #[must_use] 1268 pub const fn is_proper_subdomains(&self) -> bool { 1269 self.proper_subdomains 1270 } 1271 /// Read [`Adblock::cmp_wildcard`]. 1272 #[inline] 1273 #[must_use] 1274 pub fn cmp_adblock(&self, other: &Adblock<'_>) -> Ordering { 1275 other.cmp_wildcard(self).reverse() 1276 } 1277 /// Since `DomainOnly` and `Hosts` are treated the same, we have this helper function that can be used 1278 /// for both. 1279 #[must_use] 1280 fn cmp_dom(&self, other: &Domain<&str>) -> Ordering { 1281 match self.domain.cmp_by_domain_ordering(other) { 1282 DomainOrdering::Less => Ordering::Less, 1283 DomainOrdering::Shorter => { 1284 if self.proper_subdomains { 1285 Ordering::Greater 1286 } else { 1287 Ordering::Less 1288 } 1289 } 1290 DomainOrdering::Equal => { 1291 if self.proper_subdomains { 1292 Ordering::Greater 1293 } else { 1294 Ordering::Equal 1295 } 1296 } 1297 DomainOrdering::Longer | DomainOrdering::Greater => Ordering::Greater, 1298 } 1299 } 1300 /// The total order that is defined follows the following hierarchy: 1301 /// 1. Pairwise comparisons of each [`ascii_domain::dom::Label`] starting from the TLDs. 1302 /// 2. If 1. evaluates as not equivalent, then return the result. 1303 /// 3. If `self` represents a single `Domain` (i.e., `!self.is_proper_subdomains()`), 1304 /// then return the comparison of label counts. 1305 /// 4. Return `self` is greater. 1306 /// 1307 /// For example, `com` `<` `example.com` `<` `*.example.com` `<` `*.com` `<` `net` `<` `example.net` `<` `*.example.net` `<` `*.net`. 1308 #[inline] 1309 #[must_use] 1310 pub fn cmp_domain_only(&self, other: &DomainOnly<'_>) -> Ordering { 1311 self.cmp_dom(&other.domain) 1312 } 1313 /// Read [`Wildcard::cmp_domain_only`]. 1314 #[inline] 1315 #[must_use] 1316 pub fn cmp_hosts(&self, other: &Hosts<'_>) -> Ordering { 1317 self.cmp_dom(&other.domain) 1318 } 1319 /// Same as [`Wildcard::cardinality`] except that a `BigUint` is returned. Note the count does _not_ include 1320 /// the `Domain` itself when `self.is_proper_subdomains()`. 1321 /// 1322 /// `!self.is_proper_subdomains()` ⇔ `self.domain_count() == BigUint::new(vec![1])`. 1323 #[inline] 1324 #[must_use] 1325 pub fn domain_count(&self) -> BigUint { 1326 if self.proper_subdomains { 1327 proper_subdomain_count(&self.domain) 1328 } else { 1329 BigUint::new(vec![1]) 1330 } 1331 } 1332 } 1333 impl PartialEq<Wildcard<'_>> for Wildcard<'_> { 1334 #[inline] 1335 fn eq(&self, other: &Wildcard<'_>) -> bool { 1336 self.domain == other.domain && self.proper_subdomains == other.proper_subdomains 1337 } 1338 } 1339 impl PartialEq<Wildcard<'_>> for &Wildcard<'_> { 1340 #[inline] 1341 fn eq(&self, other: &Wildcard<'_>) -> bool { 1342 **self == *other 1343 } 1344 } 1345 impl PartialEq<&Wildcard<'_>> for Wildcard<'_> { 1346 #[inline] 1347 fn eq(&self, other: &&Wildcard<'_>) -> bool { 1348 *self == **other 1349 } 1350 } 1351 impl PartialEq<Adblock<'_>> for Wildcard<'_> { 1352 #[inline] 1353 fn eq(&self, other: &Adblock<'_>) -> bool { 1354 other == self 1355 } 1356 } 1357 impl PartialEq<DomainOnly<'_>> for Wildcard<'_> { 1358 #[inline] 1359 fn eq(&self, other: &DomainOnly<'_>) -> bool { 1360 other == self 1361 } 1362 } 1363 impl PartialEq<Hosts<'_>> for Wildcard<'_> { 1364 #[inline] 1365 fn eq(&self, other: &Hosts<'_>) -> bool { 1366 other == self 1367 } 1368 } 1369 impl Eq for Wildcard<'_> {} 1370 impl Hash for Wildcard<'_> { 1371 #[inline] 1372 fn hash<H: Hasher>(&self, state: &mut H) { 1373 self.domain.hash(state); 1374 } 1375 } 1376 impl PartialOrd<Wildcard<'_>> for Wildcard<'_> { 1377 #[inline] 1378 fn partial_cmp(&self, other: &Wildcard<'_>) -> Option<Ordering> { 1379 Some(self.cmp(other)) 1380 } 1381 } 1382 impl Ord for Wildcard<'_> { 1383 /// The total order that is defined follows the following hierarchy: 1384 /// 1. Pairwise comparisons of each [`ascii_domain::dom::Label`] starting from the TLDs. 1385 /// 2. If 1. evaluates as not equivalent, then return the result. 1386 /// 3. If both domains represent a single `Domain`, then return the comparison 1387 /// of label counts. 1388 /// 4. If one domain represents a single `Domain`, then return that that domain is less. 1389 /// 5. Return the inverse of the comparison of label counts. 1390 /// 1391 /// For example, `com` `<` `example.com` `<` `*.example.com` `<` `*.com` `<` `net` `<` `example.net` `<` `*.example.net` `<` `*.net`. 1392 #[inline] 1393 fn cmp(&self, other: &Self) -> Ordering { 1394 match self.domain.cmp_by_domain_ordering(&other.domain) { 1395 DomainOrdering::Less => Ordering::Less, 1396 DomainOrdering::Shorter => { 1397 if self.proper_subdomains { 1398 Ordering::Greater 1399 } else { 1400 Ordering::Less 1401 } 1402 } 1403 DomainOrdering::Equal => { 1404 if self.proper_subdomains { 1405 if other.proper_subdomains { 1406 Ordering::Equal 1407 } else { 1408 Ordering::Greater 1409 } 1410 } else if other.proper_subdomains { 1411 Ordering::Less 1412 } else { 1413 Ordering::Equal 1414 } 1415 } 1416 DomainOrdering::Longer => { 1417 if self.proper_subdomains { 1418 if other.proper_subdomains { 1419 Ordering::Less 1420 } else { 1421 Ordering::Greater 1422 } 1423 } else if other.proper_subdomains { 1424 Ordering::Less 1425 } else { 1426 Ordering::Greater 1427 } 1428 } 1429 DomainOrdering::Greater => Ordering::Greater, 1430 } 1431 } 1432 } 1433 impl PartialOrd<Adblock<'_>> for Wildcard<'_> { 1434 #[inline] 1435 fn partial_cmp(&self, other: &Adblock<'_>) -> Option<Ordering> { 1436 Some(self.cmp_adblock(other)) 1437 } 1438 } 1439 impl PartialOrd<DomainOnly<'_>> for Wildcard<'_> { 1440 #[inline] 1441 fn partial_cmp(&self, other: &DomainOnly<'_>) -> Option<Ordering> { 1442 Some(self.cmp_domain_only(other)) 1443 } 1444 } 1445 impl PartialOrd<Hosts<'_>> for Wildcard<'_> { 1446 #[inline] 1447 fn partial_cmp(&self, other: &Hosts<'_>) -> Option<Ordering> { 1448 Some(self.cmp_hosts(other)) 1449 } 1450 } 1451 impl Display for Wildcard<'_> { 1452 #[inline] 1453 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1454 write!( 1455 f, 1456 "{}{}", 1457 if self.proper_subdomains { "*." } else { "" }, 1458 self.domain 1459 ) 1460 } 1461 } 1462 impl<'a> Set for Wildcard<'a> { 1463 type Elem = Domain<&'a str>; 1464 #[inline] 1465 fn bounded_cardinality(&self) -> BoundedCardinality { 1466 BoundedCardinality::from_biguint_exact(self.domain_count()) 1467 } 1468 #[inline] 1469 fn cardinality(&self) -> Option<Cardinality> { 1470 Some(Cardinality::Finite(self.domain_count())) 1471 } 1472 #[inline] 1473 fn contains<Q>(&self, elem: &Q) -> bool 1474 where 1475 Q: Borrow<Self::Elem> + Eq + ?Sized, 1476 { 1477 if self.proper_subdomains { 1478 self.domain.cmp_by_domain_ordering(elem.borrow()) == DomainOrdering::Shorter 1479 } else { 1480 self.domain == *elem.borrow() 1481 } 1482 } 1483 #[inline] 1484 fn is_proper_subset(&self, val: &Self) -> bool { 1485 // A single domain can never be a proper superset. Proper subdomains cannot be a proper superset if it 1486 // has more labels or the same number of labels as another domain. In all other cases, we need to 1487 // recursively check from the TLD that the labels are the same. 1488 val.proper_subdomains 1489 && val.domain.cmp_by_domain_ordering(&self.domain) == DomainOrdering::Shorter 1490 } 1491 #[inline] 1492 fn is_subset(&self, val: &Self) -> bool { 1493 self == val || self.is_proper_subset(val) 1494 } 1495 } 1496 impl SetOrd for Wildcard<'_> {} 1497 impl<'a> Deref for Wildcard<'a> { 1498 type Target = Domain<&'a str>; 1499 #[inline] 1500 fn deref(&self) -> &Self::Target { 1501 &self.domain 1502 } 1503 } 1504 impl<'a> ParsedDomain<'a> for Wildcard<'a> { 1505 type Error = FirefoxDomainErr; 1506 #[expect( 1507 unsafe_code, 1508 clippy::arithmetic_side_effects, 1509 clippy::indexing_slicing, 1510 reason = "need them all. care has been taken." 1511 )] 1512 #[inline] 1513 fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> { 1514 let value = val.as_bytes().trim_ascii_start(); 1515 value.first().map_or_else( 1516 || Ok(Value::Blank), 1517 |byt| { 1518 if *byt == b'#' { 1519 // SAFETY: 1520 // `value` came from `val` with leading ASCII whitespace removed which is still valid UTF-8 1521 // since the first byte is '#' or '$' the remaining bytes is still valid UTF-8. 1522 let comment = unsafe { str::from_utf8_unchecked(&value[1..]) }; 1523 Ok(Value::Comment(comment)) 1524 } else { 1525 let (proper_subdomains, val2) = value.get(..2).map_or_else( 1526 || (false, value), 1527 |fst| { 1528 if fst == b"*." { 1529 (true, &value[2..]) 1530 } else { 1531 (false, value) 1532 } 1533 }, 1534 ); 1535 domain_icann_tld( 1536 val2[..val2 1537 .iter() 1538 .try_fold(0, |i, byt2| if *byt2 == b'#' { Err(i) } else { Ok(i + 1) }) 1539 .unwrap_or_else(convert::identity)] 1540 .trim_ascii_end(), 1541 ) 1542 .and_then(|domain| { 1543 if proper_subdomains { 1544 if domain.len().get() > 251 { 1545 Err(FirefoxDomainErr::InvalidWildcardDomain) 1546 } else { 1547 Ok(Value::Domain(Self { 1548 domain, 1549 proper_subdomains: true, 1550 })) 1551 } 1552 } else { 1553 Ok(Value::Domain(Self { 1554 domain, 1555 proper_subdomains, 1556 })) 1557 } 1558 }) 1559 } 1560 }, 1561 ) 1562 } 1563 #[inline] 1564 fn domain(&self) -> &Domain<&'a str> { 1565 &self.domain 1566 } 1567 #[inline] 1568 fn write_to_rpz<W: Write>(&self, action: RpzAction, mut writer: W) -> Result<(), Error> { 1569 write_rpz_line(&mut writer, self.domain(), action, self.proper_subdomains) 1570 } 1571 } 1572 /// A [`Domain`] in a [response policy zone (RPZ)](https://en.wikipedia.org/wiki/Response_policy_zone) file. 1573 #[derive(Clone, Debug)] 1574 pub enum RpzDomain<'a> { 1575 /// An `Adblock` domain. 1576 Adblock(Adblock<'a>), 1577 /// A `DomainOnly` domain. 1578 DomainOnly(DomainOnly<'a>), 1579 /// A `Hosts` domain. 1580 Hosts(Hosts<'a>), 1581 /// A `Wildcard` domain. 1582 Wildcard(Wildcard<'a>), 1583 } 1584 impl RpzDomain<'_> { 1585 /// Returns `true` iff `self` represents a single [`Domain`]. 1586 #[inline] 1587 #[must_use] 1588 pub const fn is_domain(&self) -> bool { 1589 match *self { 1590 Self::Adblock(ref dom) => !dom.subdomains, 1591 Self::DomainOnly(_) | Self::Hosts(_) => true, 1592 Self::Wildcard(ref dom) => !dom.proper_subdomains, 1593 } 1594 } 1595 /// Returns `true` iff `self` represents proper subdomains of the contained [`Domain`] (i.e., 1596 /// is a [`Wildcard`] such that [`Wildcard::is_proper_subdomains`]). 1597 #[inline] 1598 #[must_use] 1599 pub const fn is_proper_subdomains(&self) -> bool { 1600 match *self { 1601 Self::Adblock(_) | Self::DomainOnly(_) | Self::Hosts(_) => false, 1602 Self::Wildcard(ref dom) => dom.proper_subdomains, 1603 } 1604 } 1605 /// Returns `true` iff `self` represents subdomains of the contained [`Domain`] (i.e., is an 1606 /// [`Adblock`] such that [`Adblock::is_subdomains`]). 1607 #[inline] 1608 #[must_use] 1609 pub const fn is_subdomains(&self) -> bool { 1610 match *self { 1611 Self::Adblock(ref dom) => dom.subdomains, 1612 Self::DomainOnly(_) | Self::Hosts(_) | Self::Wildcard(_) => false, 1613 } 1614 } 1615 /// Returns the count of [`Domain`]s represented by `self`. This function is the same as 1616 /// [`RpzDomain::cardinality`] except that it returns a `BigUint`. 1617 #[inline] 1618 #[must_use] 1619 pub fn domain_count(&self) -> BigUint { 1620 match *self { 1621 Self::Adblock(ref dom) => dom.domain_count(), 1622 Self::DomainOnly(ref dom) => dom.domain_count().get().into(), 1623 Self::Hosts(ref dom) => dom.domain_count().get().into(), 1624 Self::Wildcard(ref dom) => dom.domain_count(), 1625 } 1626 } 1627 } 1628 impl PartialEq<RpzDomain<'_>> for RpzDomain<'_> { 1629 #[inline] 1630 fn eq(&self, other: &RpzDomain<'_>) -> bool { 1631 match *self { 1632 Self::Adblock(ref dom) => match *other { 1633 RpzDomain::Adblock(ref dom2) => dom == dom2, 1634 RpzDomain::DomainOnly(ref dom2) => dom == dom2, 1635 RpzDomain::Hosts(ref dom2) => dom == dom2, 1636 RpzDomain::Wildcard(ref dom2) => dom == dom2, 1637 }, 1638 Self::DomainOnly(ref dom) => match *other { 1639 RpzDomain::Adblock(ref dom2) => dom == dom2, 1640 RpzDomain::DomainOnly(ref dom2) => dom == dom2, 1641 RpzDomain::Hosts(ref dom2) => dom == dom2, 1642 RpzDomain::Wildcard(ref dom2) => dom == dom2, 1643 }, 1644 Self::Hosts(ref dom) => match *other { 1645 RpzDomain::Adblock(ref dom2) => dom == dom2, 1646 RpzDomain::DomainOnly(ref dom2) => dom == dom2, 1647 RpzDomain::Hosts(ref dom2) => dom == dom2, 1648 RpzDomain::Wildcard(ref dom2) => dom == dom2, 1649 }, 1650 Self::Wildcard(ref dom) => match *other { 1651 RpzDomain::Adblock(ref dom2) => dom == dom2, 1652 RpzDomain::DomainOnly(ref dom2) => dom == dom2, 1653 RpzDomain::Hosts(ref dom2) => dom == dom2, 1654 RpzDomain::Wildcard(ref dom2) => dom == dom2, 1655 }, 1656 } 1657 } 1658 } 1659 impl PartialEq<RpzDomain<'_>> for &RpzDomain<'_> { 1660 #[inline] 1661 fn eq(&self, other: &RpzDomain<'_>) -> bool { 1662 **self == *other 1663 } 1664 } 1665 impl PartialEq<&RpzDomain<'_>> for RpzDomain<'_> { 1666 #[inline] 1667 fn eq(&self, other: &&RpzDomain<'_>) -> bool { 1668 *self == **other 1669 } 1670 } 1671 impl Eq for RpzDomain<'_> {} 1672 impl Hash for RpzDomain<'_> { 1673 #[inline] 1674 fn hash<H: Hasher>(&self, state: &mut H) { 1675 self.domain().hash(state); 1676 } 1677 } 1678 impl PartialOrd<RpzDomain<'_>> for RpzDomain<'_> { 1679 #[inline] 1680 fn partial_cmp(&self, other: &RpzDomain<'_>) -> Option<Ordering> { 1681 Some(self.cmp(other)) 1682 } 1683 } 1684 impl Ord for RpzDomain<'_> { 1685 /// The total order that is defined follows the following hierarchy: 1686 /// 1. Pairwise comparisons of each [`ascii_domain::dom::Label`] starting from the TLDs. 1687 /// 2. If 1. evaluates as not equivalent, then return the result. 1688 /// 3. If both domains represent a single `Domain`, then return the comparison 1689 /// of label counts. 1690 /// 4. If one domain represents a single `Domain`, then return that that domain is less. 1691 /// 5. If the label counts are the same and exactly one domain represents proper subdomains, the other domain is greater. 1692 /// 6. Return the inverse of the comparison of label counts. 1693 /// 1694 /// For example the following is a sequence of domains in 1695 /// ascending order: 1696 /// 1697 /// `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`. 1698 #[inline] 1699 fn cmp(&self, other: &Self) -> Ordering { 1700 match *self { 1701 Self::Adblock(ref dom) => match *other { 1702 Self::Adblock(ref dom2) => dom.cmp(dom2), 1703 Self::DomainOnly(ref dom2) => dom.cmp_domain_only(dom2), 1704 Self::Hosts(ref dom2) => dom.cmp_hosts(dom2), 1705 Self::Wildcard(ref dom2) => dom.cmp_wildcard(dom2), 1706 }, 1707 Self::DomainOnly(ref dom) => match *other { 1708 Self::Adblock(ref dom2) => dom.cmp_adblock(dom2), 1709 Self::DomainOnly(ref dom2) => dom.cmp(dom2), 1710 Self::Hosts(ref dom2) => dom.cmp_hosts(dom2), 1711 Self::Wildcard(ref dom2) => dom.cmp_wildcard(dom2), 1712 }, 1713 Self::Hosts(ref dom) => match *other { 1714 Self::Adblock(ref dom2) => dom.cmp_adblock(dom2), 1715 Self::DomainOnly(ref dom2) => dom.cmp_domain_only(dom2), 1716 Self::Hosts(ref dom2) => dom.cmp(dom2), 1717 Self::Wildcard(ref dom2) => dom.cmp_wildcard(dom2), 1718 }, 1719 Self::Wildcard(ref dom) => match *other { 1720 Self::Adblock(ref dom2) => dom.cmp_adblock(dom2), 1721 Self::DomainOnly(ref dom2) => dom.cmp_domain_only(dom2), 1722 Self::Hosts(ref dom2) => dom.cmp_hosts(dom2), 1723 Self::Wildcard(ref dom2) => dom.cmp(dom2), 1724 }, 1725 } 1726 } 1727 } 1728 impl Display for RpzDomain<'_> { 1729 #[inline] 1730 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1731 match *self { 1732 Self::Adblock(ref dom) => dom.fmt(f), 1733 Self::DomainOnly(ref dom) => dom.fmt(f), 1734 Self::Hosts(ref dom) => dom.fmt(f), 1735 Self::Wildcard(ref dom) => dom.fmt(f), 1736 } 1737 } 1738 } 1739 impl<'a> Set for RpzDomain<'a> { 1740 type Elem = Domain<&'a str>; 1741 #[inline] 1742 fn bounded_cardinality(&self) -> BoundedCardinality { 1743 BoundedCardinality::from_biguint_exact(self.domain_count()) 1744 } 1745 #[inline] 1746 fn cardinality(&self) -> Option<Cardinality> { 1747 Some(Cardinality::Finite(self.domain_count())) 1748 } 1749 #[inline] 1750 fn contains<Q>(&self, elem: &Q) -> bool 1751 where 1752 Q: Borrow<Self::Elem> + Eq + ?Sized, 1753 { 1754 match *self { 1755 Self::Adblock(ref dom) => dom.contains(elem), 1756 Self::DomainOnly(ref dom) => dom.contains(elem), 1757 Self::Hosts(ref dom) => dom.contains(elem), 1758 Self::Wildcard(ref dom) => dom.contains(elem), 1759 } 1760 } 1761 #[inline] 1762 fn is_proper_subset(&self, val: &Self) -> bool { 1763 match *val { 1764 Self::Adblock(ref dom) => { 1765 dom.subdomains 1766 && match *self { 1767 Self::Adblock(ref dom2) => { 1768 dom.domain.cmp_by_domain_ordering(&dom2.domain) 1769 == DomainOrdering::Shorter 1770 } 1771 Self::DomainOnly(ref dom2) => matches!( 1772 dom.domain.cmp_by_domain_ordering(&dom2.domain), 1773 DomainOrdering::Shorter | DomainOrdering::Equal 1774 ), 1775 Self::Hosts(ref dom2) => matches!( 1776 dom.domain.cmp_by_domain_ordering(&dom2.domain), 1777 DomainOrdering::Shorter | DomainOrdering::Equal 1778 ), 1779 Self::Wildcard(ref dom2) => matches!( 1780 dom.domain.cmp_by_domain_ordering(&dom2.domain), 1781 DomainOrdering::Shorter | DomainOrdering::Equal 1782 ), 1783 } 1784 } 1785 Self::DomainOnly(_) | Self::Hosts(_) => false, 1786 Self::Wildcard(ref dom) => { 1787 dom.proper_subdomains 1788 && match *self { 1789 Self::Adblock(ref dom2) => { 1790 dom.domain.cmp_by_domain_ordering(&dom2.domain) 1791 == DomainOrdering::Shorter 1792 } 1793 Self::DomainOnly(ref dom2) => { 1794 dom.domain.cmp_by_domain_ordering(&dom2.domain) 1795 == DomainOrdering::Shorter 1796 } 1797 Self::Hosts(ref dom2) => { 1798 dom.domain.cmp_by_domain_ordering(&dom2.domain) 1799 == DomainOrdering::Shorter 1800 } 1801 Self::Wildcard(ref dom2) => { 1802 dom.domain.cmp_by_domain_ordering(&dom2.domain) 1803 == DomainOrdering::Shorter 1804 } 1805 } 1806 } 1807 } 1808 } 1809 #[inline] 1810 fn is_subset(&self, val: &Self) -> bool { 1811 self == val || self.is_proper_subset(val) 1812 } 1813 } 1814 impl SetOrd for RpzDomain<'_> {} 1815 impl<'a> Deref for RpzDomain<'a> { 1816 type Target = Domain<&'a str>; 1817 #[inline] 1818 fn deref(&self) -> &Self::Target { 1819 match *self { 1820 Self::Adblock(ref dom) => &dom.domain, 1821 Self::DomainOnly(ref dom) => &dom.domain, 1822 Self::Hosts(ref dom) => &dom.domain, 1823 Self::Wildcard(ref dom) => &dom.domain, 1824 } 1825 } 1826 } 1827 impl<'a: 'b, 'b> From<Adblock<'a>> for RpzDomain<'b> { 1828 #[inline] 1829 fn from(value: Adblock<'a>) -> Self { 1830 Self::Adblock(value) 1831 } 1832 } 1833 impl<'a: 'b, 'b> From<DomainOnly<'a>> for RpzDomain<'b> { 1834 #[inline] 1835 fn from(value: DomainOnly<'a>) -> Self { 1836 Self::DomainOnly(value) 1837 } 1838 } 1839 impl<'a: 'b, 'b> From<Hosts<'a>> for RpzDomain<'b> { 1840 #[inline] 1841 fn from(value: Hosts<'a>) -> Self { 1842 Self::Hosts(value) 1843 } 1844 } 1845 impl<'a: 'b, 'b> From<Wildcard<'a>> for RpzDomain<'b> { 1846 #[inline] 1847 fn from(value: Wildcard<'a>) -> Self { 1848 Self::Wildcard(value) 1849 } 1850 } 1851 impl<'a> ParsedDomain<'a> for RpzDomain<'a> { 1852 type Error = FirefoxDomainErr; 1853 #[inline] 1854 fn parse_value<'b: 'a>(val: &'b str) -> Result<Value<'a, Self>, Self::Error> { 1855 DomainOnly::parse_value(val).map_or_else( 1856 |_| { 1857 Hosts::parse_value(val).map_or_else( 1858 |_| { 1859 Wildcard::parse_value(val).map_or_else( 1860 |_| { 1861 Adblock::parse_value(val).map(|value| match value { 1862 Value::Domain(dom) => Value::Domain(Self::Adblock(dom)), 1863 Value::Comment(com) => Value::Comment(com), 1864 Value::Blank => Value::Blank, 1865 }) 1866 }, 1867 |value| { 1868 Ok(match value { 1869 Value::Domain(dom) => Value::Domain(Self::Wildcard(dom)), 1870 Value::Comment(com) => Value::Comment(com), 1871 Value::Blank => Value::Blank, 1872 }) 1873 }, 1874 ) 1875 }, 1876 |value| { 1877 Ok(match value { 1878 Value::Domain(dom) => Value::Domain(Self::Hosts(dom)), 1879 Value::Comment(com) => Value::Comment(com), 1880 Value::Blank => Value::Blank, 1881 }) 1882 }, 1883 ) 1884 }, 1885 |value| { 1886 Ok(match value { 1887 Value::Domain(dom) => Value::Domain(Self::DomainOnly(dom)), 1888 Value::Comment(com) => Value::Comment(com), 1889 Value::Blank => Value::Blank, 1890 }) 1891 }, 1892 ) 1893 } 1894 #[inline] 1895 fn domain(&self) -> &Domain<&'a str> { 1896 match *self { 1897 Self::Adblock(ref dom) => &dom.domain, 1898 Self::DomainOnly(ref dom) => &dom.domain, 1899 Self::Hosts(ref dom) => &dom.domain, 1900 Self::Wildcard(ref dom) => &dom.domain, 1901 } 1902 } 1903 #[inline] 1904 fn write_to_rpz<W: Write>(&self, action: RpzAction, writer: W) -> Result<(), Error> { 1905 match *self { 1906 Self::Adblock(ref dom) => dom.write_to_rpz(action, writer), 1907 Self::DomainOnly(ref dom) => dom.write_to_rpz(action, writer), 1908 Self::Hosts(ref dom) => dom.write_to_rpz(action, writer), 1909 Self::Wildcard(ref dom) => dom.write_to_rpz(action, writer), 1910 } 1911 } 1912 }