diff options
| author | Shulhan <ms@kilabit.info> | 2021-05-19 11:25:23 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2021-05-19 11:25:23 +0700 |
| commit | 2738d63671f7f8f3c724ae7bd9ce1c60e915f38b (patch) | |
| tree | bfa553394b7ea40ec05bb22907011adfa9895014 | |
| parent | 466a6047cd24914e3e7486bf90c9e3733661e0a3 (diff) | |
| download | pakakeh.go-2738d63671f7f8f3c724ae7bd9ce1c60e915f38b.tar.xz | |
xmlrpc: refactoring the parser for decoding XML-RPC request
This changes,
* method to parse XML-RPC request,
* change the Member field Value type to pointer to Value,
* change the Request field Params type to slice of pointer of Value,
* change the Response field Param type to pointer to Value,
* rename the Value fields from Members to StructMembers and Values to
ArrayValues
| -rw-r--r-- | lib/xmlrpc/member.go | 9 | ||||
| -rw-r--r-- | lib/xmlrpc/request.go | 51 | ||||
| -rw-r--r-- | lib/xmlrpc/request_test.go | 119 | ||||
| -rw-r--r-- | lib/xmlrpc/response.go | 100 | ||||
| -rw-r--r-- | lib/xmlrpc/response_test.go | 82 | ||||
| -rw-r--r-- | lib/xmlrpc/value.go | 24 | ||||
| -rw-r--r-- | lib/xmlrpc/xml.go | 513 | ||||
| -rw-r--r-- | lib/xmlrpc/xmlrpc.go | 23 |
8 files changed, 636 insertions, 285 deletions
diff --git a/lib/xmlrpc/member.go b/lib/xmlrpc/member.go index 7041305f..f9845fc6 100644 --- a/lib/xmlrpc/member.go +++ b/lib/xmlrpc/member.go @@ -13,10 +13,13 @@ import ( // type Member struct { Name string - Value Value + Value *Value } func (m Member) String() string { - return fmt.Sprintf("<member><name>%s</name>%s</member>", m.Name, - m.Value.String()) + var val string + if m.Value != nil { + val = m.Value.String() + } + return fmt.Sprintf("<member><name>%s</name>%s</member>", m.Name, val) } diff --git a/lib/xmlrpc/request.go b/lib/xmlrpc/request.go index 7e173812..aaed6e53 100644 --- a/lib/xmlrpc/request.go +++ b/lib/xmlrpc/request.go @@ -6,12 +6,17 @@ package xmlrpc import ( "bytes" + "encoding/xml" "fmt" ) +// +// Request represent the XML-RPC request, including method name and optional +// parameters. +// type Request struct { MethodName string - Params []Value + Params []*Value } // @@ -20,7 +25,7 @@ type Request struct { func NewRequest(methodName string, params []interface{}) (req Request, err error) { req = Request{ MethodName: methodName, - Params: make([]Value, 0, len(params)), + Params: make([]*Value, 0, len(params)), } for _, p := range params { @@ -29,7 +34,7 @@ func NewRequest(methodName string, params []interface{}) (req Request, err error return req, fmt.Errorf("NewRequest: cannot convert parameter %v", p) } - req.Params = append(req.Params, *v) + req.Params = append(req.Params, v) } return req, nil @@ -58,3 +63,43 @@ func (req Request) MarshalText() (out []byte, err error) { return buf.Bytes(), nil } + +// +// UnmarshalText parse the XML request. +// +func (req *Request) UnmarshalText(text []byte) (err error) { + var ( + logp = "xmlrpc: Request" + dec = xml.NewDecoder(bytes.NewReader(text)) + hasParams bool + ) + + err = xmlBegin(dec) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + err = xmlMustStart(dec, elNameMethodCall) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + req.MethodName, err = xmlMustCData(dec, elNameMethodName) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + req.Params, hasParams, err = xmlParseParams(dec, elNameMethodCall) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + if hasParams { + err = xmlMustEnd(dec, elNameMethodCall) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + } + + return nil +} diff --git a/lib/xmlrpc/request_test.go b/lib/xmlrpc/request_test.go index 5af05d8c..fc4ad465 100644 --- a/lib/xmlrpc/request_test.go +++ b/lib/xmlrpc/request_test.go @@ -15,7 +15,7 @@ type testStruct struct { Y bool } -func TestRequest(t *testing.T) { +func TestRequest_MarshalText(t *testing.T) { cases := []struct { methodName string params []interface{} @@ -77,3 +77,120 @@ func TestRequest(t *testing.T) { test.Assert(t, "Pack", c.exp, string(got)) } } + +func TestRequest_UnmarshalText(t *testing.T) { + cases := []struct { + desc string + in string + exp *Request + expError string + }{{ + desc: "Multiple param", + in: `<?xml version="1.0"?> + <methodCall> + <methodName>method.name</methodName> + <params> + <param> + <value> + <string> + param-string + </string> + </value> + </param> + <param> + <value> + <int> + 1 + </int> + </value> + </param> + </params> + </methodCall>`, + exp: &Request{ + MethodName: "method.name", + Params: []*Value{{ + Kind: String, + In: "param-string", + }, { + Kind: Integer, + In: int32(1), + }}, + }, + }, { + desc: "Param as struct", + in: `<?xml version="1.0"?> + <methodCall> + <methodName>test.struct</methodName> + <params> + <param> + <value> + <struct> + <member> + <name>X</name> + <value><int>1</int></value> + </member> + <member> + <name>Y</name> + <value><boolean>true</boolean></value> + </member> + </struct> + </value> + </param> + </params> + </methodCall>`, + exp: &Request{ + MethodName: "test.struct", + Params: []*Value{{ + Kind: Struct, + StructMembers: []*Member{{ + Name: "X", + Value: &Value{ + Kind: Integer, + In: int32(1), + }, + }, { + Name: "Y", + Value: &Value{ + Kind: Boolean, + In: true, + }, + }}, + }}, + }, + }, { + desc: "Param as array", + in: `<?xml version="1.0"?> + <methodCall><methodName>test.array</methodName> + <params><param><value><array><data> + <value><string>a</string></value> + <value><string>b</string></value> + </data></array></value></param></params> + </methodCall>`, + exp: &Request{ + MethodName: "test.array", + Params: []*Value{{ + Kind: Array, + ArrayValues: []*Value{{ + Kind: String, + In: "a", + }, { + Kind: String, + In: "b", + }}, + }}, + }, + }} + + for _, c := range cases { + t.Logf(c.desc) + + got := &Request{} + err := got.UnmarshalText([]byte(c.in)) + if err != nil { + test.Assert(t, "Unmarshal", c.expError, err.Error()) + continue + } + + test.Assert(t, "Unmarshal", c.exp, got) + } +} diff --git a/lib/xmlrpc/response.go b/lib/xmlrpc/response.go index 94cbd3b4..1e0343de 100644 --- a/lib/xmlrpc/response.go +++ b/lib/xmlrpc/response.go @@ -11,67 +11,67 @@ import ( ) type Response struct { - Param Value + Param *Value FaultMessage string FaultCode int32 IsFault bool } func (resp *Response) UnmarshalText(text []byte) (err error) { - dec := xml.NewDecoder(bytes.NewReader(text)) + var ( + logp = "xmlrpc: Response" + dec = xml.NewDecoder(bytes.NewReader(text)) + ) err = xmlBegin(dec) if err != nil { - return fmt.Errorf("UnmarshalText: %w", err) + return fmt.Errorf("%s: %w", logp, err) } - el, _, err := xmlNext(dec) + err = xmlMustStart(dec, elNameMethodResponse) if err != nil { - return fmt.Errorf("UnmarshalText: %w", err) - } - if el.Name.Local != elNameMethodResponse { - return fmt.Errorf("UnmarshalText: expecting '<%s>' got %v", - elNameMethodResponse, el.Name.Local) + return fmt.Errorf("%s: %w", logp, err) } - el, _, err = xmlNext(dec) + token, err := dec.Token() if err != nil { - return fmt.Errorf("UnmarshalText: %w", err) + return fmt.Errorf("%s: %w", logp, err) } - switch el.Name.Local { - case elNameFault: - err = resp.unmarshalFault(dec) - if err != nil { - return fmt.Errorf("UnmarshalText: %w", err) - } + found := false + for !found { + switch tok := token.(type) { + case xml.StartElement: + switch tok.Name.Local { + case elNameFault: + err = resp.unmarshalFault(dec) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + found = true - case elNameParams: - el, _, err = xmlNext(dec) - if err != nil { - return fmt.Errorf("UnmarshalText: %w", err) - } - if el.Name.Local != elNameParam { - return fmt.Errorf("UnmarshalText: expecting '<%s>' got %v", - elNameParam, el) - } + case elNameParams: + resp.Param, err = xmlParseParam(dec, elNameParams) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + found = true - el, _, err = xmlNext(dec) - if err != nil { - return fmt.Errorf("UnmarshalText: %w", err) - } - if el.Name.Local != elNameValue { - return fmt.Errorf("UnmarshalText: expecting '<%s>' got %v", - elNameValue, el) - } + default: + return fmt.Errorf("%s: expecting <params> or <fault> got <%s>", + logp, tok.Name.Local) + } + + case xml.Comment, xml.CharData: + token, err = dec.Token() + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } - resp.Param, err = xmlParseScalarValue(dec) - if err != nil { - return fmt.Errorf("UnmarshalText: %w", err) + default: + return fmt.Errorf("%s: expecting <params> or <fault>, got token %T %+v", + logp, token, tok) } - default: - return fmt.Errorf("UnmarshalText: expecting '<params>' or '<fault>' got %v", - el.Name.Local) } return nil @@ -83,25 +83,7 @@ func (resp *Response) UnmarshalText(text []byte) (err error) { func (resp *Response) unmarshalFault(dec *xml.Decoder) (err error) { resp.IsFault = true - el, _, err := xmlNext(dec) - if err != nil { - return fmt.Errorf("unmarshalFault: %w", err) - } - if el.Name.Local != elNameValue { - return fmt.Errorf("expecting '<%s>' got %v", elNameValue, - el.Name.Local) - } - - el, _, err = xmlNext(dec) - if err != nil { - return fmt.Errorf("unmarshalFault: %w", err) - } - if el.Name.Local != typeNameStruct { - return fmt.Errorf("unmarshalFault: expecting '<%s>' got %v", - typeNameStruct, el.Name.Local) - } - - v, err := xmlParseStruct(dec) + v, err := xmlParseValue(dec, elNameFault) if err != nil { return fmt.Errorf("unmarshalFault: %w", err) } diff --git a/lib/xmlrpc/response_test.go b/lib/xmlrpc/response_test.go index 04e97913..6a6f9e0f 100644 --- a/lib/xmlrpc/response_test.go +++ b/lib/xmlrpc/response_test.go @@ -18,15 +18,15 @@ func TestResponse(t *testing.T) { }{{ desc: "Normal response with string", text: `<?xml version="1.0"?> -<methodResponse> - <params> - <param> - <value><string>West Sumatra</string></value> - </param> - </params> -</methodResponse>`, + <methodResponse> + <params> + <param> + <value><string>West Sumatra</string></value> + </param> + </params> + </methodResponse>`, exp: Response{ - Param: Value{ + Param: &Value{ Kind: String, In: "West Sumatra", }, @@ -34,22 +34,22 @@ func TestResponse(t *testing.T) { }, { desc: "Faulty response", text: `<?xml version="1.0"?> -<methodResponse> - <fault> - <value> - <struct> - <member> - <name>faultCode</name> - <value><int>4</int></value> - </member> - <member> - <name>faultString</name> - <value><string>Too many parameters.</string></value> - </member> - </struct> - </value> - </fault> -</methodResponse>`, + <methodResponse> + <fault> + <value> + <struct> + <member> + <name>faultCode</name> + <value><int>4</int></value> + </member> + <member> + <name>faultString</name> + <value><string>Too many parameters.</string></value> + </member> + </struct> + </value> + </fault> + </methodResponse>`, exp: Response{ FaultCode: 4, FaultMessage: "Too many parameters.", @@ -58,25 +58,25 @@ func TestResponse(t *testing.T) { }, { desc: "Response with array", text: `<?xml version="1.0"?> -<methodResponse> - <params> - <param> - <value> - <array> - <data> - <value><string>North Sumatra</string></value> - <value><string>West Sumatra</string></value> - <value><string>South Sumatra</string></value> - </data> - </array> - </value> - </param> - </params> -</methodResponse>`, + <methodResponse> + <params> + <param> + <value> + <array> + <data> + <value><string>North Sumatra</string></value> + <value><string>West Sumatra</string></value> + <value><string>South Sumatra</string></value> + </data> + </array> + </value> + </param> + </params> + </methodResponse>`, exp: Response{ - Param: Value{ + Param: &Value{ Kind: Array, - Values: []Value{{ + ArrayValues: []*Value{{ Kind: String, In: "North Sumatra", }, { diff --git a/lib/xmlrpc/value.go b/lib/xmlrpc/value.go index e61bbb60..b2c37d12 100644 --- a/lib/xmlrpc/value.go +++ b/lib/xmlrpc/value.go @@ -18,9 +18,9 @@ type Value struct { // In contains scalar value for Base64, Boolean, Double, Integer, // String, and DateTime. // It would be nil for Kind of Array and Struct. - In interface{} - Members []Member // List of struct's member. - Values []Value // List of array's items. + In interface{} + StructMembers []*Member // List of struct's member. + ArrayValues []*Value // List of array's items. } // @@ -74,7 +74,7 @@ func NewValue(in interface{}) (out *Value) { case reflect.Struct: out.Kind = Struct for x := 0; x < reft.NumField(); x++ { - m := Member{} + m := &Member{} field := reft.Field(x) tag := field.Tag.Get(tagXML) @@ -86,8 +86,8 @@ func NewValue(in interface{}) (out *Value) { v := NewValue(refv.Field(x).Interface()) if v != nil { - m.Value = *v - out.Members = append(out.Members, m) + m.Value = v + out.StructMembers = append(out.StructMembers, m) } } @@ -96,7 +96,7 @@ func NewValue(in interface{}) (out *Value) { for x := 0; x < refv.Len(); x++ { v := NewValue(refv.Index(x).Interface()) if v != nil { - out.Values = append(out.Values, *v) + out.ArrayValues = append(out.ArrayValues, v) } } @@ -114,7 +114,7 @@ func NewValue(in interface{}) (out *Value) { // GetFieldAsFloat get struct's field value by name as float64. // func (v Value) GetFieldAsFloat(key string) float64 { - for _, m := range v.Members { + for _, m := range v.StructMembers { if m.Name == key { s, _ := m.Value.In.(float64) return s @@ -128,7 +128,7 @@ func (v Value) GetFieldAsFloat(key string) float64 { // GetFieldAsInteger get struct's field value by name as int32. // func (v Value) GetFieldAsInteger(key string) int32 { - for _, m := range v.Members { + for _, m := range v.StructMembers { if m.Name == key { s, _ := m.Value.In.(int32) return s @@ -142,7 +142,7 @@ func (v Value) GetFieldAsInteger(key string) int32 { // GetFieldAsString get struct's field value by name as string. // func (v Value) GetFieldAsString(key string) string { - for _, m := range v.Members { + for _, m := range v.StructMembers { if m.Name == key { s, _ := m.Value.In.(string) return s @@ -173,13 +173,13 @@ func (v Value) String() string { fmt.Fprintf(&buf, "<base64>%s</base64>", v.In.(string)) case Struct: buf.WriteString("<struct>") - for _, member := range v.Members { + for _, member := range v.StructMembers { buf.WriteString(member.String()) } buf.WriteString("</struct>") case Array: buf.WriteString("<array><data>") - for _, val := range v.Values { + for _, val := range v.ArrayValues { fmt.Fprintf(&buf, "%s", val.String()) } buf.WriteString("</data></array>") diff --git a/lib/xmlrpc/xml.go b/lib/xmlrpc/xml.go index 361d1235..bf06515a 100644 --- a/lib/xmlrpc/xml.go +++ b/lib/xmlrpc/xml.go @@ -12,261 +12,462 @@ import ( "time" ) -func xmlBegin(dec xml.TokenReader) (err error) { +func xmlBegin(dec *xml.Decoder) (err error) { token, err := dec.Token() if err != nil { - return fmt.Errorf("xmlBegin: %w", err) + return err } - el, ok := token.(xml.ProcInst) + _, ok := token.(xml.ProcInst) if !ok { - return fmt.Errorf("xmlBegin: expecting xml.ProcInst got %v", - el) + return fmt.Errorf(`xmlBegin: expecting <?xml version="1.0"?> got %T %+v`, token, token) } - token, err = dec.Token() + return nil +} + +// +// xmlMustCData parse the CDATA inside the tag. +// +func xmlMustCData(dec *xml.Decoder, tag string) (cdata string, err error) { + err = xmlMustStart(dec, tag) if err != nil { - return fmt.Errorf("xmlBegin: %w", err) + return "", err } - _, ok = token.(xml.CharData) - if !ok { - return fmt.Errorf("xmlNeBegin: expecting xml.CharData got %v", el) + token, err := dec.Token() + if err != nil { + return "", fmt.Errorf("expecting CDATA, got an error %w", err) } - return nil -} - -func xmlNext(dec xml.TokenReader) (start xml.StartElement, end xml.EndElement, err error) { - var ( - token xml.Token - ok bool - ) + found := false + for !found { + switch tok := token.(type) { + case xml.CharData: + cdata = string(tok) + found = true - // Skip any elements that is not StartElement or EndElement ... - for { - token, err = dec.Token() - if err != nil { - return start, end, fmt.Errorf("xmlNext: %w", err) - } + case xml.EndElement: + if tok.Name.Local != tag { + return "", fmt.Errorf("expecting </%s>, got token %+v", tag, tok) + } + return "", nil - end, ok = token.(xml.EndElement) - if ok { - end.Name.Local = strings.ToLower(end.Name.Local) - break + default: + token, err = dec.Token() + if err != nil { + return "", fmt.Errorf("expecting CDATA, got an error %w", err) + } } + } - start, ok = token.(xml.StartElement) - if ok { - start.Name.Local = strings.ToLower(start.Name.Local) - break - } + err = xmlMustEnd(dec, tag) + if err != nil { + return "", err } - return start, end, nil + return cdata, nil } -func xmlData(dec xml.TokenReader) (data string, err error) { +// +// xmlMustStart parse the first XML element that must start with passed +// openTag. +// +func xmlMustStart(dec *xml.Decoder, tag string) (err error) { token, err := dec.Token() if err != nil { - return "", fmt.Errorf("xmlData: %w", err) + return fmt.Errorf("expecting <%s>, got an error %w", tag, err) } - el, ok := token.(xml.CharData) - if !ok { - return "", fmt.Errorf("xmlData: expecting xml.CharData got %v", el) + found := false + for !found { + switch tok := token.(type) { + case xml.StartElement: + if tok.Name.Local != tag { + return fmt.Errorf("expecting <%s> got token %+v", tag, tok) + } + found = true + + case xml.Comment, xml.CharData: + token, err = dec.Token() + if err != nil { + return fmt.Errorf("expecting <%s>, got an error %w", tag, err) + } + + default: + return fmt.Errorf("expecting <%s>, got token %T", tag, tok) + } + } + return nil +} + +// +// xmlMustEnd parse the next XML element that must be a closed tag. +// +func xmlMustEnd(dec *xml.Decoder, tag string) (err error) { + token, err := dec.Token() + if err != nil { + return fmt.Errorf("expecting </%s>, got an error %w", tag, err) } - return string(el), nil + found := false + for !found { + switch tok := token.(type) { + case xml.EndElement: + if tok.Name.Local != tag { + return fmt.Errorf("expecting </%s>, got token </%s>", tag, tok.Name.Local) + } + found = true + + case xml.Comment, xml.CharData: + token, err = dec.Token() + if err != nil { + return fmt.Errorf("expecting </%s>, got an error %w", tag, err) + } + + default: + return fmt.Errorf("expecting </%s>, got an error %w", tag, err) + } + } + return nil } -func xmlParseScalarValue(dec *xml.Decoder) (v Value, err error) { - // Get the element type. - el, _, err := xmlNext(dec) +func xmlStart(dec *xml.Decoder, openTag, closeTag string) (isOpen bool, err error) { + token, err := dec.Token() if err != nil { - return v, fmt.Errorf("xmlParseScalarValue: %w", err) + return false, fmt.Errorf("expecting <%s>, got an error %w", openTag, err) } - var data string + for !isOpen { + switch tok := token.(type) { + case xml.StartElement: + if tok.Name.Local != openTag { + return false, fmt.Errorf("expecting <%s>, got <%s>", + openTag, tok.Name.Local) + } + isOpen = true - switch el.Name.Local { - case typeNameBoolean, typeNameBase64, typeNameDateTime, - typeNameDouble, typeNameInteger, typeNameInteger4, - typeNameString: + case xml.EndElement: + if tok.Name.Local == closeTag { + return false, nil + } + return false, fmt.Errorf("expecting </%s>, got </%s>", + closeTag, tok.Name.Local) - data, err = xmlData(dec) - if err != nil { - return v, fmt.Errorf("xmlParseScalarValue: %w", err) + case xml.CharData, xml.Comment: + token, err = dec.Token() + if err != nil { + return false, fmt.Errorf("expecting <%s>, got an error %w", + openTag, err) + } + default: + return false, fmt.Errorf("expecting <%s>, got token %T %+v", + openTag, token, tok) } } - switch el.Name.Local { - case typeNameArray: - v, err = xmlParseArray(dec) + return true, nil +} - case typeNameBoolean: - v.Kind = Boolean - if strings.ToLower(data) == boolTrue { - v.In = true - } else { - v.In = false +// +// xmlParseParams parse the optional <params> elements. +// +func xmlParseParams(dec *xml.Decoder, closeTag string) (params []*Value, hasParams bool, err error) { + isOpen, err := xmlStart(dec, elNameParams, closeTag) + if err != nil { + return nil, false, err + } + if !isOpen { + return nil, false, nil + } + + for { + param, err := xmlParseParam(dec, elNameParams) + if err != nil { + return nil, hasParams, err + } + if param == nil { + break } + params = append(params, param) + } - case typeNameBase64, typeNameString: - v.Kind = String - v.In = data + return params, true, nil +} - case typeNameDateTime: - v.Kind = DateTime - v.In, err = time.Parse(timeLayoutISO8601, data) +// +// xmlParseParam parse the <param> element. +// +func xmlParseParam(dec *xml.Decoder, closeTag string) (param *Value, err error) { + isOpen, err := xmlStart(dec, elNameParam, closeTag) + if err != nil { + return nil, err + } + if !isOpen { + return nil, nil + } - case typeNameDouble: - v.Kind = Double - v.In, err = strconv.ParseFloat(data, 10) + param, err = xmlParseValue(dec, elNameParam) + if err != nil { + return nil, err + } - case typeNameInteger, typeNameInteger4: - var i64 int64 - v.Kind = Integer - i64, err = strconv.ParseInt(data, 10, 64) - v.In = int32(i64) + if param != nil { + err = xmlMustEnd(dec, elNameParam) + if err != nil { + return nil, err + } + } - case typeNameStruct: - v, err = xmlParseStruct(dec) + return param, nil +} - default: - return v, fmt.Errorf("xmlParseScalarValue: unknown scalar type %q", - el.Name.Local) +func xmlParseValue(dec *xml.Decoder, closeTag string) (param *Value, err error) { + var ( + cdata string + ) + + isOpen, err := xmlStart(dec, elNameValue, closeTag) + if err != nil { + return nil, err } + if !isOpen { + return nil, nil + } + + param = &Value{} + + token, err := dec.Token() if err != nil { - return v, fmt.Errorf("xmlParseScalarValue: %w", err) + return nil, fmt.Errorf("expecting CDATA, got an error %w", err) } - // Skip element type closed tag ... - switch el.Name.Local { - case typeNameBoolean, typeNameBase64, typeNameDateTime, - typeNameDouble, typeNameInteger, typeNameInteger4, - typeNameString: + found := false + for !found { + switch tok := token.(type) { + case xml.StartElement: + switch tok.Name.Local { + case typeNameArray: + param, err = xmlParseArray(dec) + if err != nil { + return nil, err + } + err = xmlMustEnd(dec, elNameValue) + if err != nil { + return nil, err + } + return param, nil + + case typeNameStruct: + param, err = xmlParseStruct(dec) + if err != nil { + return nil, err + } + err = xmlMustEnd(dec, elNameValue) + if err != nil { + return nil, err + } + return param, nil + + case typeNameBase64: + param.Kind = Base64 + param.In = "" + case typeNameBoolean: + param.Kind = Boolean + param.In = false + case typeNameDateTime: + param.Kind = DateTime + param.In = time.Time{} + case typeNameDouble: + param.Kind = Double + param.In = float64(0) + case typeNameInteger, typeNameInteger4: + param.Kind = Integer + param.In = int32(0) + case typeNameString: + param.Kind = String + param.In = "" + + default: + return nil, fmt.Errorf("unknown type %s", tok.Name.Local) + } + + cdata, err = xmlParseCData(dec, tok.Name.Local) + if err != nil { + return nil, err + } + found = true + + case xml.EndElement: + if tok.Name.Local != elNameValue { + return nil, fmt.Errorf("expecting </value>, got token </%s>", tok.Name.Local) + } + param.Kind = String + param.In = "" + return param, nil + + case xml.CharData: + cdata = strings.TrimSpace(string(tok)) + if len(cdata) > 0 { + found = true + } else { + token, err = dec.Token() + if err != nil { + return nil, fmt.Errorf("expecting CDATA, got an error %w", err) + } + } - err = dec.Skip() + default: + return nil, fmt.Errorf("expecting <value>, got token %+v", tok) + } + } + + switch param.Kind { + case Unset, String, Base64: + param.Kind = String + param.In = cdata + case Boolean: + cdata = strings.ToLower(cdata) + if cdata == "1" || cdata == "true" { + param.In = true + } + case DateTime: + param.In, err = time.Parse(timeLayoutISO8601, cdata) + if err != nil { + return nil, fmt.Errorf("invalid dateTime value %s: %w", cdata, err) + } + case Double: + param.In, err = strconv.ParseFloat(cdata, 64) + if err != nil { + return nil, fmt.Errorf("invalid double value %s: %w", cdata, err) + } + case Integer: + i64, err := strconv.ParseInt(cdata, 10, 32) if err != nil { - return v, fmt.Errorf("xmlParseScalarValue: %w", err) + return nil, fmt.Errorf("invalid integer value %s: %w", cdata, err) } + param.In = int32(i64) } - // Skip </value> ... - err = dec.Skip() + err = xmlMustEnd(dec, elNameValue) if err != nil { - return v, fmt.Errorf("xmlParseScalarValue: %w", err) + return nil, err } - return v, nil + return param, nil } -func xmlParseArray(dec *xml.Decoder) (arr Value, err error) { - el, _, err := xmlNext(dec) +func xmlParseCData(dec *xml.Decoder, closeTag string) (cdata string, err error) { + token, err := dec.Token() if err != nil { - return arr, fmt.Errorf("xmlParseArray: %w", err) + return "", fmt.Errorf("expecting CDATA, got error %w", err) } - if el.Name.Local != elNameData { - return arr, fmt.Errorf("xmlParseArray: expecting <%s> got %v", - elNameData, el) + + found := false + for !found { + switch tok := token.(type) { + case xml.StartElement: + return "", fmt.Errorf("expecting CDATA, got <%s>", tok.Name.Local) + + case xml.EndElement: + if tok.Name.Local != closeTag { + return "", fmt.Errorf("expecting </%s>, got </%s>", closeTag, tok.Name.Local) + } + return "", nil + + case xml.CharData: + cdata = strings.TrimSpace(string(tok)) + found = true + + case xml.Comment: + token, err = dec.Token() + if err != nil { + return "", fmt.Errorf("expecting CDATA, got an error %w", err) + } + default: + return "", fmt.Errorf("expecting CDATA, got %T %+v", token, tok) + } + } + + err = xmlMustEnd(dec, closeTag) + if err != nil { + return cdata, err } + return cdata, nil +} + +func xmlParseArray(dec *xml.Decoder) (arr *Value, err error) { + arr = &Value{} arr.Kind = Array + err = xmlMustStart(dec, elNameData) + if err != nil { + return nil, err + } + for { - start, end, err := xmlNext(dec) + v, err := xmlParseValue(dec, elNameData) if err != nil { - return arr, fmt.Errorf("xmlParseArray: %w", err) + return nil, err } - if end.Name.Local == elNameData { + if v == nil { break } - if start.Name.Local != elNameValue { - return arr, fmt.Errorf("xmlParseArray: expecting '<%s>' got %v", - elNameValue, el) - } - - v, err := xmlParseScalarValue(dec) - if err != nil { - return v, fmt.Errorf("xmlParseArray: %w", err) - } + arr.ArrayValues = append(arr.ArrayValues, v) + } - arr.Values = append(arr.Values, v) + err = xmlMustEnd(dec, typeNameArray) + if err != nil { + return nil, err } return arr, nil } -func xmlParseStruct(dec *xml.Decoder) (v Value, err error) { +func xmlParseStruct(dec *xml.Decoder) (v *Value, err error) { + v = &Value{} v.Kind = Struct for { - start, end, err := xmlNext(dec) + member, err := xmlParseStructMember(dec) if err != nil { - return v, fmt.Errorf("xmlParseStruct: %w", err) + return nil, err } - if end.Name.Local == typeNameStruct { + if member == nil { break } - if start.Name.Local != elNameMember { - return v, fmt.Errorf("xmlParseStruct: expecting '<%s>' got %v", - elNameMember, start.Name.Local) - } - - m, err := xmlParseStructMember(dec) - if err != nil { - return v, fmt.Errorf("xmlParseStruct: %w", err) - } - - v.Members = append(v.Members, m) - - // skip '</member>'... - err = dec.Skip() - if err != nil { - return v, fmt.Errorf("xmlParseStruct: %w", err) - } + v.StructMembers = append(v.StructMembers, member) } return v, nil } -func xmlParseStructMember(dec *xml.Decoder) (m Member, err error) { - // member's <name> ... - el, _, err := xmlNext(dec) +func xmlParseStructMember(dec *xml.Decoder) (m *Member, err error) { + isOpen, err := xmlStart(dec, elNameMember, typeNameStruct) if err != nil { - return m, fmt.Errorf("xmlParseStructMember: %w", err) + return nil, err } - if el.Name.Local != elNameName { - return m, fmt.Errorf("xmlParseStructMember: expecting '<%s>' got %v", - elNameName, el.Name.Local) + if !isOpen { + return nil, nil } - data, err := xmlData(dec) + cdata, err := xmlMustCData(dec, elNameName) if err != nil { - return m, fmt.Errorf("xmlParseStructMember: %w", err) + return nil, err } - m.Name = strings.ToLower(data) - - // skip </name> ... - err = dec.Skip() - if err != nil { - return m, fmt.Errorf("xmlParseStructMember: %w", err) - } + m = &Member{} + m.Name = strings.TrimSpace(cdata) - el, _, err = xmlNext(dec) + m.Value, err = xmlParseValue(dec, elNameMember) if err != nil { - return m, fmt.Errorf("xmlParseStructMember: %w", err) - } - if el.Name.Local != elNameValue { - return m, fmt.Errorf("xmlParseStructMember: expecting '<%s>' got %v", - elNameValue, el.Name.Local) + return nil, err } - m.Value, err = xmlParseScalarValue(dec) + err = xmlMustEnd(dec, elNameMember) if err != nil { - return m, fmt.Errorf("xmlParseStructMember: %w", err) + return nil, err } return m, nil diff --git a/lib/xmlrpc/xmlrpc.go b/lib/xmlrpc/xmlrpc.go index 20e38a63..4c6bf0b3 100644 --- a/lib/xmlrpc/xmlrpc.go +++ b/lib/xmlrpc/xmlrpc.go @@ -36,17 +36,20 @@ const ( ) const ( - elNameData = "data" - elNameFault = "fault" - elNameMember = "member" - elNameMethodResponse = "methodresponse" - elNameName = "name" - elNameParam = "param" - elNameParams = "params" - elNameValue = "value" + elNameMethodCall = "methodCall" + elNameMethodName = "methodName" + elNameMethodResponse = "methodResponse" - memberNameFaultCode = "faultcode" - memberNameFaultString = "faultstring" + elNameData = "data" + elNameFault = "fault" + elNameMember = "member" + elNameName = "name" + elNameParam = "param" + elNameParams = "params" + elNameValue = "value" + + memberNameFaultCode = "faultCode" + memberNameFaultString = "faultString" typeNameArray = "array" typeNameBase64 = "base64" |
