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