github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/golang/text/secure/precis/profile.go (about)

     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.
     4  
     5  package precis
     6  
     7  import (
     8  	"errors"
     9  	"unicode/utf8"
    10  
    11  	"github.com/insionng/yougam/libraries/x/text/runes"
    12  	"github.com/insionng/yougam/libraries/x/text/transform"
    13  	"github.com/insionng/yougam/libraries/x/text/width"
    14  )
    15  
    16  var (
    17  	disallowedRune = errors.New("disallowed rune encountered")
    18  )
    19  
    20  var dpTrie = newDerivedPropertiesTrie(0)
    21  
    22  // A Profile represents a set of rules for normalizing and validating strings in
    23  // the PRECIS framework.
    24  type Profile struct {
    25  	options
    26  	class *class
    27  	transform.NopResetter
    28  }
    29  
    30  // NewIdentifier creates a new PRECIS profile based on the Identifier string
    31  // class. Profiles created from this class are suitable for use where safety is
    32  // prioritized over expressiveness like network identifiers, user accounts, chat
    33  // rooms, and file names.
    34  func NewIdentifier(opts ...Option) Profile {
    35  	return Profile{
    36  		options: getOpts(opts...),
    37  		class:   identifier,
    38  	}
    39  }
    40  
    41  // NewFreeform creates a new PRECIS profile based on the Freeform string class.
    42  // Profiles created from this class are suitable for use where expressiveness is
    43  // prioritized over safety like passwords, and display-elements such as
    44  // nicknames in a chat room.
    45  func NewFreeform(opts ...Option) Profile {
    46  	return Profile{
    47  		options: getOpts(opts...),
    48  		class:   freeform,
    49  	}
    50  }
    51  
    52  // NewTransformer creates a new transform.Transformer that performs the PRECIS
    53  // preparation and enforcement steps on the given UTF-8 encoded bytes.
    54  func (p Profile) NewTransformer() *Transformer {
    55  	var ts []transform.Transformer
    56  
    57  	if p.options.allowwidechars {
    58  		ts = append(ts, width.Fold)
    59  	}
    60  
    61  	ts = append(ts, checker{p: p})
    62  
    63  	if p.options.width != nil {
    64  		ts = append(ts, width.Fold)
    65  	}
    66  
    67  	for _, f := range p.options.additional {
    68  		ts = append(ts, f())
    69  	}
    70  
    71  	if p.options.cases != nil {
    72  		ts = append(ts, p.options.cases)
    73  	}
    74  
    75  	ts = append(ts, p.options.norm)
    76  
    77  	// TODO: Apply directionality rule (blocking on the Bidi package)
    78  	// TODO: Add the disallow empty rule with a dummy transformer?
    79  
    80  	return &Transformer{transform.Chain(ts...)}
    81  }
    82  
    83  // Bytes returns a new byte slice with the result of applying the profile to b.
    84  func (p Profile) Bytes(b []byte) ([]byte, error) {
    85  	b, _, err := transform.Bytes(p.NewTransformer(), b)
    86  	if err == nil && p.options.disallowEmpty && len(b) == 0 {
    87  		return b, errors.New("enforce resulted in empty string")
    88  	}
    89  	return b, err
    90  }
    91  
    92  // String returns a string with the result of applying the profile to s.
    93  func (p Profile) String(s string) (string, error) {
    94  	s, _, err := transform.String(p.NewTransformer(), s)
    95  	if err == nil && p.options.disallowEmpty && len(s) == 0 {
    96  		return s, errors.New("enforce resulted in empty string")
    97  	}
    98  	return s, err
    99  }
   100  
   101  // Compare enforces both strings, and then compares them for bit-string identity
   102  // (byte-for-byte equality). If either string cannot be enforced, the comparison
   103  // is false.
   104  func (p Profile) Compare(a, b string) bool {
   105  	a, err := p.String(a)
   106  	if err != nil {
   107  		return false
   108  	}
   109  	b, err = p.String(b)
   110  	if err != nil {
   111  		return false
   112  	}
   113  
   114  	// TODO: This is out of order. Need to extract the transformation logic and
   115  	// put this in where the normal case folding would go (but only for
   116  	// comparison).
   117  	if p.options.ignorecase {
   118  		a = width.Fold.String(a)
   119  		b = width.Fold.String(a)
   120  	}
   121  
   122  	return a == b
   123  }
   124  
   125  // Allowed returns a runes.Set containing every rune that is a member of the
   126  // underlying profile's string class and not disallowed by any profile specific
   127  // rules.
   128  func (p Profile) Allowed() runes.Set {
   129  	return runes.Predicate(func(r rune) bool {
   130  		if p.options.disallow != nil {
   131  			return p.class.Contains(r) && !p.options.disallow.Contains(r)
   132  		} else {
   133  			return p.class.Contains(r)
   134  		}
   135  	})
   136  }
   137  
   138  type checker struct {
   139  	p Profile
   140  	transform.NopResetter
   141  }
   142  
   143  func (c checker) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
   144  	for nSrc < len(src) {
   145  		r, size := utf8.DecodeRune(src[nSrc:])
   146  		if size == 0 { // Incomplete UTF-8 encoding
   147  			if !atEOF {
   148  				return nDst, nSrc, transform.ErrShortSrc
   149  			}
   150  			size = 1
   151  		}
   152  		if c.p.Allowed().Contains(r) {
   153  			if size != copy(dst[nDst:], src[nSrc:nSrc+size]) {
   154  				return nDst, nSrc, transform.ErrShortDst
   155  			}
   156  			nDst += size
   157  		} else {
   158  			return nDst, nSrc, disallowedRune
   159  		}
   160  		nSrc += size
   161  	}
   162  	return nDst, nSrc, nil
   163  }