1package xmlrpc
2
3import (
4	"bytes"
5	"encoding/xml"
6	"fmt"
7	"reflect"
8	"sort"
9	"strconv"
10	"strings"
11	"time"
12)
13
14// Base64 represents value in base64 encoding
15type Base64 string
16
17type encodeFunc func(reflect.Value) ([]byte, error)
18
19func marshal(v interface{}) ([]byte, error) {
20	if v == nil {
21		return []byte{}, nil
22	}
23
24	val := reflect.ValueOf(v)
25	return encodeValue(val)
26}
27
28func encodeValue(val reflect.Value) ([]byte, error) {
29	var b []byte
30	var err error
31
32	if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
33		if val.IsNil() {
34			return []byte("<value/>"), nil
35		}
36
37		val = val.Elem()
38	}
39
40	switch val.Kind() {
41	case reflect.Struct:
42		switch val.Interface().(type) {
43		case time.Time:
44			t := val.Interface().(time.Time)
45			b = []byte(fmt.Sprintf("<dateTime.iso8601>%s</dateTime.iso8601>", t.Format(iso8601)))
46		default:
47			b, err = encodeStruct(val)
48		}
49	case reflect.Map:
50		b, err = encodeMap(val)
51	case reflect.Slice:
52		b, err = encodeSlice(val)
53	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
54		b = []byte(fmt.Sprintf("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
55	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
56		b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
57	case reflect.Float32, reflect.Float64:
58		b = []byte(fmt.Sprintf("<double>%s</double>",
59			strconv.FormatFloat(val.Float(), 'f', -1, val.Type().Bits())))
60	case reflect.Bool:
61		if val.Bool() {
62			b = []byte("<boolean>1</boolean>")
63		} else {
64			b = []byte("<boolean>0</boolean>")
65		}
66	case reflect.String:
67		var buf bytes.Buffer
68
69		xml.Escape(&buf, []byte(val.String()))
70
71		if _, ok := val.Interface().(Base64); ok {
72			b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
73		} else {
74			b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
75		}
76	default:
77		return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
78	}
79
80	if err != nil {
81		return nil, err
82	}
83
84	return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
85}
86
87func encodeStruct(value reflect.Value) ([]byte, error) {
88	var b bytes.Buffer
89
90	b.WriteString("<struct>")
91
92	vals := []reflect.Value{value}
93	for j := 0; j < len(vals); j++ {
94		val := vals[j]
95		t := val.Type()
96		for i := 0; i < t.NumField(); i++ {
97			f := t.Field(i)
98			tag := f.Tag.Get("xmlrpc")
99			name := f.Name
100			fieldVal := val.FieldByName(f.Name)
101			fieldValKind := fieldVal.Kind()
102
103			// Omit unexported fields
104			if !fieldVal.CanInterface() {
105				continue
106			}
107
108			// Omit fields who are structs that contain no fields themselves
109			if fieldValKind == reflect.Struct && fieldVal.NumField() == 0 {
110				continue
111			}
112
113			// Omit empty slices
114			if fieldValKind == reflect.Slice && fieldVal.Len() == 0 {
115				continue
116			}
117
118			// Omit empty fields (defined as nil pointers)
119			if tag != "" {
120				parts := strings.Split(tag, ",")
121				name = parts[0]
122				if len(parts) > 1 && parts[1] == "omitempty" {
123					if fieldValKind == reflect.Ptr && fieldVal.IsNil() {
124						continue
125					}
126				}
127			}
128
129			// Drill down into anonymous/embedded structs and do not expose the
130			// containing embedded struct in request.
131			// This will effectively pull up fields in embedded structs to look
132			// as part of the original struct in the request.
133			if f.Anonymous {
134				vals = append(vals, fieldVal)
135				continue
136			}
137
138			b.WriteString("<member>")
139			b.WriteString(fmt.Sprintf("<name>%s</name>", name))
140
141			p, err := encodeValue(fieldVal)
142			if err != nil {
143				return nil, err
144			}
145			b.Write(p)
146
147			b.WriteString("</member>")
148		}
149	}
150
151	b.WriteString("</struct>")
152
153	return b.Bytes(), nil
154}
155
156var sortMapKeys bool
157
158func encodeMap(val reflect.Value) ([]byte, error) {
159	var t = val.Type()
160
161	if t.Key().Kind() != reflect.String {
162		return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
163	}
164
165	var b bytes.Buffer
166
167	b.WriteString("<struct>")
168
169	keys := val.MapKeys()
170
171	if sortMapKeys {
172		sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() })
173	}
174
175	for i := 0; i < val.Len(); i++ {
176		key := keys[i]
177		kval := val.MapIndex(key)
178
179		b.WriteString("<member>")
180		b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
181
182		p, err := encodeValue(kval)
183
184		if err != nil {
185			return nil, err
186		}
187
188		b.Write(p)
189		b.WriteString("</member>")
190	}
191
192	b.WriteString("</struct>")
193
194	return b.Bytes(), nil
195}
196
197func encodeSlice(val reflect.Value) ([]byte, error) {
198	var b bytes.Buffer
199
200	b.WriteString("<array><data>")
201
202	for i := 0; i < val.Len(); i++ {
203		p, err := encodeValue(val.Index(i))
204		if err != nil {
205			return nil, err
206		}
207
208		b.Write(p)
209	}
210
211	b.WriteString("</data></array>")
212
213	return b.Bytes(), nil
214}
215