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"
20	"fmt"
21	"io"
22	"reflect"
23	"regexp"
24	"sort"
25	"strconv"
26	"strings"
27	"time"
28	"unicode/utf8"
29)
30
31type encoder struct {
32	emitter  yaml_emitter_t
33	event    yaml_event_t
34	out      []byte
35	flow     bool
36	indent   int
37	doneInit bool
38}
39
40func newEncoder() *encoder {
41	e := &encoder{}
42	yaml_emitter_initialize(&e.emitter)
43	yaml_emitter_set_output_string(&e.emitter, &e.out)
44	yaml_emitter_set_unicode(&e.emitter, true)
45	return e
46}
47
48func newEncoderWithWriter(w io.Writer) *encoder {
49	e := &encoder{}
50	yaml_emitter_initialize(&e.emitter)
51	yaml_emitter_set_output_writer(&e.emitter, w)
52	yaml_emitter_set_unicode(&e.emitter, true)
53	return e
54}
55
56func (e *encoder) init() {
57	if e.doneInit {
58		return
59	}
60	if e.indent == 0 {
61		e.indent = 4
62	}
63	e.emitter.best_indent = e.indent
64	yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
65	e.emit()
66	e.doneInit = true
67}
68
69func (e *encoder) finish() {
70	e.emitter.open_ended = false
71	yaml_stream_end_event_initialize(&e.event)
72	e.emit()
73}
74
75func (e *encoder) destroy() {
76	yaml_emitter_delete(&e.emitter)
77}
78
79func (e *encoder) emit() {
80	// This will internally delete the e.event value.
81	e.must(yaml_emitter_emit(&e.emitter, &e.event))
82}
83
84func (e *encoder) must(ok bool) {
85	if !ok {
86		msg := e.emitter.problem
87		if msg == "" {
88			msg = "unknown problem generating YAML content"
89		}
90		failf("%s", msg)
91	}
92}
93
94func (e *encoder) marshalDoc(tag string, in reflect.Value) {
95	e.init()
96	var node *Node
97	if in.IsValid() {
98		node, _ = in.Interface().(*Node)
99	}
100	if node != nil && node.Kind == DocumentNode {
101		e.nodev(in)
102	} else {
103		yaml_document_start_event_initialize(&e.event, nil, nil, true)
104		e.emit()
105		e.marshal(tag, in)
106		yaml_document_end_event_initialize(&e.event, true)
107		e.emit()
108	}
109}
110
111func (e *encoder) marshal(tag string, in reflect.Value) {
112	tag = shortTag(tag)
113	if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
114		e.nilv()
115		return
116	}
117	iface := in.Interface()
118	switch value := iface.(type) {
119	case *Node:
120		e.nodev(in)
121		return
122	case Node:
123		if !in.CanAddr() {
124			var n = reflect.New(in.Type()).Elem()
125			n.Set(in)
126			in = n
127		}
128		e.nodev(in.Addr())
129		return
130	case time.Time:
131		e.timev(tag, in)
132		return
133	case *time.Time:
134		e.timev(tag, in.Elem())
135		return
136	case time.Duration:
137		e.stringv(tag, reflect.ValueOf(value.String()))
138		return
139	case Marshaler:
140		v, err := value.MarshalYAML()
141		if err != nil {
142			fail(err)
143		}
144		if v == nil {
145			e.nilv()
146			return
147		}
148		e.marshal(tag, reflect.ValueOf(v))
149		return
150	case encoding.TextMarshaler:
151		text, err := value.MarshalText()
152		if err != nil {
153			fail(err)
154		}
155		in = reflect.ValueOf(string(text))
156	case nil:
157		e.nilv()
158		return
159	}
160	switch in.Kind() {
161	case reflect.Interface:
162		e.marshal(tag, in.Elem())
163	case reflect.Map:
164		e.mapv(tag, in)
165	case reflect.Ptr:
166		e.marshal(tag, in.Elem())
167	case reflect.Struct:
168		e.structv(tag, in)
169	case reflect.Slice, reflect.Array:
170		e.slicev(tag, in)
171	case reflect.String:
172		e.stringv(tag, in)
173	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
174		e.intv(tag, in)
175	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
176		e.uintv(tag, in)
177	case reflect.Float32, reflect.Float64:
178		e.floatv(tag, in)
179	case reflect.Bool:
180		e.boolv(tag, in)
181	default:
182		panic("cannot marshal type: " + in.Type().String())
183	}
184}
185
186func (e *encoder) mapv(tag string, in reflect.Value) {
187	e.mappingv(tag, func() {
188		keys := keyList(in.MapKeys())
189		sort.Sort(keys)
190		for _, k := range keys {
191			e.marshal("", k)
192			e.marshal("", in.MapIndex(k))
193		}
194	})
195}
196
197func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
198	for _, num := range index {
199		for {
200			if v.Kind() == reflect.Ptr {
201				if v.IsNil() {
202					return reflect.Value{}
203				}
204				v = v.Elem()
205				continue
206			}
207			break
208		}
209		v = v.Field(num)
210	}
211	return v
212}
213
214func (e *encoder) structv(tag string, in reflect.Value) {
215	sinfo, err := getStructInfo(in.Type())
216	if err != nil {
217		panic(err)
218	}
219	e.mappingv(tag, func() {
220		for _, info := range sinfo.FieldsList {
221			var value reflect.Value
222			if info.Inline == nil {
223				value = in.Field(info.Num)
224			} else {
225				value = e.fieldByIndex(in, info.Inline)
226				if !value.IsValid() {
227					continue
228				}
229			}
230			if info.OmitEmpty && isZero(value) {
231				continue
232			}
233			e.marshal("", reflect.ValueOf(info.Key))
234			e.flow = info.Flow
235			e.marshal("", value)
236		}
237		if sinfo.InlineMap >= 0 {
238			m := in.Field(sinfo.InlineMap)
239			if m.Len() > 0 {
240				e.flow = false
241				keys := keyList(m.MapKeys())
242				sort.Sort(keys)
243				for _, k := range keys {
244					if _, found := sinfo.FieldsMap[k.String()]; found {
245						panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
246					}
247					e.marshal("", k)
248					e.flow = false
249					e.marshal("", m.MapIndex(k))
250				}
251			}
252		}
253	})
254}
255
256func (e *encoder) mappingv(tag string, f func()) {
257	implicit := tag == ""
258	style := yaml_BLOCK_MAPPING_STYLE
259	if e.flow {
260		e.flow = false
261		style = yaml_FLOW_MAPPING_STYLE
262	}
263	yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
264	e.emit()
265	f()
266	yaml_mapping_end_event_initialize(&e.event)
267	e.emit()
268}
269
270func (e *encoder) slicev(tag string, in reflect.Value) {
271	implicit := tag == ""
272	style := yaml_BLOCK_SEQUENCE_STYLE
273	if e.flow {
274		e.flow = false
275		style = yaml_FLOW_SEQUENCE_STYLE
276	}
277	e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
278	e.emit()
279	n := in.Len()
280	for i := 0; i < n; i++ {
281		e.marshal("", in.Index(i))
282	}
283	e.must(yaml_sequence_end_event_initialize(&e.event))
284	e.emit()
285}
286
287// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
288//
289// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
290// in YAML 1.2 and by this package, but these should be marshalled quoted for
291// the time being for compatibility with other parsers.
292func isBase60Float(s string) (result bool) {
293	// Fast path.
294	if s == "" {
295		return false
296	}
297	c := s[0]
298	if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
299		return false
300	}
301	// Do the full match.
302	return base60float.MatchString(s)
303}
304
305// From http://yaml.org/type/float.html, except the regular expression there
306// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
307var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
308
309// isOldBool returns whether s is bool notation as defined in YAML 1.1.
310//
311// We continue to force strings that YAML 1.1 would interpret as booleans to be
312// rendered as quotes strings so that the marshalled output valid for YAML 1.1
313// parsing.
314func isOldBool(s string) (result bool) {
315	switch s {
316	case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
317		"n", "N", "no", "No", "NO", "off", "Off", "OFF":
318		return true
319	default:
320		return false
321	}
322}
323
324func (e *encoder) stringv(tag string, in reflect.Value) {
325	var style yaml_scalar_style_t
326	s := in.String()
327	canUsePlain := true
328	switch {
329	case !utf8.ValidString(s):
330		if tag == binaryTag {
331			failf("explicitly tagged !!binary data must be base64-encoded")
332		}
333		if tag != "" {
334			failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
335		}
336		// It can't be encoded directly as YAML so use a binary tag
337		// and encode it as base64.
338		tag = binaryTag
339		s = encodeBase64(s)
340	case tag == "":
341		// Check to see if it would resolve to a specific
342		// tag when encoded unquoted. If it doesn't,
343		// there's no need to quote it.
344		rtag, _ := resolve("", s)
345		canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
346	}
347	// Note: it's possible for user code to emit invalid YAML
348	// if they explicitly specify a tag and a string containing
349	// text that's incompatible with that tag.
350	switch {
351	case strings.Contains(s, "\n"):
352		if e.flow {
353			style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
354		} else {
355			style = yaml_LITERAL_SCALAR_STYLE
356		}
357	case canUsePlain:
358		style = yaml_PLAIN_SCALAR_STYLE
359	default:
360		style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
361	}
362	e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
363}
364
365func (e *encoder) boolv(tag string, in reflect.Value) {
366	var s string
367	if in.Bool() {
368		s = "true"
369	} else {
370		s = "false"
371	}
372	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
373}
374
375func (e *encoder) intv(tag string, in reflect.Value) {
376	s := strconv.FormatInt(in.Int(), 10)
377	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
378}
379
380func (e *encoder) uintv(tag string, in reflect.Value) {
381	s := strconv.FormatUint(in.Uint(), 10)
382	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
383}
384
385func (e *encoder) timev(tag string, in reflect.Value) {
386	t := in.Interface().(time.Time)
387	s := t.Format(time.RFC3339Nano)
388	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
389}
390
391func (e *encoder) floatv(tag string, in reflect.Value) {
392	// Issue #352: When formatting, use the precision of the underlying value
393	precision := 64
394	if in.Kind() == reflect.Float32 {
395		precision = 32
396	}
397
398	s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
399	switch s {
400	case "+Inf":
401		s = ".inf"
402	case "-Inf":
403		s = "-.inf"
404	case "NaN":
405		s = ".nan"
406	}
407	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
408}
409
410func (e *encoder) nilv() {
411	e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
412}
413
414func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
415	// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
416	implicit := tag == ""
417	if !implicit {
418		tag = longTag(tag)
419	}
420	e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
421	e.event.head_comment = head
422	e.event.line_comment = line
423	e.event.foot_comment = foot
424	e.event.tail_comment = tail
425	e.emit()
426}
427
428func (e *encoder) nodev(in reflect.Value) {
429	e.node(in.Interface().(*Node), "")
430}
431
432func (e *encoder) node(node *Node, tail string) {
433	// Zero nodes behave as nil.
434	if node.Kind == 0 && node.IsZero() {
435		e.nilv()
436		return
437	}
438
439	// If the tag was not explicitly requested, and dropping it won't change the
440	// implicit tag of the value, don't include it in the presentation.
441	var tag = node.Tag
442	var stag = shortTag(tag)
443	var forceQuoting bool
444	if tag != "" && node.Style&TaggedStyle == 0 {
445		if node.Kind == ScalarNode {
446			if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
447				tag = ""
448			} else {
449				rtag, _ := resolve("", node.Value)
450				if rtag == stag {
451					tag = ""
452				} else if stag == strTag {
453					tag = ""
454					forceQuoting = true
455				}
456			}
457		} else {
458			var rtag string
459			switch node.Kind {
460			case MappingNode:
461				rtag = mapTag
462			case SequenceNode:
463				rtag = seqTag
464			}
465			if rtag == stag {
466				tag = ""
467			}
468		}
469	}
470
471	switch node.Kind {
472	case DocumentNode:
473		yaml_document_start_event_initialize(&e.event, nil, nil, true)
474		e.event.head_comment = []byte(node.HeadComment)
475		e.emit()
476		for _, node := range node.Content {
477			e.node(node, "")
478		}
479		yaml_document_end_event_initialize(&e.event, true)
480		e.event.foot_comment = []byte(node.FootComment)
481		e.emit()
482
483	case SequenceNode:
484		style := yaml_BLOCK_SEQUENCE_STYLE
485		if node.Style&FlowStyle != 0 {
486			style = yaml_FLOW_SEQUENCE_STYLE
487		}
488		e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style))
489		e.event.head_comment = []byte(node.HeadComment)
490		e.emit()
491		for _, node := range node.Content {
492			e.node(node, "")
493		}
494		e.must(yaml_sequence_end_event_initialize(&e.event))
495		e.event.line_comment = []byte(node.LineComment)
496		e.event.foot_comment = []byte(node.FootComment)
497		e.emit()
498
499	case MappingNode:
500		style := yaml_BLOCK_MAPPING_STYLE
501		if node.Style&FlowStyle != 0 {
502			style = yaml_FLOW_MAPPING_STYLE
503		}
504		yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
505		e.event.tail_comment = []byte(tail)
506		e.event.head_comment = []byte(node.HeadComment)
507		e.emit()
508
509		// The tail logic below moves the foot comment of prior keys to the following key,
510		// since the value for each key may be a nested structure and the foot needs to be
511		// processed only the entirety of the value is streamed. The last tail is processed
512		// with the mapping end event.
513		var tail string
514		for i := 0; i+1 < len(node.Content); i += 2 {
515			k := node.Content[i]
516			foot := k.FootComment
517			if foot != "" {
518				kopy := *k
519				kopy.FootComment = ""
520				k = &kopy
521			}
522			e.node(k, tail)
523			tail = foot
524
525			v := node.Content[i+1]
526			e.node(v, "")
527		}
528
529		yaml_mapping_end_event_initialize(&e.event)
530		e.event.tail_comment = []byte(tail)
531		e.event.line_comment = []byte(node.LineComment)
532		e.event.foot_comment = []byte(node.FootComment)
533		e.emit()
534
535	case AliasNode:
536		yaml_alias_event_initialize(&e.event, []byte(node.Value))
537		e.event.head_comment = []byte(node.HeadComment)
538		e.event.line_comment = []byte(node.LineComment)
539		e.event.foot_comment = []byte(node.FootComment)
540		e.emit()
541
542	case ScalarNode:
543		value := node.Value
544		if !utf8.ValidString(value) {
545			if stag == binaryTag {
546				failf("explicitly tagged !!binary data must be base64-encoded")
547			}
548			if stag != "" {
549				failf("cannot marshal invalid UTF-8 data as %s", stag)
550			}
551			// It can't be encoded directly as YAML so use a binary tag
552			// and encode it as base64.
553			tag = binaryTag
554			value = encodeBase64(value)
555		}
556
557		style := yaml_PLAIN_SCALAR_STYLE
558		switch {
559		case node.Style&DoubleQuotedStyle != 0:
560			style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
561		case node.Style&SingleQuotedStyle != 0:
562			style = yaml_SINGLE_QUOTED_SCALAR_STYLE
563		case node.Style&LiteralStyle != 0:
564			style = yaml_LITERAL_SCALAR_STYLE
565		case node.Style&FoldedStyle != 0:
566			style = yaml_FOLDED_SCALAR_STYLE
567		case strings.Contains(value, "\n"):
568			style = yaml_LITERAL_SCALAR_STYLE
569		case forceQuoting:
570			style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
571		}
572
573		e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
574	default:
575		failf("cannot encode node with unknown kind %d", node.Kind)
576	}
577}
578