1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build go1.10
6
7package idna
8
9import "testing"
10
11// TestLabelErrors tests strings returned in case of error. All results should
12// be identical to the reference implementation and can be verified at
13// https://unicode.org/cldr/utility/idna.jsp. The reference implementation,
14// however, seems to not display Bidi and ContextJ errors.
15//
16// In some cases the behavior of browsers is added as a comment. In all cases,
17// whenever a resolve search returns an error here, Chrome will treat the input
18// string as a search string (including those for Bidi and Context J errors),
19// unless noted otherwise.
20func TestLabelErrors(t *testing.T) {
21	encode := func(s string) string { s, _ = encode(acePrefix, s); return s }
22	type kind struct {
23		name string
24		f    func(string) (string, error)
25	}
26	punyA := kind{"PunycodeA", punycode.ToASCII}
27	resolve := kind{"ResolveA", Lookup.ToASCII}
28	display := kind{"ToUnicode", Display.ToUnicode}
29	p := New(VerifyDNSLength(true), MapForLookup(), BidiRule())
30	lengthU := kind{"CheckLengthU", p.ToUnicode}
31	lengthA := kind{"CheckLengthA", p.ToASCII}
32	p = New(MapForLookup(), StrictDomainName(false))
33	std3 := kind{"STD3", p.ToASCII}
34	p = New(MapForLookup(), CheckHyphens(false))
35	hyphens := kind{"CheckHyphens", p.ToASCII}
36
37	testCases := []struct {
38		kind
39		input   string
40		want    string
41		wantErr string
42	}{
43		{lengthU, "", "", "A4"}, // From UTS 46 conformance test.
44		{lengthA, "", "", "A4"},
45
46		{lengthU, "xn--", "", "A4"},
47		{lengthU, "foo.xn--", "foo.", "A4"}, // TODO: is dropping xn-- correct?
48		{lengthU, "xn--.foo", ".foo", "A4"},
49		{lengthU, "foo.xn--.bar", "foo..bar", "A4"},
50
51		{display, "xn--", "", ""},
52		{display, "foo.xn--", "foo.", ""}, // TODO: is dropping xn-- correct?
53		{display, "xn--.foo", ".foo", ""},
54		{display, "foo.xn--.bar", "foo..bar", ""},
55
56		{lengthA, "a..b", "a..b", "A4"},
57		{punyA, ".b", ".b", ""},
58		// For backwards compatibility, the Punycode profile does not map runes.
59		{punyA, "\u3002b", "xn--b-83t", ""},
60		{punyA, "..b", "..b", ""},
61
62		{lengthA, ".b", ".b", "A4"},
63		{lengthA, "\u3002b", ".b", "A4"},
64		{lengthA, "..b", "..b", "A4"},
65		{lengthA, "b..", "b..", ""},
66
67		// Sharpened Bidi rules for Unicode 10.0.0. Apply for ALL labels in ANY
68		// of the labels is RTL.
69		{lengthA, "\ufe05\u3002\u3002\U0002603e\u1ce0", "..xn--t6f5138v", "A4"},
70		{lengthA, "FAX\u2a77\U0001d186\u3002\U0001e942\U000e0181\u180c", "", "B6"},
71
72		{resolve, "a..b", "a..b", ""},
73		// Note that leading dots are not stripped. This is to be consistent
74		// with the Punycode profile as well as the conformance test.
75		{resolve, ".b", ".b", ""},
76		{resolve, "\u3002b", ".b", ""},
77		{resolve, "..b", "..b", ""},
78		{resolve, "b..", "b..", ""},
79		{resolve, "\xed", "", "P1"},
80
81		// Raw punycode
82		{punyA, "", "", ""},
83		{punyA, "*.foo.com", "*.foo.com", ""},
84		{punyA, "Foo.com", "Foo.com", ""},
85
86		// STD3 rules
87		{display, "*.foo.com", "*.foo.com", "P1"},
88		{std3, "*.foo.com", "*.foo.com", ""},
89
90		// Hyphens
91		{display, "r3---sn-apo3qvuoxuxbt-j5pe.googlevideo.com", "r3---sn-apo3qvuoxuxbt-j5pe.googlevideo.com", "V2"},
92		{hyphens, "r3---sn-apo3qvuoxuxbt-j5pe.googlevideo.com", "r3---sn-apo3qvuoxuxbt-j5pe.googlevideo.com", ""},
93		{display, "-label-.com", "-label-.com", "V3"},
94		{hyphens, "-label-.com", "-label-.com", ""},
95
96		// Don't map U+2490 (DIGIT NINE FULL STOP). This is the behavior of
97		// Chrome, Safari, and IE. Firefox will first map ⒐ to 9. and return
98		// lab9.be.
99		{resolve, "lab⒐be", "xn--labbe-zh9b", "P1"}, // encode("lab⒐be")
100		{display, "lab⒐be", "lab⒐be", "P1"},
101
102		{resolve, "plan⒐faß.de", "xn--planfass-c31e.de", "P1"}, // encode("plan⒐fass") + ".de"
103		{display, "Plan⒐faß.de", "plan⒐faß.de", "P1"},
104
105		// Chrome 54.0 recognizes the error and treats this input verbatim as a
106		// search string.
107		// Safari 10.0 (non-conform spec) decomposes "⒈" and computes the
108		// punycode on the result using transitional mapping.
109		// Firefox 49.0.1 goes haywire on this string and prints a bunch of what
110		// seems to be nested punycode encodings.
111		{resolve, "日本⒈co.ßßß.de", "xn--co-wuw5954azlb.ssssss.de", "P1"},
112		{display, "日本⒈co.ßßß.de", "日本⒈co.ßßß.de", "P1"},
113
114		{resolve, "a\u200Cb", "ab", ""},
115		{display, "a\u200Cb", "a\u200Cb", "C"},
116
117		{resolve, encode("a\u200Cb"), encode("a\u200Cb"), "C"},
118		{display, "a\u200Cb", "a\u200Cb", "C"},
119
120		{resolve, "grﻋﺮﺑﻲ.de", "xn--gr-gtd9a1b0g.de", "B"},
121		{
122			// Notice how the string gets transformed, even with an error.
123			// Chrome will use the original string if it finds an error, so not
124			// the transformed one.
125			display,
126			"gr\ufecb\ufeae\ufe91\ufef2.de",
127			"gr\u0639\u0631\u0628\u064a.de",
128			"B",
129		},
130
131		{resolve, "\u0671.\u03c3\u07dc", "xn--qib.xn--4xa21s", "B"}, // ٱ.σߜ
132		{display, "\u0671.\u03c3\u07dc", "\u0671.\u03c3\u07dc", "B"},
133
134		// normalize input
135		{resolve, "a\u0323\u0322", "xn--jta191l", ""}, // ạ̢
136		{display, "a\u0323\u0322", "\u1ea1\u0322", ""},
137
138		// Non-normalized strings are not normalized when they originate from
139		// punycode. Despite the error, Chrome, Safari and Firefox will attempt
140		// to look up the input punycode.
141		{resolve, encode("a\u0323\u0322") + ".com", "xn--a-tdbc.com", "V1"},
142		{display, encode("a\u0323\u0322") + ".com", "a\u0323\u0322.com", "V1"},
143	}
144
145	for _, tc := range testCases {
146		doTest(t, tc.f, tc.name, tc.input, tc.want, tc.wantErr)
147	}
148}
149