package xmlrpc import ( "encoding/base64" "encoding/xml" "fmt" "io" "reflect" "time" ) // Encoder implementations are responsible for handling encoding of XML-RPC requests to the proper wire format. type Encoder interface { Encode(w io.Writer, methodName string, args interface{}) error } // StdEncoder is the default implementation of Encoder interface. type StdEncoder struct{} func (e *StdEncoder) Encode(w io.Writer, methodName string, args interface{}) error { _, _ = fmt.Fprintf(w, "%s", methodName) if args != nil { if err := e.encodeArgs(w, args); err != nil { return fmt.Errorf("cannot encoded provided method arguments: %w", err) } } _, _ = fmt.Fprint(w, "") return nil } func (e *StdEncoder) encodeArgs(w io.Writer, args interface{}) error { // Allows reading both pointer and value-structs elem := reflect.Indirect(reflect.ValueOf(args)) numFields := elem.NumField() if numFields > 0 { hasExportedFields := false for fN := 0; fN < numFields; fN++ { field := elem.Field(fN) if !field.CanInterface() { continue } // If this is first exported field - print out tag if !hasExportedFields { hasExportedFields = true _, _ = fmt.Fprint(w, "") } _, _ = fmt.Fprint(w, "") if err := e.encodeValue(w, field.Interface()); err != nil { return fmt.Errorf("cannot encode argument '%s': %w", elem.Type().Field(fN).Name, err) } _, _ = fmt.Fprint(w, "") } // Only write closing tag if at least one exported field is found if hasExportedFields { _, _ = fmt.Fprint(w, "") } } return nil } // encodeValue will encode input into the XML-RPC compatible format. // If provided value is a pointer, value of pointer will be used, unless pointer is nil. // In that case a value is returned. // // See more: https://en.wikipedia.org/wiki/XML-RPC#Data_types func (e *StdEncoder) encodeValue(w io.Writer, value interface{}) error { valueOf := reflect.ValueOf(value) kind := valueOf.Kind() // Handling pointers by following them. if kind == reflect.Ptr { if valueOf.IsNil() { _, _ = fmt.Fprint(w, "") return nil } return e.encodeValue(w, valueOf.Elem().Interface()) } _, _ = fmt.Fprint(w, "") switch kind { case reflect.Bool: if err := e.encodeBoolean(w, value.(bool)); err != nil { return fmt.Errorf("cannot encode boolean value: %w", err) } case reflect.Int: if err := e.encodeInteger(w, value.(int)); err != nil { return fmt.Errorf("cannot encode integer value: %w", err) } case reflect.Float64: if err := e.encodeDouble(w, value.(float64)); err != nil { return fmt.Errorf("cannot encode double value: %w", err) } case reflect.String: if err := e.encodeString(w, value.(string)); err != nil { return fmt.Errorf("cannot encode string value: %w", err) } case reflect.Array, reflect.Slice: if e.isByteArray(value) { if err := e.encodeBase64(w, value.([]byte)); err != nil { return fmt.Errorf("cannot encode byte-array value: %w", err) } } else { if err := e.encodeArray(w, value); err != nil { return fmt.Errorf("cannot encode array value: %w", err) } } case reflect.Struct: if reflect.TypeOf(value).String() != "time.Time" { if err := e.encodeStruct(w, value); err != nil { return fmt.Errorf("cannot encode struct value: %w", err) } } else { if err := e.encodeTime(w, value.(time.Time)); err != nil { return fmt.Errorf("cannot encode time.Time value: %w", err) } } } _, _ = fmt.Fprint(w, "") return nil } func (e *StdEncoder) isByteArray(val interface{}) bool { _, ok := val.([]byte) return ok } func (e *StdEncoder) encodeInteger(w io.Writer, val int) error { _, err := fmt.Fprintf(w, "%d", val) return err } func (e *StdEncoder) encodeDouble(w io.Writer, val float64) error { _, err := fmt.Fprintf(w, "%f", val) return err } func (e *StdEncoder) encodeBoolean(w io.Writer, val bool) error { v := 0 if val { v = 1 } _, err := fmt.Fprintf(w, "%d", v) return err } func (e *StdEncoder) encodeString(w io.Writer, val string) error { _, _ = fmt.Fprint(w, "") if err := xml.EscapeText(w, []byte(val)); err != nil { return fmt.Errorf("failed to escape string: %w", err) } _, _ = fmt.Fprint(w, "") return nil } func (e *StdEncoder) encodeArray(w io.Writer, val interface{}) error { _, _ = fmt.Fprint(w, "") for i := 0; i < reflect.ValueOf(val).Len(); i++ { if err := e.encodeValue(w, reflect.ValueOf(val).Index(i).Interface()); err != nil { return fmt.Errorf("cannot encode array element at index %d: %w", i, err) } } _, _ = fmt.Fprint(w, "") return nil } func (e *StdEncoder) encodeStruct(w io.Writer, val interface{}) error { _, _ = fmt.Fprint(w, "") for i := 0; i < reflect.TypeOf(val).NumField(); i++ { field := reflect.ValueOf(val).Field(i) // Skip over unexported fields if !field.CanInterface() { continue } fieldType := reflect.TypeOf(val).Field(i) fieldName := fieldType.Name if fieldType.Tag.Get("xml") != "" { fieldName = fieldType.Tag.Get("xml") } _, _ = fmt.Fprintf(w, "%s", fieldName) if err := e.encodeValue(w, field.Interface()); err != nil { return fmt.Errorf("cannot encode value of struct field '%s': %w", fieldName, err) } _, _ = fmt.Fprint(w, "") } _, _ = fmt.Fprint(w, "") return nil } func (e *StdEncoder) encodeBase64(w io.Writer, val []byte) error { _, err := fmt.Fprintf(w, "%s", base64.StdEncoding.EncodeToString(val)) return err } func (e *StdEncoder) encodeTime(w io.Writer, val time.Time) error { _, err := fmt.Fprintf(w, "%s", val.Format(time.RFC3339)) return err }