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(value reflect.Value) ([]byte, error) { var b bytes.Buffer b.WriteString("") 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("") b.WriteString(fmt.Sprintf("%s", name)) p, err := encodeValue(fieldVal) if err != nil { return nil, err } 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 }