resolve.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. //
  2. // Copyright (c) 2011-2019 Canonical Ltd
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. package yaml
  16. import (
  17. "encoding/base64"
  18. "math"
  19. "regexp"
  20. "strconv"
  21. "strings"
  22. "time"
  23. )
  24. type resolveMapItem struct {
  25. value interface{}
  26. tag string
  27. }
  28. var resolveTable = make([]byte, 256)
  29. var resolveMap = make(map[string]resolveMapItem)
  30. func init() {
  31. t := resolveTable
  32. t[int('+')] = 'S' // Sign
  33. t[int('-')] = 'S'
  34. for _, c := range "0123456789" {
  35. t[int(c)] = 'D' // Digit
  36. }
  37. for _, c := range "yYnNtTfFoO~" {
  38. t[int(c)] = 'M' // In map
  39. }
  40. t[int('.')] = '.' // Float (potentially in map)
  41. var resolveMapList = []struct {
  42. v interface{}
  43. tag string
  44. l []string
  45. }{
  46. {true, boolTag, []string{"true", "True", "TRUE"}},
  47. {false, boolTag, []string{"false", "False", "FALSE"}},
  48. {nil, nullTag, []string{"", "~", "null", "Null", "NULL"}},
  49. {math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}},
  50. {math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}},
  51. {math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}},
  52. {math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}},
  53. {"<<", mergeTag, []string{"<<"}},
  54. }
  55. m := resolveMap
  56. for _, item := range resolveMapList {
  57. for _, s := range item.l {
  58. m[s] = resolveMapItem{item.v, item.tag}
  59. }
  60. }
  61. }
  62. const (
  63. nullTag = "!!null"
  64. boolTag = "!!bool"
  65. strTag = "!!str"
  66. intTag = "!!int"
  67. floatTag = "!!float"
  68. timestampTag = "!!timestamp"
  69. seqTag = "!!seq"
  70. mapTag = "!!map"
  71. binaryTag = "!!binary"
  72. mergeTag = "!!merge"
  73. )
  74. var longTags = make(map[string]string)
  75. var shortTags = make(map[string]string)
  76. func init() {
  77. for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} {
  78. ltag := longTag(stag)
  79. longTags[stag] = ltag
  80. shortTags[ltag] = stag
  81. }
  82. }
  83. const longTagPrefix = "tag:yaml.org,2002:"
  84. func shortTag(tag string) string {
  85. if strings.HasPrefix(tag, longTagPrefix) {
  86. if stag, ok := shortTags[tag]; ok {
  87. return stag
  88. }
  89. return "!!" + tag[len(longTagPrefix):]
  90. }
  91. return tag
  92. }
  93. func longTag(tag string) string {
  94. if strings.HasPrefix(tag, "!!") {
  95. if ltag, ok := longTags[tag]; ok {
  96. return ltag
  97. }
  98. return longTagPrefix + tag[2:]
  99. }
  100. return tag
  101. }
  102. func resolvableTag(tag string) bool {
  103. switch tag {
  104. case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag:
  105. return true
  106. }
  107. return false
  108. }
  109. var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
  110. func resolve(tag string, in string) (rtag string, out interface{}) {
  111. tag = shortTag(tag)
  112. if !resolvableTag(tag) {
  113. return tag, in
  114. }
  115. defer func() {
  116. switch tag {
  117. case "", rtag, strTag, binaryTag:
  118. return
  119. case floatTag:
  120. if rtag == intTag {
  121. switch v := out.(type) {
  122. case int64:
  123. rtag = floatTag
  124. out = float64(v)
  125. return
  126. case int:
  127. rtag = floatTag
  128. out = float64(v)
  129. return
  130. }
  131. }
  132. }
  133. failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
  134. }()
  135. // Any data is accepted as a !!str or !!binary.
  136. // Otherwise, the prefix is enough of a hint about what it might be.
  137. hint := byte('N')
  138. if in != "" {
  139. hint = resolveTable[in[0]]
  140. }
  141. if hint != 0 && tag != strTag && tag != binaryTag {
  142. // Handle things we can lookup in a map.
  143. if item, ok := resolveMap[in]; ok {
  144. return item.tag, item.value
  145. }
  146. // Base 60 floats are a bad idea, were dropped in YAML 1.2, and
  147. // are purposefully unsupported here. They're still quoted on
  148. // the way out for compatibility with other parser, though.
  149. switch hint {
  150. case 'M':
  151. // We've already checked the map above.
  152. case '.':
  153. // Not in the map, so maybe a normal float.
  154. floatv, err := strconv.ParseFloat(in, 64)
  155. if err == nil {
  156. return floatTag, floatv
  157. }
  158. case 'D', 'S':
  159. // Int, float, or timestamp.
  160. // Only try values as a timestamp if the value is unquoted or there's an explicit
  161. // !!timestamp tag.
  162. if tag == "" || tag == timestampTag {
  163. t, ok := parseTimestamp(in)
  164. if ok {
  165. return timestampTag, t
  166. }
  167. }
  168. plain := strings.Replace(in, "_", "", -1)
  169. intv, err := strconv.ParseInt(plain, 0, 64)
  170. if err == nil {
  171. if intv == int64(int(intv)) {
  172. return intTag, int(intv)
  173. } else {
  174. return intTag, intv
  175. }
  176. }
  177. uintv, err := strconv.ParseUint(plain, 0, 64)
  178. if err == nil {
  179. return intTag, uintv
  180. }
  181. if yamlStyleFloat.MatchString(plain) {
  182. floatv, err := strconv.ParseFloat(plain, 64)
  183. if err == nil {
  184. return floatTag, floatv
  185. }
  186. }
  187. if strings.HasPrefix(plain, "0b") {
  188. intv, err := strconv.ParseInt(plain[2:], 2, 64)
  189. if err == nil {
  190. if intv == int64(int(intv)) {
  191. return intTag, int(intv)
  192. } else {
  193. return intTag, intv
  194. }
  195. }
  196. uintv, err := strconv.ParseUint(plain[2:], 2, 64)
  197. if err == nil {
  198. return intTag, uintv
  199. }
  200. } else if strings.HasPrefix(plain, "-0b") {
  201. intv, err := strconv.ParseInt("-"+plain[3:], 2, 64)
  202. if err == nil {
  203. if true || intv == int64(int(intv)) {
  204. return intTag, int(intv)
  205. } else {
  206. return intTag, intv
  207. }
  208. }
  209. }
  210. // Octals as introduced in version 1.2 of the spec.
  211. // Octals from the 1.1 spec, spelled as 0777, are still
  212. // decoded by default in v3 as well for compatibility.
  213. // May be dropped in v4 depending on how usage evolves.
  214. if strings.HasPrefix(plain, "0o") {
  215. intv, err := strconv.ParseInt(plain[2:], 8, 64)
  216. if err == nil {
  217. if intv == int64(int(intv)) {
  218. return intTag, int(intv)
  219. } else {
  220. return intTag, intv
  221. }
  222. }
  223. uintv, err := strconv.ParseUint(plain[2:], 8, 64)
  224. if err == nil {
  225. return intTag, uintv
  226. }
  227. } else if strings.HasPrefix(plain, "-0o") {
  228. intv, err := strconv.ParseInt("-"+plain[3:], 8, 64)
  229. if err == nil {
  230. if true || intv == int64(int(intv)) {
  231. return intTag, int(intv)
  232. } else {
  233. return intTag, intv
  234. }
  235. }
  236. }
  237. default:
  238. panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")")
  239. }
  240. }
  241. return strTag, in
  242. }
  243. // encodeBase64 encodes s as base64 that is broken up into multiple lines
  244. // as appropriate for the resulting length.
  245. func encodeBase64(s string) string {
  246. const lineLen = 70
  247. encLen := base64.StdEncoding.EncodedLen(len(s))
  248. lines := encLen/lineLen + 1
  249. buf := make([]byte, encLen*2+lines)
  250. in := buf[0:encLen]
  251. out := buf[encLen:]
  252. base64.StdEncoding.Encode(in, []byte(s))
  253. k := 0
  254. for i := 0; i < len(in); i += lineLen {
  255. j := i + lineLen
  256. if j > len(in) {
  257. j = len(in)
  258. }
  259. k += copy(out[k:], in[i:j])
  260. if lines > 1 {
  261. out[k] = '\n'
  262. k++
  263. }
  264. }
  265. return string(out[:k])
  266. }
  267. // This is a subset of the formats allowed by the regular expression
  268. // defined at http://yaml.org/type/timestamp.html.
  269. var allowedTimestampFormats = []string{
  270. "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
  271. "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
  272. "2006-1-2 15:4:5.999999999", // space separated with no time zone
  273. "2006-1-2", // date only
  274. // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
  275. // from the set of examples.
  276. }
  277. // parseTimestamp parses s as a timestamp string and
  278. // returns the timestamp and reports whether it succeeded.
  279. // Timestamp formats are defined at http://yaml.org/type/timestamp.html
  280. func parseTimestamp(s string) (time.Time, bool) {
  281. // TODO write code to check all the formats supported by
  282. // http://yaml.org/type/timestamp.html instead of using time.Parse.
  283. // Quick check: all date formats start with YYYY-.
  284. i := 0
  285. for ; i < len(s); i++ {
  286. if c := s[i]; c < '0' || c > '9' {
  287. break
  288. }
  289. }
  290. if i != 4 || i == len(s) || s[i] != '-' {
  291. return time.Time{}, false
  292. }
  293. for _, format := range allowedTimestampFormats {
  294. if t, err := time.Parse(format, s); err == nil {
  295. return t, true
  296. }
  297. }
  298. return time.Time{}, false
  299. }