secure/precis: use toLower instead of case folding

Change default case folding to the Unicode toLower operation as
specified in draft-ietf-precis-7700bis-03 and
draft-ietf-precis-7613bis-03.

Also handle Greek final sigma correctly

Change-Id: I8e9fec1fc51d38a1950f95be72e07fb80d9949e4
Reviewed-on: https://go-review.googlesource.com/30254
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/secure/precis/enforce_test.go b/secure/precis/enforce_test.go
index 15d7876..a2f2328 100644
--- a/secure/precis/enforce_test.go
+++ b/secure/precis/enforce_test.go
@@ -162,6 +162,7 @@
 		{"foo", "foo", nil},
 		{"Foo Bar", "Foo Bar", nil},
 		{"foo bar", "foo bar", nil},
+		{"\u03A3", "\u03A3", nil},
 		{"\u03C3", "\u03C3", nil},
 		// Greek final sigma is left as is (do not fold!)
 		{"\u03C2", "\u03C2", nil},
@@ -194,11 +195,12 @@
 		// {UsernameCaseMapped, "", "", errDisallowedRune},
 		{"juliet@example.com", "juliet@example.com", nil},
 		{"fussball", "fussball", nil},
-		{"fu\u00DFball", "fussball", nil},
+		{"fu\u00DFball", "fu\u00DFball", nil},
 		{"\u03C0", "\u03C0", nil},
 		{"\u03A3", "\u03C3", nil},
 		{"\u03C3", "\u03C3", nil},
-		{"\u03C2", "\u03C3", nil},
+		// Greek final sigma is left as is (do not fold!)
+		{"\u03C2", "\u03C2", nil},
 		{"\u0049", "\u0069", nil},
 		{"\u0049", "\u0069", nil},
 		{"\u03D2", "", errDisallowedRune},
@@ -213,7 +215,7 @@
 		{"\n", "", bidirule.ErrInvalid},
 		{"\u26D6", "", bidirule.ErrInvalid},
 		{"\u26FF", "", bidirule.ErrInvalid},
-		{"\uFB00", "ff", nil}, // Side effect of case folding.
+		{"\uFB00", "", errDisallowedRune},
 		{"\u1680", "", bidirule.ErrInvalid},
 		{" ", "", bidirule.ErrInvalid},
 		{"  ", "", bidirule.ErrInvalid},
@@ -229,8 +231,6 @@
 		{"\u0052\u030C", "ř", nil},
 
 		{"\u1E61", "\u1E61", nil}, // LATIN SMALL LETTER S WITH DOT ABOVE
-		// U+1e9B: case folded.
-		{"ẛ", "\u1E61", nil}, // LATIN SMALL LETTER LONG S WITH DOT ABOVE
 
 		// Confusable characters ARE allowed and should NOT be mapped.
 		{"\u0410", "\u0430", nil}, // CYRILLIC CAPITAL LETTER A
diff --git a/secure/precis/options.go b/secure/precis/options.go
index ec63783..4b71c6a 100644
--- a/secure/precis/options.go
+++ b/secure/precis/options.go
@@ -6,6 +6,7 @@
 
 import (
 	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
 	"golang.org/x/text/runes"
 	"golang.org/x/text/transform"
 	"golang.org/x/text/unicode/norm"
@@ -97,6 +98,20 @@
 	}
 }
 
+// The LowerCase option defines a Profile's case mapping rule. Options can be
+// provided to determine the type of case folding used.
+func LowerCase(opts ...cases.Option) Option {
+	return func(o *options) {
+		if len(opts) == 0 {
+			o.cases = cases.Lower(language.Und, cases.HandleFinalSigma(false))
+			return
+		}
+
+		opts = append([]cases.Option{cases.HandleFinalSigma(false)}, opts...)
+		o.cases = cases.Lower(language.Und, opts...)
+	}
+}
+
 // The Disallow option further restricts a Profile's allowed characters beyond
 // what is disallowed by the underlying string class.
 func Disallow(set runes.Set) Option {
diff --git a/secure/precis/profile.go b/secure/precis/profile.go
index fd5c422..5fb74b8 100644
--- a/secure/precis/profile.go
+++ b/secure/precis/profile.go
@@ -8,6 +8,8 @@
 	"errors"
 	"unicode/utf8"
 
+	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
 	"golang.org/x/text/runes"
 	"golang.org/x/text/secure/bidirule"
 	"golang.org/x/text/transform"
@@ -222,6 +224,10 @@
 	if p.options.ignorecase {
 		a = width.Fold.String(a)
 		b = width.Fold.String(a)
+
+		caser := cases.Lower(language.Und, cases.HandleFinalSigma(false))
+		a = caser.String(a)
+		b = caser.String(b)
 	}
 
 	return a == b
diff --git a/secure/precis/profiles.go b/secure/precis/profiles.go
index ad50ae8..89089c2 100644
--- a/secure/precis/profiles.go
+++ b/secure/precis/profiles.go
@@ -32,7 +32,7 @@
 	)
 	usernameCaseMap = NewIdentifier(
 		FoldWidth,
-		FoldCase(),
+		LowerCase(),
 		Norm(norm.NFC),
 		BidiRule,
 	)