blob: 996fc74d5f56f362a4632ab564b6a824ccf6b048 [file] [log] [blame]
extern crate rspec;
use {List, request};
use errors::ErrorKind;
use self::rspec::context::rdescribe;
#[test]
fn list_behaviour() {
let list = List::fetch().unwrap();
rdescribe("the list", |ctx| {
ctx.it("should not be empty", || {
assert!(!list.all().is_empty());
});
ctx.it("should have ICANN domains", || {
assert!(!list.icann().is_empty());
});
ctx.it("should have private domains", || {
assert!(!list.private().is_empty());
});
ctx.it("should have at least 1000 domains", || {
assert!(list.all().len() > 1000);
});
});
rdescribe("the official test", |_| {
let tests = "https://raw.githubusercontent.com/publicsuffix/list/master/tests/tests.txt";
let body = request(tests).unwrap();
let mut parse = false;
for (i, line) in body.lines().enumerate() {
match line {
line if line.trim().is_empty() => { parse = true; continue; }
line if line.starts_with("//") => { continue; }
line => {
if !parse { continue; }
let mut test = line.split_whitespace().peekable();
if test.peek().is_none() {
continue;
}
let input = match test.next() {
Some("null") => "",
Some(res) => res,
None => { panic!(format!("line {} of the test file doesn't seem to be valid", i)); },
};
let (expected_root, expected_suffix) = match test.next() {
Some("null") => (None, None),
Some(root) => {
let suffix = {
let parts: Vec<&str> = root.split('.').rev().collect();
(&parts[..parts.len()-1]).iter().rev()
.map(|part| *part)
.collect::<Vec<_>>()
.join(".")
};
(Some(root.to_string()), Some(suffix.to_string()))
},
None => { panic!(format!("line {} of the test file doesn't seem to be valid", i)); },
};
let (found_root, found_suffix) = match list.parse_domain(input) {
Ok(domain) => {
let found_root = match domain.root() {
Some(found) => Some(found.to_string()),
None => None,
};
let found_suffix = match domain.suffix() {
Some(found) => Some(found.to_string()),
None => None,
};
(found_root, found_suffix)
},
Err(_) => (None, None),
};
if expected_root != found_root || (expected_root.is_some() && expected_suffix != found_suffix) {
let msg = format!("\n\nGiven `{}`:\nWe expected root domain to be `{:?}` and suffix be `{:?}`\nBut instead, we have `{:?}` as root domain and `{:?}` as suffix.\nWe are on line {} of `test_psl.txt`.\n\n",
input, expected_root, expected_suffix, found_root, found_suffix, i+1);
panic!(msg);
}
}
}
}
});
rdescribe("a domain", |ctx| {
ctx.it("should allow fully qualified domain names", || {
assert!(list.parse_domain("example.com.").is_ok());
});
ctx.it("should not allow more than 1 trailing dot", || {
assert!(list.parse_domain("example.com..").is_err());
match *list.parse_domain("example.com..").unwrap_err().kind() {
ErrorKind::InvalidDomain(ref domain) => assert_eq!(domain, "example.com.."),
_ => assert!(false),
}
});
ctx.it("should allow a single label with a single trailing dot", || {
assert!(list.parse_domain("com.").is_ok());
});
ctx.it("should always have a suffix for single-label domains", || {
let domains = vec![
// real TLDs
"com",
"saarland",
"museum.",
// non-existant TLDs
"localhost",
"madeup",
"with-dot.",
];
for domain in domains {
let res = list.parse_domain(domain).unwrap();
assert_eq!(res.suffix(), Some(domain.trim_right_matches('.')));
assert!(res.root().is_none());
}
});
ctx.it("should have the same result with or without the trailing dot", || {
assert_eq!(list.parse_domain("com.").unwrap(), list.parse_domain("com").unwrap());
});
ctx.it("should not have empty labels", || {
assert!(list.parse_domain("exa..mple.com").is_err());
});
ctx.it("should not contain spaces", || {
assert!(list.parse_domain("exa mple.com").is_err());
});
ctx.it("should not start with a dash", || {
assert!(list.parse_domain("-example.com").is_err());
});
ctx.it("should not end with a dash", || {
assert!(list.parse_domain("example-.com").is_err());
});
ctx.it("should not contain /", || {
assert!(list.parse_domain("exa/mple.com").is_err());
});
ctx.it("should not have a label > 63 characters", || {
let mut too_long_domain = String::from("a");
for _ in 0..64 {
too_long_domain.push_str("a");
}
too_long_domain.push_str(".com");
assert!(list.parse_domain(&too_long_domain).is_err());
});
ctx.it("should not be an IPv4 address", || {
assert!(list.parse_domain("127.38.53.247").is_err());
});
ctx.it("should not be an IPv6 address", || {
assert!(list.parse_domain("fd79:cdcb:38cc:9dd:f686:e06d:32f3:c123").is_err());
});
ctx.it("should allow numbers only labels that are not the tld", || {
assert!(list.parse_domain("127.com").is_ok());
});
ctx.it("should not have more than 127 labels", || {
let mut too_many_labels_domain = String::from("a");
for _ in 0..126 {
too_many_labels_domain.push_str(".a");
}
too_many_labels_domain.push_str(".com");
assert!(list.parse_domain(&too_many_labels_domain).is_err());
});
ctx.it("should not have more than 253 characters", || {
let mut too_many_chars_domain = String::from("aaaaa");
for _ in 0..50 {
too_many_chars_domain.push_str(".aaaaaa");
}
too_many_chars_domain.push_str(".com");
assert!(list.parse_domain(&too_many_chars_domain).is_err());
});
});
rdescribe("a DNS name", |ctx| {
ctx.it("should allow extended characters", || {
let names = vec![
"_tcp.example.com.",
"_telnet._tcp.example.com.",
"*.example.com.",
"ex!mple.com.",
];
for name in names {
println!("{} should be valid", name);
assert!(list.parse_dns_name(name).is_ok());
}
});
ctx.it("should allow extracting the correct domain name where possible", || {
let names = vec![
("_tcp.example.com.", "example.com"),
("_telnet._tcp.example.com.", "example.com"),
("*.example.com.", "example.com"),
];
for (name, domain) in names {
println!("{}'s root domain should be {}", name, domain);
let name = list.parse_dns_name(name).unwrap();
let root = name.domain().unwrap().root();
assert_eq!(root, Some(domain));
}
});
ctx.it("should not extract any domain where not possible", || {
let names = vec![
"_tcp.com.",
"_telnet._tcp.com.",
"*.com.",
"ex!mple.com.",
];
for name in names {
println!("{} should not have any root domain", name);
let name = list.parse_dns_name(name).unwrap();
assert!(name.domain().is_none());
}
});
ctx.it("should not allow more than 1 trailing dot", || {
assert!(list.parse_dns_name("example.com..").is_err());
match *list.parse_dns_name("example.com..").unwrap_err().kind() {
ErrorKind::InvalidDomain(ref domain) => assert_eq!(domain, "example.com.."),
_ => assert!(false),
}
});
});
rdescribe("a host", |ctx| {
ctx.it("can be an IPv4 address", || {
assert!(list.parse_host("127.38.53.247").is_ok());
});
ctx.it("can be an IPv6 address", || {
assert!(list.parse_host("fd79:cdcb:38cc:9dd:f686:e06d:32f3:c123").is_ok());
});
ctx.it("can be a domain name", || {
assert!(list.parse_host("example.com").is_ok());
});
ctx.it("cannot be neither an IP address nor a domain name", || {
assert!(list.parse_host("23.56").is_err());
});
ctx.it("an IPv4 address should parse into an IP object", || {
assert!(list.parse_host("127.38.53.247").unwrap().is_ip());
});
ctx.it("an IPv6 address should parse into an IP object", || {
assert!(list.parse_host("fd79:cdcb:38cc:9dd:f686:e06d:32f3:c123").unwrap().is_ip());
});
ctx.it("a domain name should parse into a domain object", || {
assert!(list.parse_host("example.com").unwrap().is_domain());
});
ctx.it("can be parsed from a URL with a domain as hostname", || {
assert!(list.parse_url("https://publicsuffix.org/list/").unwrap().is_domain());
});
ctx.it("can be parsed from a URL with an IP address as hostname", || {
assert!(list.parse_url("https://127.38.53.247:8080/list/").unwrap().is_ip());
});
ctx.it("can be parsed from a URL using `parse_str`", || {
assert!(list.parse_str("https://127.38.53.247:8080/list/").unwrap().is_ip());
});
ctx.it("can be parsed from a non-URL using `parse_str`", || {
assert!(list.parse_str("example.com").unwrap().is_domain());
});
});
rdescribe("a parsed email", |ctx| {
ctx.it("should allow valid email addresses", || {
let emails = vec![
"prettyandsimple@example.com",
"very.common@example.com",
"disposable.style.email.with+symbol@example.com",
"other.email-with-dash@example.com",
"x@example.com",
"example-indeed@strange-example.com",
"#!$%&'*+-/=?^_`{}|~@example.org",
"example@s.solutions",
"user@[fd79:cdcb:38cc:9dd:f686:e06d:32f3:c123]",
r#""Abc\@def"@example.com"#,
r#""Fred Bloggs"@example.com"#,
r#""Joe\\Blow"@example.com"#,
r#""Abc@def"@example.com"#,
r#"customer/department=shipping@example.com"#,
"$A12345@example.com",
"!def!xyz%abc@example.com",
"_somename@example.com",
];
for email in emails {
println!("{} should be valid", email);
assert!(list.parse_email(email).is_ok());
}
});
ctx.it("should reject invalid email addresses", || {
let emails = vec![
"Abc.example.com",
"A@b@c@example.com",
r#"a"b(c)d,e:f;g<h>i[j\k]l@example.com"#,
r#""just"not"right@example.com"#,
r#"this is"not\allowed@example.com"#,
r#"this\ still\"not\\allowed@example.com"#,
"1234567890123456789012345678901234567890123456789012345678901234+x@example.com",
"john..doe@example.com",
"john.doe@example..com",
" prettyandsimple@example.com",
"prettyandsimple@example.com ",
];
for email in emails {
println!("{} should not be valid", email);
assert!(list.parse_email(email).is_err());
}
});
ctx.it("should allow parsing emails as str", || {
assert!(list.parse_str("prettyandsimple@example.com").unwrap().is_domain());
});
ctx.it("should allow parsing emails as URL", || {
assert!(list.parse_url("mailto://prettyandsimple@example.com").unwrap().is_domain());
});
ctx.it("should allow parsing IDN email addresses", || {
let emails = vec![
r#"Pelé@example.com"#,
r#"δοκιμή@παράδειγμα.δοκιμή"#,
r#"我買@屋企.香港"#,
r#"甲斐@黒川.日本"#,
r#"чебурашка@ящик-с-апельсинами.рф"#,
r#"संपर्क@डाटामेल.भारत"#,
r#"用户@例子.广告"#,
];
for email in emails {
println!("{} should be valid", email);
assert!(list.parse_email(email).is_ok());
}
});
});
}