internal/export/idna: don't remove leading dots

Dots are still removed for profiles for which this makes sense.
Update golang/go#19821

Change-Id: I3de20bc5ddd943557831d1de998554105d7c07e7
Reviewed-on: https://go-review.googlesource.com/44380
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/internal/export/idna/idna.go b/internal/export/idna/idna.go
index 3184fbb..4711196 100644
--- a/internal/export/idna/idna.go
+++ b/internal/export/idna/idna.go
@@ -67,6 +67,15 @@
 	return func(o *options) { o.verifyDNSLength = verify }
 }
 
+// RemoveLeadingDots removes leading label separators. Leading runes that map to
+// dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
+//
+// This is the behavior suggested by the UTS #46 and is adopted by some
+// browsers.
+func RemoveLeadingDots(remove bool) Option {
+	return func(o *options) { o.removeLeadingDots = remove }
+}
+
 // ValidateLabels sets whether to check the mandatory label validation criteria
 // as defined in Section 5.4 of RFC 5891. This includes testing for correct use
 // of hyphens ('-'), normalization, validity of runes, and the context rules.
@@ -133,14 +142,16 @@
 		o.mapping = validateAndMap
 		StrictDomainName(true)(o)
 		ValidateLabels(true)(o)
+		RemoveLeadingDots(true)(o)
 	}
 }
 
 type options struct {
-	transitional    bool
-	useSTD3Rules    bool
-	validateLabels  bool
-	verifyDNSLength bool
+	transitional      bool
+	useSTD3Rules      bool
+	validateLabels    bool
+	verifyDNSLength   bool
+	removeLeadingDots bool
 
 	trie *idnaTrie
 
@@ -240,21 +251,23 @@
 
 	punycode = &Profile{}
 	lookup   = &Profile{options{
-		transitional:   true,
-		useSTD3Rules:   true,
-		validateLabels: true,
-		trie:           trie,
-		fromPuny:       validateFromPunycode,
-		mapping:        validateAndMap,
-		bidirule:       bidirule.ValidString,
+		transitional:      true,
+		useSTD3Rules:      true,
+		validateLabels:    true,
+		removeLeadingDots: true,
+		trie:              trie,
+		fromPuny:          validateFromPunycode,
+		mapping:           validateAndMap,
+		bidirule:          bidirule.ValidString,
 	}}
 	display = &Profile{options{
-		useSTD3Rules:   true,
-		validateLabels: true,
-		trie:           trie,
-		fromPuny:       validateFromPunycode,
-		mapping:        validateAndMap,
-		bidirule:       bidirule.ValidString,
+		useSTD3Rules:      true,
+		validateLabels:    true,
+		removeLeadingDots: true,
+		trie:              trie,
+		fromPuny:          validateFromPunycode,
+		mapping:           validateAndMap,
+		bidirule:          bidirule.ValidString,
 	}}
 	registration = &Profile{options{
 		useSTD3Rules:    true,
@@ -293,7 +306,9 @@
 		s, err = p.mapping(p, s)
 	}
 	// Remove leading empty labels.
-	for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
+	if p.removeLeadingDots {
+		for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
+		}
 	}
 	// It seems like we should only create this error on ToASCII, but the
 	// UTS 46 conformance tests suggests we should always check this.
diff --git a/internal/export/idna/idna_test.go b/internal/export/idna/idna_test.go
index 01fd50b..82ead03 100644
--- a/internal/export/idna/idna_test.go
+++ b/internal/export/idna/idna_test.go
@@ -117,7 +117,7 @@
 		f    func(string) (string, error)
 	}
 	punyA := kind{"PunycodeA", punycode.ToASCII}
-	resolve := kind{"ToASCII", Lookup.ToASCII}
+	resolve := kind{"ResolveA", Lookup.ToASCII}
 	display := kind{"ToUnicode", Display.ToUnicode}
 	p := New(VerifyDNSLength(true), MapForLookup(), BidiRule())
 	lengthU := kind{"CheckLengthU", p.ToUnicode}
@@ -145,16 +145,25 @@
 		{display, "foo.xn--.bar", "foo..bar", ""},
 
 		{lengthA, "a..b", "a..b", "A4"},
-		// Stripping leading empty labels here but not for "empty" punycode
-		// above seems inconsistent, but seems to be applied by both the
-		// conformance test and Chrome. Different interpretations would be
-		// possible, though.
+		{punyA, ".b", ".b", ""},
+		// For backwards compatibility, the Punycode profile does not map runes.
+		{punyA, "\u3002b", "xn--b-83t", ""},
+		{punyA, "..b", "..b", ""},
+		// Only strip leading empty labels for certain profiles. Stripping
+		// leading empty labels here but not for "empty" punycode above seems
+		// inconsistent, but seems to be applied by both the conformance test
+		// and Chrome. So we turn it off by default, support it as an option,
+		// and enable it in profiles where it seems commonplace.
+		{lengthA, ".b", "b", ""},
+		{lengthA, "\u3002b", "b", ""},
 		{lengthA, "..b", "b", ""},
-		{lengthA, "b..", "b..", ""}, // TODO: remove trailing dots?
+		{lengthA, "b..", "b..", ""},
 
 		{resolve, "a..b", "a..b", ""},
+		{resolve, ".b", "b", ""},
+		{resolve, "\u3002b", "b", ""},
 		{resolve, "..b", "b", ""},
-		{resolve, "b..", "b..", ""}, // TODO: remove trailing dots?
+		{resolve, "b..", "b..", ""},
 
 		// Raw punycode
 		{punyA, "", "", ""},