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 }