package xmlrpc import ( "bytes" "encoding/xml" "fmt" "reflect" "sort" "strconv" "strings" "time" ) // Base64 represents value in base64 encoding type Base64 string 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(""), 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("%s", 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("%s", strconv.FormatInt(val.Int(), 10))) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: b = []byte(fmt.Sprintf("%s", strconv.FormatUint(val.Uint(), 10))) case reflect.Float32, reflect.Float64: b = []byte(fmt.Sprintf("%s", strconv.FormatFloat(val.Float(), 'f', -1, val.Type().Bits()))) case reflect.Bool: if val.Bool() { b = []byte("1") } else { b = []byte("0") } case reflect.String: var buf bytes.Buffer xml.Escape(&buf, []byte(val.String())) if _, ok := val.Interface().(Base64); ok { b = []byte(fmt.Sprintf("%s", buf.String())) } else { b = []byte(fmt.Sprintf("%s", buf.String())) } default: return nil, fmt.Errorf("xmlrpc encode error: unsupported type") } if err != nil { return nil, err } return []byte(fmt.Sprintf("%s", string(b))), nil } func encodeStruct(structVal reflect.Value) ([]byte, error) { var b bytes.Buffer b.WriteString("") structType := structVal.Type() for i := 0; i < structType.NumField(); i++ { fieldVal := structVal.Field(i) fieldType := structType.Field(i) name := fieldType.Tag.Get("xmlrpc") // skip ignored fields. if name == "-" { continue } // if the tag has the omitempty property, skip it if strings.HasSuffix(name, ",omitempty") && fieldVal.IsZero() { continue } name = strings.TrimSuffix(name, ",omitempty") if name == "" { name = fieldType.Name } p, err := encodeValue(fieldVal) if err != nil { return nil, err } b.WriteString("") b.WriteString(fmt.Sprintf("%s", name)) b.Write(p) b.WriteString("") } b.WriteString("") return b.Bytes(), nil } var sortMapKeys bool 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("") keys := val.MapKeys() if sortMapKeys { sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() }) } for i := 0; i < val.Len(); i++ { key := keys[i] kval := val.MapIndex(key) b.WriteString("") b.WriteString(fmt.Sprintf("%s", key.String())) p, err := encodeValue(kval) if err != nil { return nil, err } b.Write(p) b.WriteString("") } b.WriteString("") return b.Bytes(), nil } func encodeSlice(val reflect.Value) ([]byte, error) { var b bytes.Buffer b.WriteString("") for i := 0; i < val.Len(); i++ { p, err := encodeValue(val.Index(i)) if err != nil { return nil, err } b.Write(p) } b.WriteString("") return b.Bytes(), nil }