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