Copyright 2015 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

package precis

import (
	
	
	

	
	
	
	
	
	
)

var (
	errDisallowedRune = errors.New("precis: disallowed rune encountered")
)

var dpTrie = newDerivedPropertiesTrie(0)
A Profile represents a set of rules for normalizing and validating strings in the PRECIS framework.
type Profile struct {
	options
	class *class
}
NewIdentifier creates a new PRECIS profile based on the Identifier string class. Profiles created from this class are suitable for use where safety is prioritized over expressiveness like network identifiers, user accounts, chat rooms, and file names.
func ( ...Option) *Profile {
	return &Profile{
		options: getOpts(...),
		class:   identifier,
	}
}
NewFreeform creates a new PRECIS profile based on the Freeform string class. Profiles created from this class are suitable for use where expressiveness is prioritized over safety like passwords, and display-elements such as nicknames in a chat room.
func ( ...Option) *Profile {
	return &Profile{
		options: getOpts(...),
		class:   freeform,
	}
}
NewRestrictedProfile creates a new PRECIS profile based on an existing profile. If the parent profile already had the Disallow option set, the new rule overrides the parents rule.
func ( *Profile,  runes.Set) *Profile {
	 := *
	Disallow()(&.options)
	return &
}
NewTransformer creates a new transform.Transformer that performs the PRECIS preparation and enforcement steps on the given UTF-8 encoded bytes.
These transforms are applied in the order defined in https://tools.ietf.org/html/rfc7564#section-7
RFC 8266 ยง2.1: Implementation experience has shown that applying the rules for the Nickname profile is not an idempotent procedure for all code points. Therefore, an implementation SHOULD apply the rules repeatedly until the output string is stable; if the output string does not stabilize after reapplying the rules three (3) additional times after the first application, the implementation SHOULD terminate application of the rules and reject the input string as invalid. There is no known string that will change indefinitely, so repeat 4 times and rely on the Span method to keep things relatively performant.
	 := 1
	if .options.repeat {
		 = 4
	}
	for ;  > 0; -- {
		if .options.foldWidth {
			 = append(, width.Fold)
		}

		for ,  := range .options.additional {
			 = append(, ())
		}

		if .options.cases != nil {
			 = append(, .options.cases)
		}

		 = append(, .options.norm)

		if .options.bidiRule {
			 = append(, bidirule.New())
		}

		 = append(, &checker{p: , allowed: .Allowed()})
	}
TODO: Add the disallow empty rule with a dummy transformer?

	return &Transformer{transform.Chain(...)}
}

var errEmptyString = errors.New("precis: transformation resulted in empty string")

type buffers struct {
	src  []byte
	buf  [2][]byte
	next int
}

func ( *buffers) ( transform.SpanningTransformer) ( error) {
	,  := .Span(.src, true)
	if  != transform.ErrEndOfSpan {
		return 
	}
	 := .next & 1
	if .buf[] == nil {
		.buf[] = make([]byte, 0, 8+len(.src)+len(.src)>>2)
	}
	 := append(.buf[][:0], .src[:]...)
	.src, _,  = transform.Append(, , .src[:])
	.buf[] = .src
	.next++
	return 
}
Pre-allocate transformers when possible. In some cases this avoids allocation.
TODO: make this a method on profile.

func ( *buffers) ( *Profile,  []byte,  bool) ( []byte,  error) {
	.src = 

	 := true
	for ,  := range  {
		if  >= utf8.RuneSelf {
			 = false
			break
		}
ASCII fast path.
	if  {
		for ,  := range .options.additional {
			if  = .apply(());  != nil {
				return nil, 
			}
		}
		switch {
		case .options.asciiLower || ( && .options.ignorecase):
			for ,  := range .src {
				if 'A' <=  &&  <= 'Z' {
					.src[] =  ^ 1<<5
				}
			}
		case .options.cases != nil:
			.apply(.options.cases)
		}
		 := checker{p: }
		if ,  := .span(.src, true);  != nil {
			return nil, 
		}
		if .disallow != nil {
			for ,  := range .src {
				if .disallow.Contains(rune()) {
					return nil, errDisallowedRune
				}
			}
		}
		if .options.disallowEmpty && len(.src) == 0 {
			return nil, errEmptyString
		}
		return .src, nil
	}
These transforms are applied in the order defined in https://tools.ietf.org/html/rfc8264#section-7

	 := 1
	if .options.repeat {
		 = 4
	}
TODO: allow different width transforms options.
		if .options.foldWidth || (.options.ignorecase && ) {
			.apply(foldWidthT)
		}
		for ,  := range .options.additional {
			if  = .apply(());  != nil {
				return nil, 
			}
		}
		if .options.cases != nil {
			.apply(.options.cases)
		}
		if  && .options.ignorecase {
			.apply(lowerCaseT)
		}
		.apply(.norm)
		if .options.bidiRule && !bidirule.Valid(.src) {
			return nil, bidirule.ErrInvalid
		}
		 := checker{p: }
		if ,  := .span(.src, true);  != nil {
			return nil, 
		}
		if .disallow != nil {
			for  := 0;  < len(.src); {
				,  := utf8.DecodeRune(.src[:])
				if .disallow.Contains() {
					return nil, errDisallowedRune
				}
				 += 
			}
		}
		if .options.disallowEmpty && len(.src) == 0 {
			return nil, errEmptyString
		}
	}
	return .src, nil
}
Append appends the result of applying p to src writing the result to dst. It returns an error if the input string is invalid.
func ( *Profile) (,  []byte) ([]byte, error) {
	var  buffers
	,  := .enforce(, , false)
	if  != nil {
		return nil, 
	}
	return append(, ...), nil
}

