secure/precis: add ascii fast path to enforce

benchmark                                                    old ns/op     new ns/op     delta
BenchmarkAppend/UsernameCaseMapped/ASCII-4                   526           85.6          -83.73%
BenchmarkBytes/UsernameCaseMapped/ASCII-4                    523           114           -78.20%
BenchmarkString/UsernameCaseMapped/ASCII-4                   593           146           -75.38%
BenchmarkAppend/UsernameCasePreserved/ASCII-4                180           82.5          -54.17%
BenchmarkBytes/UsernameCasePreserved/ASCII-4                 214           110           -48.60%
BenchmarkString/UsernameCasePreserved/ASCII-4                247           146           -40.89%
BenchmarkAppend/FreeForm/ASCII-4                             112           81.4          -27.32%
BenchmarkBytes/FreeForm/ASCII-4                              143           112           -21.68%
BenchmarkString/FreeForm/ASCII-4                             170           139           -18.24%
BenchmarkAppend/OpaqueString/ASCII-4                         242           208           -14.05%
BenchmarkBytes/OpaqueString/ASCII-4                          276           242           -12.32%
BenchmarkString/OpaqueString/ASCII-4                         305           270           -11.48%
BenchmarkAppend/Nickname/ASCII-4                             481           446           -7.28%
BenchmarkString/Nickname/ASCII-4                             548           512           -6.57%
BenchmarkBytes/Nickname/ASCII-4                              478           450           -5.86%
BenchmarkBytes/OpaqueString/Arabic-4                         334           324           -2.99%
BenchmarkTransform/UsernameCaseMapped/Arabic-4               516           506           -1.94%
BenchmarkString/FreeForm/Hangul-4                            671           658           -1.94%
BenchmarkBytes/OpaqueString/Hangul-4                         760           775           +1.97%
BenchmarkString/Nickname/Hangul-4                            1016          1036          +1.97%
…

Updates golang/go#17423

Change-Id: Ia70335212f1089f280653c2b7bea9f769373ae1d
Reviewed-on: https://go-review.googlesource.com/33435
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/secure/precis/options.go b/secure/precis/options.go
index 4934f8b..488f0b1 100644
--- a/secure/precis/options.go
+++ b/secure/precis/options.go
@@ -20,6 +20,7 @@
 	foldWidth bool
 
 	// Enforcement options
+	asciiLower    bool
 	cases         transform.SpanningTransformer
 	disallow      runes.Set
 	norm          transform.SpanningTransformer
@@ -123,6 +124,7 @@
 // provided to determine the type of case folding used.
 func FoldCase(opts ...cases.Option) Option {
 	return func(o *options) {
+		o.asciiLower = true
 		o.cases = cases.Fold(opts...)
 	}
 }
@@ -131,6 +133,7 @@
 // provided to determine the type of case folding used.
 func LowerCase(opts ...cases.Option) Option {
 	return func(o *options) {
+		o.asciiLower = true
 		if len(opts) == 0 {
 			o.cases = cases.Lower(language.Und, cases.HandleFinalSigma(false))
 			return
diff --git a/secure/precis/profile.go b/secure/precis/profile.go
index 081f555..1d7898d 100644
--- a/secure/precis/profile.go
+++ b/secure/precis/profile.go
@@ -118,9 +118,49 @@
 // TODO: make this a method on profile.
 
 func (b *buffers) enforce(p *Profile, src []byte, comparing bool) (str []byte, err error) {
-	// TODO: ASCII fast path, if options allow.
 	b.src = src
 
+	ascii := true
+	for _, c := range src {
+		if c >= utf8.RuneSelf {
+			ascii = false
+			break
+		}
+	}
+	// ASCII fast path.
+	if ascii {
+		for _, f := range p.options.additional {
+			if err = b.apply(f()); err != nil {
+				return nil, err
+			}
+		}
+		switch {
+		case p.options.asciiLower || (comparing && p.options.ignorecase):
+			for i, c := range b.src {
+				if 'A' <= c && c <= 'Z' {
+					b.src[i] = c ^ 1<<5
+				}
+			}
+		case p.options.cases != nil:
+			b.apply(p.options.cases)
+		}
+		c := checker{p: p}
+		if _, err := c.span(b.src, true); err != nil {
+			return nil, err
+		}
+		if p.disallow != nil {
+			for _, c := range b.src {
+				if p.disallow.Contains(rune(c)) {
+					return nil, errDisallowedRune
+				}
+			}
+		}
+		if p.options.disallowEmpty && len(b.src) == 0 {
+			return nil, errEmptyString
+		}
+		return b.src, nil
+	}
+
 	// These transforms are applied in the order defined in
 	// https://tools.ietf.org/html/rfc7564#section-7