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