1 // Copyright 2015 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.
12 "golang.org/x/text/cases"
13 "golang.org/x/text/language"
14 "golang.org/x/text/runes"
15 "golang.org/x/text/secure/bidirule"
16 "golang.org/x/text/transform"
17 "golang.org/x/text/width"
21 errDisallowedRune = errors.New("precis: disallowed rune encountered")
24 var dpTrie = newDerivedPropertiesTrie(0)
26 // A Profile represents a set of rules for normalizing and validating strings in
27 // the PRECIS framework.
33 // NewIdentifier creates a new PRECIS profile based on the Identifier string
34 // class. Profiles created from this class are suitable for use where safety is
35 // prioritized over expressiveness like network identifiers, user accounts, chat
36 // rooms, and file names.
37 func NewIdentifier(opts ...Option) *Profile {
39 options: getOpts(opts...),
44 // NewFreeform creates a new PRECIS profile based on the Freeform string class.
45 // Profiles created from this class are suitable for use where expressiveness is
46 // prioritized over safety like passwords, and display-elements such as
47 // nicknames in a chat room.
48 func NewFreeform(opts ...Option) *Profile {
50 options: getOpts(opts...),
55 // NewTransformer creates a new transform.Transformer that performs the PRECIS
56 // preparation and enforcement steps on the given UTF-8 encoded bytes.
57 func (p *Profile) NewTransformer() *Transformer {
58 var ts []transform.Transformer
60 // These transforms are applied in the order defined in
61 // https://tools.ietf.org/html/rfc7564#section-7
63 if p.options.foldWidth {
64 ts = append(ts, width.Fold)
67 for _, f := range p.options.additional {
71 if p.options.cases != nil {
72 ts = append(ts, p.options.cases)
75 ts = append(ts, p.options.norm)
77 if p.options.bidiRule {
78 ts = append(ts, bidirule.New())
81 ts = append(ts, &checker{p: p, allowed: p.Allowed()})
83 // TODO: Add the disallow empty rule with a dummy transformer?
85 return &Transformer{transform.Chain(ts...)}
88 var errEmptyString = errors.New("precis: transformation resulted in empty string")
96 func (b *buffers) apply(t transform.SpanningTransformer) (err error) {
97 n, err := t.Span(b.src, true)
98 if err != transform.ErrEndOfSpan {
103 b.buf[x] = make([]byte, 0, 8+len(b.src)+len(b.src)>>2)
105 span := append(b.buf[x][:0], b.src[:n]...)
106 b.src, _, err = transform.Append(t, span, b.src[n:])
112 // Pre-allocate transformers when possible. In some cases this avoids allocation.
114 foldWidthT transform.SpanningTransformer = width.Fold
115 lowerCaseT transform.SpanningTransformer = cases.Lower(language.Und, cases.HandleFinalSigma(false))
118 // TODO: make this a method on profile.
120 func (b *buffers) enforce(p *Profile, src []byte, comparing bool) (str []byte, err error) {
124 for _, c := range src {
125 if c >= utf8.RuneSelf {
132 for _, f := range p.options.additional {
133 if err = b.apply(f()); err != nil {
138 case p.options.asciiLower || (comparing && p.options.ignorecase):
139 for i, c := range b.src {
140 if 'A' <= c && c <= 'Z' {
144 case p.options.cases != nil:
145 b.apply(p.options.cases)
148 if _, err := c.span(b.src, true); err != nil {
151 if p.disallow != nil {
152 for _, c := range b.src {
153 if p.disallow.Contains(rune(c)) {
154 return nil, errDisallowedRune
158 if p.options.disallowEmpty && len(b.src) == 0 {
159 return nil, errEmptyString
164 // These transforms are applied in the order defined in
165 // https://tools.ietf.org/html/rfc7564#section-7
167 // TODO: allow different width transforms options.
168 if p.options.foldWidth || (p.options.ignorecase && comparing) {
171 for _, f := range p.options.additional {
172 if err = b.apply(f()); err != nil {
176 if p.options.cases != nil {
177 b.apply(p.options.cases)
179 if comparing && p.options.ignorecase {
183 if p.options.bidiRule && !bidirule.Valid(b.src) {
184 return nil, bidirule.ErrInvalid
187 if _, err := c.span(b.src, true); err != nil {
190 if p.disallow != nil {
191 for i := 0; i < len(b.src); {
192 r, size := utf8.DecodeRune(b.src[i:])
193 if p.disallow.Contains(r) {
194 return nil, errDisallowedRune
199 if p.options.disallowEmpty && len(b.src) == 0 {
200 return nil, errEmptyString
205 // Append appends the result of applying p to src writing the result to dst.
206 // It returns an error if the input string is invalid.
207 func (p *Profile) Append(dst, src []byte) ([]byte, error) {
209 b, err := buf.enforce(p, src, false)
213 return append(dst, b...), nil
216 func processBytes(p *Profile, b []byte, key bool) ([]byte, error) {
218 b, err := buf.enforce(p, b, key)
223 c := make([]byte, len(b))
230 // Bytes returns a new byte slice with the result of applying the profile to b.
231 func (p *Profile) Bytes(b []byte) ([]byte, error) {
232 return processBytes(p, b, false)
235 // AppendCompareKey appends the result of applying p to src (including any
236 // optional rules to make strings comparable or useful in a map key such as
237 // applying lowercasing) writing the result to dst. It returns an error if the
238 // input string is invalid.
239 func (p *Profile) AppendCompareKey(dst, src []byte) ([]byte, error) {
241 b, err := buf.enforce(p, src, true)
245 return append(dst, b...), nil
248 func processString(p *Profile, s string, key bool) (string, error) {
250 b, err := buf.enforce(p, []byte(s), key)
254 return string(b), nil
257 // String returns a string with the result of applying the profile to s.
258 func (p *Profile) String(s string) (string, error) {
259 return processString(p, s, false)
262 // CompareKey returns a string that can be used for comparison, hashing, or
264 func (p *Profile) CompareKey(s string) (string, error) {
265 return processString(p, s, true)
268 // Compare enforces both strings, and then compares them for bit-string identity
269 // (byte-for-byte equality). If either string cannot be enforced, the comparison
271 func (p *Profile) Compare(a, b string) bool {
274 akey, err := buf.enforce(p, []byte(a), true)
280 bkey, err := buf.enforce(p, []byte(b), true)
285 return bytes.Compare(akey, bkey) == 0
288 // Allowed returns a runes.Set containing every rune that is a member of the
289 // underlying profile's string class and not disallowed by any profile specific
291 func (p *Profile) Allowed() runes.Set {
292 if p.options.disallow != nil {
293 return runes.Predicate(func(r rune) bool {
294 return p.class.Contains(r) && !p.options.disallow.Contains(r)
300 type checker struct {
309 func (c *checker) Reset() {
315 func (c *checker) span(src []byte, atEOF bool) (n int, err error) {
317 e, sz := dpTrie.lookup(src[n:])
318 d := categoryTransitions[category(e&catMask)]
321 return n, transform.ErrShortSrc
323 return n, errDisallowedRune
326 if property(e) < c.p.class.validFrom {
328 return n, errDisallowedRune
330 doLookAhead, err = d.rule(c.beforeBits)
335 c.beforeBits &= d.keep
336 c.beforeBits |= d.set
338 // We are currently in an unterminated lookahead.
339 if c.beforeBits&c.termBits != 0 {
342 } else if c.beforeBits&c.acceptBits == 0 {
343 // Invalid continuation of the unterminated lookahead sequence.
349 // A previous lookahead run has not been terminated yet.
353 c.acceptBits = d.accept
357 if m := c.beforeBits >> finalShift; c.beforeBits&m != m || c.termBits != 0 {
363 // TODO: we may get rid of this transform if transform.Chain understands
364 // something like a Spanner interface.
365 func (c checker) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
367 if len(dst) < len(src) {
372 nSrc, err = c.span(src, atEOF)
373 nDst = copy(dst, src[:nSrc])
374 if short && (err == transform.ErrShortSrc || err == nil) {
375 err = transform.ErrShortDst
377 return nDst, nSrc, err