mirror of https://github.com/status-im/consul.git
205 lines
4.3 KiB
Go
205 lines
4.3 KiB
Go
|
package xmlrpc
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/xml"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type encodeFunc func(reflect.Value) ([]byte, error)
|
||
|
|
||
|
func marshal(v interface{}) ([]byte, error) {
|
||
|
if v == nil {
|
||
|
return []byte{}, nil
|
||
|
}
|
||
|
|
||
|
val := reflect.ValueOf(v)
|
||
|
return encodeValue(val)
|
||
|
}
|
||
|
|
||
|
func encodeValue(val reflect.Value) ([]byte, error) {
|
||
|
var b []byte
|
||
|
var err error
|
||
|
|
||
|
if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
|
||
|
if val.IsNil() {
|
||
|
return []byte("<value/>"), nil
|
||
|
}
|
||
|
|
||
|
val = val.Elem()
|
||
|
}
|
||
|
|
||
|
switch val.Kind() {
|
||
|
case reflect.Struct:
|
||
|
switch val.Interface().(type) {
|
||
|
case time.Time:
|
||
|
t := val.Interface().(time.Time)
|
||
|
b = []byte(fmt.Sprintf("<dateTime.iso8601>%s</dateTime.iso8601>", t.Format(iso8601)))
|
||
|
default:
|
||
|
b, err = encodeStruct(val)
|
||
|
}
|
||
|
case reflect.Map:
|
||
|
b, err = encodeMap(val)
|
||
|
case reflect.Slice:
|
||
|
b, err = encodeSlice(val)
|
||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
|
b = []byte(fmt.Sprintf("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
|
||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||
|
b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
b = []byte(fmt.Sprintf("<double>%s</double>",
|
||
|
strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits())))
|
||
|
case reflect.Bool:
|
||
|
if val.Bool() {
|
||
|
b = []byte("<boolean>1</boolean>")
|
||
|
} else {
|
||
|
b = []byte("<boolean>0</boolean>")
|
||
|
}
|
||
|
case reflect.String:
|
||
|
var buf bytes.Buffer
|
||
|
|
||
|
xml.Escape(&buf, []byte(val.String()))
|
||
|
|
||
|
if _, ok := val.Interface().(Base64); ok {
|
||
|
b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
|
||
|
} else {
|
||
|
b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
|
||
|
}
|
||
|
default:
|
||
|
return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
|
||
|
}
|
||
|
|
||
|
func encodeStruct(value reflect.Value) ([]byte, error) {
|
||
|
var b bytes.Buffer
|
||
|
|
||
|
b.WriteString("<struct>")
|
||
|
|
||
|
vals := []reflect.Value{value}
|
||
|
for j := 0; j < len(vals); j++ {
|
||
|
val := vals[j]
|
||
|
t := val.Type()
|
||
|
for i := 0; i < t.NumField(); i++ {
|
||
|
f := t.Field(i)
|
||
|
tag := f.Tag.Get("xmlrpc")
|
||
|
name := f.Name
|
||
|
fieldVal := val.FieldByName(f.Name)
|
||
|
fieldValKind := fieldVal.Kind()
|
||
|
|
||
|
// Omit unexported fields
|
||
|
if !fieldVal.CanInterface() {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Omit fields who are structs that contain no fields themselves
|
||
|
if fieldValKind == reflect.Struct && fieldVal.NumField() == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Omit empty slices
|
||
|
if fieldValKind == reflect.Slice && fieldVal.Len() == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Omit empty fields (defined as nil pointers)
|
||
|
if tag != "" {
|
||
|
parts := strings.Split(tag, ",")
|
||
|
name = parts[0]
|
||
|
if len(parts) > 1 && parts[1] == "omitempty" {
|
||
|
if fieldValKind == reflect.Ptr && fieldVal.IsNil() {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Drill down into anonymous/embedded structs and do not expose the
|
||
|
// containing embedded struct in request.
|
||
|
// This will effectively pull up fields in embedded structs to look
|
||
|
// as part of the original struct in the request.
|
||
|
if f.Anonymous {
|
||
|
vals = append(vals, fieldVal)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
b.WriteString("<member>")
|
||
|
b.WriteString(fmt.Sprintf("<name>%s</name>", name))
|
||
|
|
||
|
p, err := encodeValue(fieldVal)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
b.Write(p)
|
||
|
|
||
|
b.WriteString("</member>")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
b.WriteString("</struct>")
|
||
|
|
||
|
return b.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
func encodeMap(val reflect.Value) ([]byte, error) {
|
||
|
var t = val.Type()
|
||
|
|
||
|
if t.Key().Kind() != reflect.String {
|
||
|
return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
|
||
|
}
|
||
|
|
||
|
var b bytes.Buffer
|
||
|
|
||
|
b.WriteString("<struct>")
|
||
|
|
||
|
keys := val.MapKeys()
|
||
|
|
||
|
for i := 0; i < val.Len(); i++ {
|
||
|
key := keys[i]
|
||
|
kval := val.MapIndex(key)
|
||
|
|
||
|
b.WriteString("<member>")
|
||
|
b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
|
||
|
|
||
|
p, err := encodeValue(kval)
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
b.Write(p)
|
||
|
b.WriteString("</member>")
|
||
|
}
|
||
|
|
||
|
b.WriteString("</struct>")
|
||
|
|
||
|
return b.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
func encodeSlice(val reflect.Value) ([]byte, error) {
|
||
|
var b bytes.Buffer
|
||
|
|
||
|
b.WriteString("<array><data>")
|
||
|
|
||
|
for i := 0; i < val.Len(); i++ {
|
||
|
p, err := encodeValue(val.Index(i))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
b.Write(p)
|
||
|
}
|
||
|
|
||
|
b.WriteString("</data></array>")
|
||
|
|
||
|
return b.Bytes(), nil
|
||
|
}
|