package publicsuffix
import (
"reflect"
"testing"
xlib "golang.org/x/net/publicsuffix"
)
func TestNewListFromString(t *testing.T) {
src := `
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// ===BEGIN ICANN DOMAINS===
// ac : http://en.wikipedia.org/wiki/.ac
ac
com.ac
// ===END ICANN DOMAINS===
// ===BEGIN PRIVATE DOMAINS===
// Google, Inc.
blogspot.com
// ===END PRIVATE DOMAINS===
`
list, err := NewListFromString(src, nil)
if err != nil {
t.Fatalf("Parse returned an error: %v", err)
}
if want, got := 3, list.Size(); want != got {
t.Errorf("Parse returned a list with %v rules, want %v", got, want)
t.Fatalf("%v", list.rules)
}
rules := list.rules
var testRules []Rule
testRules = []Rule{}
for _, rule := range rules {
if rule.Private == false {
testRules = append(testRules, *rule)
}
}
if want, got := 2, len(testRules); want != got {
t.Errorf("Parse returned a list with %v IANA rules, want %v", got, want)
t.Fatalf("%v", testRules)
}
testRules = []Rule{}
for _, rule := range rules {
if rule.Private == true {
testRules = append(testRules, *rule)
}
}
if want, got := 1, len(testRules); want != got {
t.Errorf("Parse returned a list with %v PRIVATE rules, want %v", got, want)
t.Fatalf("%v", testRules)
}
}
func TestNewListFromString_IDNAInputIsUnicode(t *testing.T) {
src := `
// xn--d1alf ("mkd", Macedonian) : MK
// MARnet
мкд
// xn--l1acc ("mon", Mongolian) : MN
xn--l1acc
`
list, err := NewListFromString(src, nil)
if err != nil {
t.Fatalf("Parse returned error: %v", err)
}
if want, got := 2, list.Size(); want != got {
t.Errorf("Parse returned a list with %v rules, want %v", got, want)
t.Fatalf("%v", list.rules)
}
if rule := list.Find("hello.xn--d1alf", &FindOptions{DefaultRule: nil}); rule == nil {
t.Fatalf("Find(%v) returned nil", "hello.xn--d1alf")
}
if rule := list.Find("hello.мкд", &FindOptions{DefaultRule: nil}); rule != nil {
t.Fatalf("Find(%v) expected to return nil, got %v", "hello.xn--d1alf", rule)
}
if rule := list.Find("hello.xn--l1acc", &FindOptions{DefaultRule: nil}); rule == nil {
t.Fatalf("Find(%v) returned nil", "hello.xn--l1acc")
}
}
func TestNewListFromString_IDNAInputIsAscii(t *testing.T) {
src := `
// xn--d1alf ("mkd", Macedonian) : MK
// MARnet
xn--d1alf
// xn--l1acc ("mon", Mongolian) : MN
xn--l1acc
`
list, err := NewListFromString(src, &ParserOption{ASCIIEncoded: true})
if err != nil {
t.Fatalf("Parse returned error: %v", err)
}
if want, got := 2, list.Size(); want != got {
t.Errorf("Parse returned a list with %v rules, want %v", got, want)
t.Fatalf("%v", list.rules)
}
if rule := list.Find("hello.xn--d1alf", &FindOptions{DefaultRule: nil}); rule == nil {
t.Fatalf("Find(%v) returned nil", "hello.xn--d1alf")
}
if rule := list.Find("hello.мкд", &FindOptions{DefaultRule: nil}); rule != nil {
t.Fatalf("Find(%v) expected to return nil, got %v", "hello.xn--d1alf", rule)
}
if rule := list.Find("hello.xn--l1acc", &FindOptions{DefaultRule: nil}); rule == nil {
t.Fatalf("Find(%v) returned nil", "hello.xn--l1acc")
}
}
func TestNewListFromFile(t *testing.T) {
list, err := NewListFromFile("../fixtures/list-simple.txt", nil)
if err != nil {
t.Fatalf("Parse returned an error: %v", err)
}
if want, got := 3, list.Size(); want != got {
t.Errorf("Parse returned a list with %v rules, want %v", got, want)
t.Fatalf("%v", list.rules)
}
rules := list.rules
var testRules []Rule
testRules = []Rule{}
for _, rule := range rules {
if rule.Private == false {
testRules = append(testRules, *rule)
}
}
if want, got := 2, len(testRules); want != got {
t.Errorf("Parse returned a list with %v IANA rules, want %v", got, want)
t.Fatalf("%v", testRules)
}
testRules = []Rule{}
for _, rule := range rules {
if rule.Private == true {
testRules = append(testRules, *rule)
}
}
if want, got := 1, len(testRules); want != got {
t.Errorf("Parse returned a list with %v PRIVATE rules, want %v", got, want)
t.Fatalf("%v", testRules)
}
}
func TestListAddRule(t *testing.T) {
list := NewList()
if list.Size() != 0 {
t.Fatalf("Empty list should have 0 rules, got %v", list.Size())
}
rule := MustNewRule("com")
list.AddRule(rule)
if list.Size() != 1 {
t.Fatalf("List should have 1 rule, got %v", list.Size())
}
for _, got := range list.rules {
if !reflect.DeepEqual(rule, got) {
t.Fatalf("List[0] expected to be %v, got %v", rule, got)
}
}
}
type listFindTestCase struct {
input string
expected *Rule
}
func TestListFind(t *testing.T) {
src := `
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// ===BEGIN ICANN DOMAINS===
// com
com
// uk
*.uk
*.sch.uk
!bl.uk
!british-library.uk
// io
io
// jp
jp
*.kawasaki.jp
*.kitakyushu.jp
*.kobe.jp
*.nagoya.jp
*.sapporo.jp
*.sendai.jp
*.yokohama.jp
!city.kawasaki.jp
!city.kitakyushu.jp
!city.kobe.jp
!city.nagoya.jp
!city.sapporo.jp
!city.sendai.jp
!city.yokohama.jp
// ===END ICANN DOMAINS===
// ===BEGIN PRIVATE DOMAINS===
// Google, Inc.
blogspot.com
// ===END PRIVATE DOMAINS===
`
// TODO(weppos): ability to set type to a rule.
p1 := MustNewRule("blogspot.com")
p1.Private = true
testCases := []listFindTestCase{
// match standard
{"example.com", MustNewRule("com")},
{"foo.example.com", MustNewRule("com")},
// match wildcard
{"example.uk", MustNewRule("*.uk")},
{"example.co.uk", MustNewRule("*.uk")},
{"foo.example.co.uk", MustNewRule("*.uk")},
// match exception
{"british-library.uk", MustNewRule("!british-library.uk")},
{"foo.british-library.uk", MustNewRule("!british-library.uk")},
// match default rule
{"test", DefaultRule},
{"example.test", DefaultRule},
{"foo.example.test", DefaultRule},
// match private
{"blogspot.com", p1},
{"foo.blogspot.com", p1},
// input is wildcard rule
{"kobe.jp", MustNewRule("jp")},
}
list, err := NewListFromString(src, nil)
if err != nil {
t.Fatalf("Unable to parse list: %v", err)
}
for _, testCase := range testCases {
if want, got := testCase.expected, list.Find(testCase.input, nil); !reflect.DeepEqual(want, got) {
t.Errorf("Find(%v) = %v, want %v", testCase.input, got, want)
}
}
}
func TestNewRule_Normal(t *testing.T) {
rule := MustNewRule("com")
want := &Rule{Type: NormalType, Value: "com", Length: 1}
if !reflect.DeepEqual(want, rule) {
t.Fatalf("NewRule returned %v, want %v", rule, want)
}
}
func TestNewRule_Wildcard(t *testing.T) {
rule := MustNewRule("*.example.com")
want := &Rule{Type: WildcardType, Value: "example.com", Length: 3}
if !reflect.DeepEqual(want, rule) {
t.Fatalf("NewRule returned %v, want %v", rule, want)
}
}
func TestNewRule_Exception(t *testing.T) {
rule := MustNewRule("!example.com")
want := &Rule{Type: ExceptionType, Value: "example.com", Length: 2}
if !reflect.DeepEqual(want, rule) {
t.Fatalf("NewRule returned %v, want %v", rule, want)
}
}
func TestNewRule_FromASCII(t *testing.T) {
rule, _ := NewRule("xn--l1acc")
if want := "xn--l1acc"; rule.Value != want {
t.Fatalf("NewRule == %v, want %v", rule.Value, want)
}
}
func TestNewRule_FromUnicode(t *testing.T) {
rule, _ := NewRule("мон")
// No transformation is performed
if want := "мон"; rule.Value != want {
t.Fatalf("NewRule == %v, want %v", rule.Value, want)
}
}
func TestNewRuleUnicode_FromASCII(t *testing.T) {
rule, _ := NewRuleUnicode("xn--l1acc")
if want := "xn--l1acc"; rule.Value != want {
t.Fatalf("NewRule == %v, want %v", rule.Value, want)
}
}
func TestNewRuleUnicode_FromUnicode(t *testing.T) {
rule, _ := NewRuleUnicode("мон")
if want := "xn--l1acc"; rule.Value != want {
t.Fatalf("NewRule == %v, want %v", rule.Value, want)
}
}
type ruleMatchTestCase struct {
rule *Rule
input string
expected bool
}
func TestRuleMatch(t *testing.T) {
testCases := []ruleMatchTestCase{
// standard match
{MustNewRule("uk"), "uk", true},
{MustNewRule("uk"), "example.uk", true},
{MustNewRule("uk"), "example.co.uk", true},
{MustNewRule("co.uk"), "example.co.uk", true},
// special rules match
{MustNewRule("*.com"), "com", false},
{MustNewRule("*.com"), "example.com", true},
{MustNewRule("*.com"), "foo.example.com", true},
{MustNewRule("!example.com"), "com", false},
{MustNewRule("!example.com"), "example.com", true},
{MustNewRule("!example.com"), "foo.example.com", true},
// TLD mismatch
{MustNewRule("gk"), "example.uk", false},
{MustNewRule("gk"), "example.co.uk", false},
// general mismatch
{MustNewRule("uk.co"), "example.co.uk", false},
{MustNewRule("go.uk"), "example.co.uk", false},
// rule is longer than input, should not match
{MustNewRule("co.uk"), "uk", false},
// partial matches/mismatches
{MustNewRule("co"), "example.co.uk", false},
{MustNewRule("example"), "example.uk", false},
{MustNewRule("le.it"), "example.it", false},
{MustNewRule("le.it"), "le.it", true},
{MustNewRule("le.it"), "foo.le.it", true},
}
for _, testCase := range testCases {
if testCase.rule.Match(testCase.input) != testCase.expected {
t.Errorf("Expected %v to %v match %v", testCase.rule.Value, testCase.expected, testCase.input)
}
}
}
type ruleDecomposeTestCase struct {
rule *Rule
input string
expected [2]string
}
func TestRuleDecompose(t *testing.T) {
testCases := []ruleDecomposeTestCase{
{MustNewRule("com"), "com", [2]string{"", ""}},
{MustNewRule("com"), "example.com", [2]string{"example", "com"}},
{MustNewRule("com"), "foo.example.com", [2]string{"foo.example", "com"}},
{MustNewRule("!british-library.uk"), "uk", [2]string{"", ""}},
{MustNewRule("!british-library.uk"), "british-library.uk", [2]string{"british-library", "uk"}},
{MustNewRule("!british-library.uk"), "foo.british-library.uk", [2]string{"foo.british-library", "uk"}},
{MustNewRule("*.com"), "com", [2]string{"", ""}},
{MustNewRule("*.com"), "example.com", [2]string{"", ""}},
{MustNewRule("*.com"), "foo.example.com", [2]string{"foo", "example.com"}},
{MustNewRule("*.com"), "bar.foo.example.com", [2]string{"bar.foo", "example.com"}},
}
for _, testCase := range testCases {
if got := testCase.rule.Decompose(testCase.input); !reflect.DeepEqual(got, testCase.expected) {
t.Errorf("Expected %v to decompose %v into %v, got %v", testCase.rule.Value, testCase.input, testCase.expected, got)
}
}
}
func TestLabels(t *testing.T) {
testCases := map[string][]string{
"com": {"com"},
"example.com": {"example", "com"},
"www.example.com": {"www", "example", "com"},
}
for input, expected := range testCases {
if output := Labels(input); !reflect.DeepEqual(output, expected) {
t.Errorf("Labels(%v) = %v, want %v", input, output, expected)
}
}
}
func TestParseFromListWithOptions_RuleFound(t *testing.T) {
list := NewList()
rule := MustNewRule("com")
_ = list.AddRule(rule)
input := "foobar.com"
got, err := ParseFromListWithOptions(list, "foobar.com", &FindOptions{IgnorePrivate: true})
if err != nil {
t.Fatalf("ParseFromListWithOptions(%v) error: %v", input, err)
}
want := &DomainName{TLD: "com", SLD: "foobar", Rule: rule}
if !reflect.DeepEqual(want, got) {
t.Errorf("ParseFromListWithOptions(%v) = %v, want %v", input, got, want)
}
}
func TestParseFromListWithOptions_RuleNotFoundDefaultNil(t *testing.T) {
list := NewList()
rule := MustNewRule("com")
_ = list.AddRule(rule)
input := "foobar.localdomain"
_, err := ParseFromListWithOptions(list, "foobar.localdomain", &FindOptions{IgnorePrivate: true})
if err == nil {
t.Fatalf("ParseFromListWithOptions(%v) should have returned error", input)
}
if want := "no rule matching name foobar.localdomain"; err.Error() != want {
t.Errorf("Error expected to be %v, got %v", want, err)
}
}
func TestParseFromListWithOptions_RuleNotFoundDefaultRule(t *testing.T) {
list := NewList()
rule := MustNewRule("com")
_ = list.AddRule(rule)
input := "foobar.localdomain"
got, err := ParseFromListWithOptions(list, "foobar.localdomain", &FindOptions{IgnorePrivate: true, DefaultRule: DefaultRule})
if err != nil {
t.Fatalf("ParseFromListWithOptions(%v) error: %v", input, err)
}
want := &DomainName{TLD: "localdomain", SLD: "foobar", Rule: DefaultRule}
if !reflect.DeepEqual(want, got) {
t.Errorf("ParseFromListWithOptions(%v) = %v, want %v", input, got, want)
}
}
func TestToASCII(t *testing.T) {
testCases := []string{
"example.com",
".example.com",
"..example.com",
}
for _, input := range testCases {
output, err := ToASCII(input)
if err != nil {
t.Errorf("ToASCII(%s) returned error", input)
}
if output != input {
t.Errorf("ToASCII(%s) = %s, want %s", input, output, input)
}
}
}
func TestCookieJarList(t *testing.T) {
testCases := map[string]string{
"example.com": "com",
"www.example.com": "com",
"example.co.uk": "co.uk",
"www.example.co.uk": "co.uk",
"example.blogspot.com": "blogspot.com",
"www.example.blogspot.com": "blogspot.com",
"parliament.uk": "uk",
"www.parliament.uk": "uk",
// not listed
"www.example.test": "test",
}
for input, suffix := range testCases {
if output := CookieJarList.PublicSuffix(input); output != suffix {
t.Errorf("CookieJarList.PublicSuffix(%v) = %v, want %v", input, output, suffix)
}
}
}
var benchmarkTestCases = map[string]string{
"example.com": "example.com",
"example.id.au": "example.id.au",
"www.ck": "www.ck",
"foo.bar.xn--55qx5d.cn": "bar.xn--55qx5d.cn",
"a.b.c.minami.fukuoka.jp": "c.minami.fukuoka.jp",
"posts-and-telecommunications.museum": "",
"www.example.pvt.k12.ma.us": "example.pvt.k12.ma.us",
"many.lol": "many.lol",
"the.russian.for.moscow.is.xn--80adxhks": "is.xn--80adxhks",
"blah.blah.s3-us-west-1.amazonaws.com": "blah.s3-us-west-1.amazonaws.com",
"thing.dyndns.org": "thing.dyndns.org",
"nosuchtld": "",
}
func benchmarkDomain(b *testing.B, domainFunc func(string) (string, error)) {
var got string
for i := 0; i < b.N; i++ {
for input := range benchmarkTestCases {
got, _ = domainFunc(input)
}
}
_ = got
}
func BenchmarkDomain(b *testing.B) {
benchmarkDomain(b, Domain)
}
func BenchmarkXNet(b *testing.B) {
benchmarkDomain(b, xlib.EffectiveTLDPlusOne)
}