summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2021-05-19 11:25:23 +0700
committerShulhan <ms@kilabit.info>2021-05-19 11:25:23 +0700
commit2738d63671f7f8f3c724ae7bd9ce1c60e915f38b (patch)
treebfa553394b7ea40ec05bb22907011adfa9895014
parent466a6047cd24914e3e7486bf90c9e3733661e0a3 (diff)
downloadpakakeh.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.go9
-rw-r--r--lib/xmlrpc/request.go51
-rw-r--r--lib/xmlrpc/request_test.go119
-rw-r--r--lib/xmlrpc/response.go100
-rw-r--r--lib/xmlrpc/response_test.go82
-rw-r--r--lib/xmlrpc/value.go24
-rw-r--r--lib/xmlrpc/xml.go513
-rw-r--r--lib/xmlrpc/xmlrpc.go23
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"