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