rpz

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

tests.rs (19161B)


      1 use super::{
      2     Adblock, DomainOnly, FirefoxDomainErr, Hosts, ParsedDomain as _, RpzDomain, Value, Wildcard,
      3 };
      4 use ascii_domain::dom::DomainErr;
      5 use num_bigint::BigUint;
      6 use superset_map::SupersetSet;
      7 #[test]
      8 fn adblock_parse() {
      9     // Test subdomains.
     10     assert!(
     11         Adblock::parse_value("||www.example.com").is_ok_and(|val| match val {
     12             Value::Domain(ref dom) => dom.subdomains && dom.domain.as_bytes() == b"www.example.com",
     13             Value::Comment(_) | Value::Blank => false,
     14         })
     15     );
     16     // Test whitespace and '^' removal.
     17     assert!(
     18         Adblock::parse_value("   \t\t  ||\t\t  \twww.example.com   \t\t  ^  \t\t  ").is_ok_and(
     19             |val| match val {
     20                 Value::Domain(ref dom) =>
     21                     dom.subdomains && dom.domain.as_bytes() == b"www.example.com",
     22                 Value::Comment(_) | Value::Blank => false,
     23             }
     24         )
     25     );
     26     assert!(
     27         Adblock::parse_value("\t\t  \twww.example.com   \t\t   \t\t  ").is_ok_and(|val| {
     28             match val {
     29                 Value::Domain(ref dom) => {
     30                     !dom.subdomains && dom.domain.as_bytes() == b"www.example.com"
     31                 }
     32                 Value::Comment(_) | Value::Blank => false,
     33             }
     34         })
     35     );
     36     assert!(Adblock::parse_value("www .example.com").is_err_and(
     37             |err| err == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')),
     38         ));
     39     assert!(Adblock::parse_value("||www.ExAMPle.COm").is_ok_and(|val| {
     40         match val {
     41             Value::Domain(ref dom) => {
     42                 Adblock::parse_value("||www.example.com").is_ok_and(|val_2| match val_2 {
     43                     Value::Domain(ref dom2) => {
     44                         dom == dom2 && dom.subdomains && dom2.subdomains && dom.cmp(dom2).is_eq()
     45                     }
     46                     Value::Comment(_) | Value::Blank => false,
     47                 })
     48             }
     49             Value::Comment(_) | Value::Blank => false,
     50         }
     51     }));
     52     // Test comment
     53     assert!(
     54         Adblock::parse_value("  \t\t   #hi").is_ok_and(|val| match val {
     55             Value::Comment(com) => com == "hi",
     56             Value::Domain(_) | Value::Blank => false,
     57         })
     58     );
     59     assert!(
     60         Adblock::parse_value("  \t\t   !!  foo").is_ok_and(|val| match val {
     61             Value::Comment(com) => com == "!  foo",
     62             Value::Domain(_) | Value::Blank => false,
     63         })
     64     );
     65     // Test blank
     66     assert!(Adblock::parse_value("  \t\t   ").is_ok_and(|val| matches!(val, Value::Blank)));
     67 }
     68 #[test]
     69 fn domain_only_parse_value() {
     70     // Test whitespace and comment.
     71     assert!(
     72         DomainOnly::parse_value("   \t\t  \t\t  \twww.example.com#asdflkj asdf alskdfj  ")
     73             .is_ok_and(|val| match val {
     74                 Value::Domain(ref dom) => dom.domain.as_bytes() == b"www.example.com",
     75                 Value::Comment(_) | Value::Blank => false,
     76             })
     77     );
     78     assert!(
     79         DomainOnly::parse_value("   \t\t  \t\t  \twww.example.com   \t\t  ^  \t\t  ")
     80             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
     81     );
     82     // Test case-insensitivity.
     83     assert!(
     84         DomainOnly::parse_value("www.ExAMPle.CoM").is_ok_and(|val| match val {
     85             Value::Domain(ref dom) =>
     86                 DomainOnly::parse_value("www.example.com").is_ok_and(|val2| match val2 {
     87                     Value::Domain(ref dom2) => dom.cmp(dom2).is_eq(),
     88                     Value::Comment(_) | Value::Blank => false,
     89                 }),
     90             Value::Comment(_) | Value::Blank => false,
     91         })
     92     );
     93     // Test comment.
     94     assert!(
     95         DomainOnly::parse_value("   \t\t  \t\t  \t # hi").is_ok_and(|val| match val {
     96             Value::Comment(com) => com == " hi",
     97             Value::Domain(_) | Value::Blank => false,
     98         })
     99     );
    100     // Test blank.
    101     assert!(
    102         DomainOnly::parse_value("   \t\t  \t\t  \t ").is_ok_and(|val| matches!(val, Value::Blank))
    103     );
    104     // Test blank.
    105     assert!(
    106         DomainOnly::parse_value("example.xn--abc").is_ok_and(|val| matches!(val, Value::Domain(_)))
    107     );
    108     // Test invalid TLD.
    109     assert!(
    110         DomainOnly::parse_value("www.c1m").is_err_and(|err| err == FirefoxDomainErr::InvalidTld)
    111     );
    112 }
    113 #[test]
    114 fn hosts_parse_value() {
    115     // Test whitespace and comment.
    116     assert!(
    117         Hosts::parse_value("   \t\t  127.0.0.1\t\t  \twww.example.com#asdflkj asdf alskdfj  ")
    118             .is_ok_and(|val| match val {
    119                 Value::Domain(ref dom) => dom.domain.as_bytes() == b"www.example.com",
    120                 Value::Comment(_) | Value::Blank => false,
    121             })
    122     );
    123     assert!(
    124         Hosts::parse_value("   \t\t  0.0.0.0\t\t  \twww.example.com   \t\t  ^  \t\t  ")
    125             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
    126     );
    127     assert!(
    128         Hosts::parse_value("::1\twww .example.com")
    129             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
    130     );
    131     // Test invalid IP
    132     assert!(
    133         Hosts::parse_value("::2 www.example.com")
    134             .is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
    135     );
    136     assert!(
    137         Hosts::parse_value(":2 www.example.com")
    138             .is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
    139     );
    140     assert!(
    141         Hosts::parse_value("www.example.com").is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
    142     );
    143     assert!(
    144         Hosts::parse_value("10.4.2.256 www.example.com")
    145             .is_err_and(|e| e == FirefoxDomainErr::InvalidHostsIP)
    146     );
    147     // Test case-insensitivity.
    148     assert!(
    149         Hosts::parse_value(":: www.ExAMPle.Com").is_ok_and(|val| match val {
    150             Value::Domain(ref dom) =>
    151                 Hosts::parse_value("127.0.0.1 www.example.com").is_ok_and(|val2| match val2 {
    152                     Value::Domain(ref dom2) => dom.cmp(dom2).is_eq(),
    153                     Value::Comment(_) | Value::Blank => false,
    154                 }),
    155             Value::Comment(_) | Value::Blank => false,
    156         })
    157     );
    158     // Test comment.
    159     assert!(
    160         Hosts::parse_value("   \t\t  \t\t  \t # hi").is_ok_and(|val| match val {
    161             Value::Comment(com) => com == " hi",
    162             Value::Domain(_) | Value::Blank => false,
    163         })
    164     );
    165     // Test blank.
    166     assert!(Hosts::parse_value("   \t\t  \t\t  \t ").is_ok_and(|val| matches!(val, Value::Blank)));
    167 }
    168 #[test]
    169 fn wildcard_parse_value() {
    170     // Test bad asterisk.
    171     assert!(
    172         Wildcard::parse_value("*")
    173             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
    174     );
    175     assert!(
    176         Wildcard::parse_value("www*.example.com")
    177             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
    178     );
    179     assert!(
    180         Wildcard::parse_value("www.*.com")
    181             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
    182     );
    183     assert!(
    184         Wildcard::parse_value("*..com")
    185             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::EmptyLabel))
    186     );
    187     assert!(
    188         Wildcard::parse_value("www.com*")
    189             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
    190     );
    191     assert!(
    192         Wildcard::parse_value("ww*w.com")
    193             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b'*')))
    194     );
    195     // Test case-insensitivity.
    196     assert!(
    197         Wildcard::parse_value("*.wWw.ExamPLE.com").is_ok_and(|val| match val {
    198             Value::Domain(ref dom) =>
    199                 Wildcard::parse_value("*.www.example.com").is_ok_and(|val2| match val2 {
    200                     Value::Domain(ref dom2) =>
    201                         dom.cmp(dom2).is_eq()
    202                             && dom == dom2
    203                             && dom.proper_subdomains
    204                             && dom2.proper_subdomains,
    205                     Value::Comment(_) | Value::Blank => false,
    206                 }),
    207             Value::Comment(_) | Value::Blank => false,
    208         })
    209     );
    210     // Test proper subdomains.
    211     assert!(
    212         Wildcard::parse_value("*.www.example.com").is_ok_and(|val| match val {
    213             Value::Domain(ref dom) =>
    214                 dom.domain.as_bytes() == b"www.example.com" && dom.proper_subdomains,
    215             Value::Comment(_) | Value::Blank => false,
    216         })
    217     );
    218     // Test comment.
    219     assert!(
    220         Wildcard::parse_value("   \t\t  \t\t  \t*.www.example.com#asdflkj asdf alskdfj  ")
    221             .is_ok_and(|val| match val {
    222                 Value::Domain(ref dom) =>
    223                     dom.domain.as_bytes() == b"www.example.com" && dom.proper_subdomains,
    224                 Value::Comment(_) | Value::Blank => false,
    225             })
    226     );
    227     assert!(
    228         Wildcard::parse_value("   \t\t  \t\t  \twww.example.com #asdflkj asdf alskdfj  ")
    229             .is_ok_and(|val| match val {
    230                 Value::Domain(ref dom) =>
    231                     dom.domain.as_bytes() == b"www.example.com" && !dom.proper_subdomains,
    232                 Value::Comment(_) | Value::Blank => false,
    233             })
    234     );
    235     // Test whitespace removal.
    236     assert!(
    237         Wildcard::parse_value("   \t\t  *.www.example.com   \t\t \t  ").is_ok_and(|val| {
    238             match val {
    239                 Value::Domain(ref dom) => {
    240                     dom.domain.as_bytes() == b"www.example.com" && dom.proper_subdomains
    241                 }
    242                 Value::Comment(_) | Value::Blank => false,
    243             }
    244         })
    245     );
    246     assert!(
    247         Wildcard::parse_value("\t\t  \twww.example.com   \t\t   \t\t  ").is_ok_and(|val| {
    248             match val {
    249                 Value::Domain(ref dom) => {
    250                     dom.domain.as_bytes() == b"www.example.com" && !dom.proper_subdomains
    251                 }
    252                 Value::Comment(_) | Value::Blank => false,
    253             }
    254         })
    255     );
    256     assert!(
    257         Wildcard::parse_value("www .example.com")
    258             .is_err_and(|e| e == FirefoxDomainErr::InvalidDomain(DomainErr::InvalidByte(b' ')))
    259     );
    260     // Test 127 labels after wildcard error.
    261     assert!(Wildcard::parse_value("*.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_err_and(|e| e == FirefoxDomainErr::InvalidWildcardDomain));
    262     // Test 126 labels after wildcard is ok.
    263     assert!(Wildcard::parse_value("*.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_ok_and(|val| match val {
    264             Value::Domain(ref dom) => dom.domain.iter().count() == 126 && dom.proper_subdomains,
    265             Value::Comment(_) | Value::Blank => false,
    266         }));
    267     // Test comment.
    268     assert!(
    269         Wildcard::parse_value("   \t\t  \t\t  \t # hi").is_ok_and(|val| match val {
    270             Value::Comment(com) => com == " hi",
    271             Value::Domain(_) | Value::Blank => false,
    272         })
    273     );
    274     // Test blank.
    275     assert!(
    276         Wildcard::parse_value("   \t\t  \t\t  \t ").is_ok_and(|val| matches!(val, Value::Blank))
    277     );
    278 }
    279 #[test]
    280 fn rpz_parse_value() {
    281     assert!(
    282         RpzDomain::parse_value("*.www.example.com").is_ok_and(|val| {
    283             let dom = val.unwrap_domain();
    284             dom.is_proper_subdomains() && dom.domain().as_bytes() == b"www.example.com"
    285         })
    286     );
    287     assert!(
    288         RpzDomain::parse_value("||www.example.com").is_ok_and(|val| {
    289             let dom = val.unwrap_domain();
    290             dom.is_subdomains() && dom.domain().as_bytes() == b"www.example.com"
    291         })
    292     );
    293     assert!(
    294         RpzDomain::parse_value("0.0.0.0 www.example.com").is_ok_and(|val| {
    295             let dom = val.unwrap_domain();
    296             !(dom.is_subdomains() || dom.is_proper_subdomains())
    297                 && dom.domain().as_bytes() == b"www.example.com"
    298         })
    299     );
    300     assert!(RpzDomain::parse_value("www.example.com").is_ok_and(|val| {
    301         let dom = val.unwrap_domain();
    302         !(dom.is_subdomains() || dom.is_proper_subdomains())
    303             && dom.domain().as_bytes() == b"www.example.com"
    304     }));
    305     // Test case-insensitivity.
    306     assert!(
    307         RpzDomain::parse_value("*.Www.ExaMPle.COm").is_ok_and(|val| {
    308             let dom = val.unwrap_domain();
    309             RpzDomain::parse_value("*.www.example.com").is_ok_and(|val2| {
    310                 let dom2 = val2.unwrap_domain();
    311                 dom.is_proper_subdomains()
    312                     && dom2.is_proper_subdomains()
    313                     && dom == dom2
    314                     && dom.cmp(&dom2).is_eq()
    315             })
    316         })
    317     );
    318     // Test comment.
    319     assert!(
    320         RpzDomain::parse_value("   \t\t  \t\t  \t # hi").is_ok_and(|val| match val {
    321             Value::Comment(com) => com == " hi",
    322             Value::Domain(_) | Value::Blank => false,
    323         })
    324     );
    325     assert!(
    326         RpzDomain::parse_value("   \t\t  \t\t  \t ! hi").is_ok_and(|val| match val {
    327             Value::Comment(com) => com == " hi",
    328             Value::Domain(_) | Value::Blank => false,
    329         })
    330     );
    331     // Test blank.
    332     assert!(
    333         RpzDomain::parse_value("   \t\t  \t\t  \t ").is_ok_and(|val| matches!(val, Value::Blank))
    334     );
    335 }
    336 #[expect(clippy::expect_used, clippy::unwrap_in_result, reason = "OK in tests")]
    337 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
    338 #[expect(clippy::nonminimal_bool, reason = "want to test all ord ops")]
    339 #[test]
    340 fn rpz_ord_and_eq() -> Result<(), &'static str> {
    341     "www.bar.com,*.www.bar.com,||www.bar.com,*.bar.com,||bar.com,Example.com,WwW.exaMple.com,*.www.example.com,||www.example.com,*.example.com,||example.com,FOo.coM,Www.foo.com,*.foo.com,*.coM,example.net,*.net".split(',').try_fold(RpzDomain::DomainOnly(DomainOnly::parse_value("bar.com").expect("bug in DomainOnly::parse_value").unwrap_domain()), |prev, slice| {
    342             let cur = if slice.as_bytes()[0] == b'|' {
    343                 RpzDomain::Adblock(Adblock::parse_value(slice).expect("Bug in Adblock::parse_value").unwrap_domain())
    344             } else {
    345                 RpzDomain::Wildcard(Wildcard::parse_value(slice).expect("Bug in Wildcard::parse_value").unwrap_domain())
    346             };
    347             if prev < cur && cur > prev && prev == prev && cur == cur {
    348                 Ok(cur)
    349             } else {
    350                 Err("PartialEq or Ord are not correctly implemented for RpzDomain.")
    351             }
    352         }).map(|_| ())
    353 }
    354 #[expect(clippy::expect_used, reason = "OK in tests")]
    355 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
    356 #[test]
    357 fn superset_set() {
    358     let mut iter = "*.NeT,*.net,www.bar.com,*.net,*.www.bar.com,||www.bar.com,*.bar.com,||bar.com,example.com,www.example.com,*.www.example.com,||www.example.com,*.example.com,||example.com,foo.com,www.foo.com,*.foo.com,*.com,example.net,*.abc.abc,||aawww.abc,abc.abc".split(',').fold(SupersetSet::new(), |mut doms, slice| {
    359             _ = doms.insert(if slice.as_bytes()[0] == b'|' {
    360                 RpzDomain::Adblock(Adblock::parse_value(slice).expect("Bug in Adblock::parse_value").unwrap_domain())
    361             } else {
    362                 RpzDomain::Wildcard(Wildcard::parse_value(slice).expect("Bug in Wildcard::parse_value").unwrap_domain())
    363             });
    364             doms
    365         }).into_iter();
    366     assert!(
    367         iter.next()
    368             .is_some_and(|d| { d.domain().as_bytes() == b"aawww.abc" && d.is_subdomains() })
    369     );
    370     assert!(
    371         iter.next()
    372             .is_some_and(|d| { d.domain().as_bytes() == b"abc.abc" && d.is_domain() })
    373     );
    374     assert!(
    375         iter.next()
    376             .is_some_and(|d| { d.domain().as_bytes() == b"abc.abc" && d.is_proper_subdomains() })
    377     );
    378     assert!(
    379         iter.next()
    380             .is_some_and(|d| { d.domain().as_bytes() == b"com" && d.is_proper_subdomains() })
    381     );
    382     assert!(
    383         iter.next()
    384             .is_some_and(|d| { d.domain().as_bytes() == b"NeT" && d.is_proper_subdomains() })
    385     );
    386     assert!(iter.next().is_none());
    387 }
    388 #[test]
    389 fn card() {
    390     // Geometric series.
    391     // We can have two labels each with one character,
    392     // one label with one to three characters, or 0 labels.
    393     // This is 1 + 52 + 52^2 + 52^3 + 52^2 = (1-52^4)/(1-52) + 52^2 = (52^4 - 1)/51 + 52^2 = 146069.
    394     assert!(Adblock::parse_value("||a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_ok_and(|val| { let dom = val.unwrap_domain(); dom.domain.len().get() == 249 && dom.domain.iter().count() == 125 && dom.domain_count() == BigUint::new(vec![146_069]) }));
    395     // A subdomain of length 252 or 253 gets converted to a domain.
    396     assert!(Adblock::parse_value("||a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a").is_ok_and(|val| { let dom = val.unwrap_domain(); dom.domain.iter().count() == 127 && !dom.subdomains && dom.domain_count() == BigUint::new(vec![1]) }));
    397     // Pre-calculated manually.
    398     // This is the number of domains possible between 2 and 252 characters.
    399     assert!(Wildcard::parse_value("*.a").is_ok_and(|val| {
    400         val.unwrap_domain().domain_count()
    401             == BigUint::new(vec![
    402                 375_288_404,
    403                 2_460_223_985,
    404                 1_334_358_771,
    405                 2_543_621_408,
    406                 2_519_466_280,
    407                 1_133_682_239,
    408                 3_589_178_618,
    409                 348_125_705,
    410                 1_709_233_643,
    411                 958_334_503,
    412                 3_780_539_710,
    413                 2_181_893_897,
    414                 2_457_156_833,
    415                 3_204_765_645,
    416                 2_728_103_430,
    417                 1_817_547_150,
    418                 3_102_358_416,
    419                 444_185_044,
    420                 3_659_003_776,
    421                 10_341_713,
    422                 306_326_206,
    423                 1_336_386_425,
    424                 3_942_332_649,
    425                 2_036_577_878,
    426                 2_460_939_277,
    427                 3_976_861_337,
    428                 2_101_094_571,
    429                 2_241_770_079,
    430                 2_667_853_164,
    431                 3_687_350_273,
    432                 109_356_153,
    433                 3_455_569_358,
    434                 2_333_076_459,
    435                 2_433_207_896,
    436                 1_553_903_141,
    437                 2_621_943_843,
    438                 4_223_295_645,
    439                 1_753_858_368,
    440                 130_924_388,
    441                 965_594_304,
    442                 3_942_586_845,
    443                 1_573_844_087,
    444                 4_237_886_128,
    445                 481_383_133,
    446                 56_931_017,
    447             ])
    448     }));
    449 }