github.com/sandwich-go/boost@v1.3.29/misc/xtag/valid.go (about)

     1  package xtag
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"strings"
     7  )
     8  
     9  var (
    10  	checkTagSpaces    = map[string]bool{"json": true, "xml": true, "asn1": true}
    11  	errTagSyntax      = errors.New("bad syntax for struct tag pair")
    12  	errTagKeySyntax   = errors.New("bad syntax for struct tag key")
    13  	errTagValueSyntax = errors.New("bad syntax for struct tag value")
    14  	errTagValueSpace  = errors.New("suspicious space in struct tag value")
    15  	errTagSpace       = errors.New("key:\"value\" pairs not separated by spaces")
    16  )
    17  
    18  // ValidateStructTag parses the struct tag and returns an error if it is not
    19  // in the canonical format, which is a space-separated list of key:"value"
    20  // settings. The value may contain spaces.
    21  func ValidateStructTag(tag string) error {
    22  	// This code is based on the StructTag.Get code in package reflect.
    23  	n := 0
    24  	for ; tag != ""; n++ {
    25  		if n > 0 && tag != "" && tag[0] != ' ' {
    26  			// More restrictive than reflect, but catches likely mistakes
    27  			// like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y".
    28  			return errTagSpace
    29  		}
    30  		// Skip leading space.
    31  		i := 0
    32  		for i < len(tag) && tag[i] == ' ' {
    33  			i++
    34  		}
    35  		tag = tag[i:]
    36  		if tag == "" {
    37  			break
    38  		}
    39  
    40  		// Scan to colon. A space, a quote or a control character is a syntax error.
    41  		// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
    42  		// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
    43  		// as it is simpler to inspect the tag's bytes than the tag's runes.
    44  		i = 0
    45  		for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
    46  			i++
    47  		}
    48  		if i == 0 {
    49  			return errTagKeySyntax
    50  		}
    51  		if i+1 >= len(tag) || tag[i] != ':' {
    52  			return errTagSyntax
    53  		}
    54  		if tag[i+1] != '"' {
    55  			return errTagValueSyntax
    56  		}
    57  		key := tag[:i]
    58  		tag = tag[i+1:]
    59  
    60  		// Scan quoted string to find value.
    61  		i = 1
    62  		for i < len(tag) && tag[i] != '"' {
    63  			if tag[i] == '\\' {
    64  				i++
    65  			}
    66  			i++
    67  		}
    68  		if i >= len(tag) {
    69  			return errTagValueSyntax
    70  		}
    71  		qvalue := tag[:i+1]
    72  		tag = tag[i+1:]
    73  
    74  		value, err := strconv.Unquote(qvalue)
    75  		if err != nil {
    76  			return errTagValueSyntax
    77  		}
    78  
    79  		if !checkTagSpaces[key] {
    80  			continue
    81  		}
    82  
    83  		switch key {
    84  		case "xml":
    85  			// If the first or last character in the XML tag is a space, it is
    86  			// suspicious.
    87  			if strings.Trim(value, " ") != value {
    88  				return errTagValueSpace
    89  			}
    90  
    91  			// If there are multiple spaces, they are suspicious.
    92  			if strings.Count(value, " ") > 1 {
    93  				return errTagValueSpace
    94  			}
    95  
    96  			// If there is no comma, skip the rest of the checks.
    97  			comma := strings.IndexRune(value, ',')
    98  			if comma < 0 {
    99  				continue
   100  			}
   101  			// If the character before a comma is a space, this is suspicious.
   102  			if comma > 0 && value[comma-1] == ' ' {
   103  				return errTagValueSpace
   104  			}
   105  			value = value[comma+1:]
   106  		case "json":
   107  			// JSON allows using spaces in the name, so skip it.
   108  			comma := strings.IndexRune(value, ',')
   109  			if comma < 0 {
   110  				continue
   111  			}
   112  			value = value[comma+1:]
   113  		}
   114  
   115  		if strings.IndexByte(value, ' ') >= 0 {
   116  			return errTagValueSpace
   117  		}
   118  	}
   119  	return nil
   120  }