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 }