func ( *Profile,  []byte,  bool) ([]byte, error) {
	var  buffers
	,  := .enforce(, , )
	if  != nil {
		return nil, 
	}
	if .next == 0 {
		 := make([]byte, len())
		copy(, )
		return , nil
	}
	return , nil
}
Bytes returns a new byte slice with the result of applying the profile to b.
func ( *Profile) ( []byte) ([]byte, error) {
	return processBytes(, , false)
}
AppendCompareKey appends the result of applying p to src (including any optional rules to make strings comparable or useful in a map key such as applying lowercasing) writing the result to dst. It returns an error if the input string is invalid.
func ( *Profile) (,  []byte) ([]byte, error) {
	var  buffers
	,  := .enforce(, , true)
	if  != nil {
		return nil, 
	}
	return append(, ...), nil
}

func ( *Profile,  string,  bool) (string, error) {
	var  buffers
	,  := .enforce(, []byte(), )
	if  != nil {
		return "", 
	}
	return string(), nil
}
String returns a string with the result of applying the profile to s.
func ( *Profile) ( string) (string, error) {
	return processString(, , false)
}
CompareKey returns a string that can be used for comparison, hashing, or collation.
func ( *Profile) ( string) (string, error) {
	return processString(, , true)
}
Compare enforces both strings, and then compares them for bit-string identity (byte-for-byte equality). If either string cannot be enforced, the comparison is false.
func ( *Profile) (,  string) bool {
	var  buffers

	,  := .enforce(, []byte(), true)
	if  != nil {
		return false
	}

	 = buffers{}
	,  := .enforce(, []byte(), true)
	if  != nil {
		return false
	}

	return bytes.Compare(, ) == 0
}
Allowed returns a runes.Set containing every rune that is a member of the underlying profile's string class and not disallowed by any profile specific rules.
func ( *Profile) () runes.Set {
	if .options.disallow != nil {
		return runes.Predicate(func( rune) bool {
			return .class.Contains() && !.options.disallow.Contains()
		})
	}
	return .class
}

type checker struct {
	p       *Profile
	allowed runes.Set

	beforeBits catBitmap
	termBits   catBitmap
	acceptBits catBitmap
}

func ( *checker) () {
	.beforeBits = 0
	.termBits = 0
	.acceptBits = 0
}

func ( *checker) ( []byte,  bool) ( int,  error) {
	for  < len() {
		,  := dpTrie.lookup([:])
		 := categoryTransitions[category(&catMask)]
		if  == 0 {
			if ! {
				return , transform.ErrShortSrc
			}
			return , errDisallowedRune
		}
		 := false
		if property() < .p.class.validFrom {
			if .rule == nil {
				return , errDisallowedRune
			}
			,  = .rule(.beforeBits)
			if  != nil {
				return , 
			}
		}
		.beforeBits &= .keep
		.beforeBits |= .set
We are currently in an unterminated lookahead.
			if .beforeBits&.termBits != 0 {
				.termBits = 0
				.acceptBits = 0
Invalid continuation of the unterminated lookahead sequence.
				return , errContext
			}
		}
		if  {
A previous lookahead run has not been terminated yet.
				return , errContext
			}
			.termBits = .term
			.acceptBits = .accept
		}
		 += 
	}
	if  := .beforeBits >> finalShift; .beforeBits& !=  || .termBits != 0 {
		 = errContext
	}
	return , 
}
TODO: we may get rid of this transform if transform.Chain understands something like a Spanner interface.
func ( checker) (,  []byte,  bool) (,  int,  error) {
	 := false
	if len() < len() {
		 = [:len()]
		 = false
		 = true
	}
	,  = .span(, )
	 = copy(, [:])
	if  && ( == transform.ErrShortSrc ||  == nil) {
		 = transform.ErrShortDst
	}
	return , ,