Add workaround for SOAP server XML decoding limitations.
This commit is contained in:
parent
29fc54a4c0
commit
991e174e2e
40
soap/soap.go
40
soap/soap.go
@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -126,14 +127,49 @@ func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
|
|||||||
if value.Kind() != reflect.String {
|
if value.Kind() != reflect.String {
|
||||||
return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
|
return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
|
||||||
}
|
}
|
||||||
if err := enc.EncodeElement(value.Interface(), xml.StartElement{xml.Name{"", argName}, nil}); err != nil {
|
elem := xml.StartElement{xml.Name{"", argName}, nil}
|
||||||
return fmt.Errorf("goupnp: error encoding SOAP arg %q: %v", argName, err)
|
if err := enc.EncodeToken(elem); err != nil {
|
||||||
|
return fmt.Errorf("goupnp: error encoding start element for SOAP arg %q: %v", argName, err)
|
||||||
|
}
|
||||||
|
if err := enc.Flush(); err != nil {
|
||||||
|
return fmt.Errorf("goupnp: error flushing start element for SOAP arg %q: %v", argName, err)
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(escapeXMLText(value.Interface().(string)))); err != nil {
|
||||||
|
return fmt.Errorf("goupnp: error writing value for SOAP arg %q: %v", argName, err)
|
||||||
|
}
|
||||||
|
if err := enc.EncodeToken(elem.End()); err != nil {
|
||||||
|
return fmt.Errorf("goupnp: error encoding end element for SOAP arg %q: %v", argName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enc.Flush()
|
enc.Flush()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var xmlCharRx = regexp.MustCompile("[<>&]")
|
||||||
|
|
||||||
|
// escapeXMLText is used by generated code to escape text in XML, but only
|
||||||
|
// escaping the characters `<`, `>`, and `&`.
|
||||||
|
//
|
||||||
|
// This is provided in order to work around SOAP server implementations that
|
||||||
|
// fail to decode XML correctly, specifically failing to decode `"`, `'`. Note
|
||||||
|
// that this can only be safely used for injecting into XML text, but not into
|
||||||
|
// attributes or other contexts.
|
||||||
|
func escapeXMLText(s string) string {
|
||||||
|
return xmlCharRx.ReplaceAllStringFunc(s, replaceEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceEntity(s string) string {
|
||||||
|
switch s {
|
||||||
|
case "<":
|
||||||
|
return "<"
|
||||||
|
case ">":
|
||||||
|
return ">"
|
||||||
|
case "&":
|
||||||
|
return "&"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
type soapEnvelope struct {
|
type soapEnvelope struct {
|
||||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||||
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
|
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
|
||||||
|
@ -21,6 +21,7 @@ func (rt *capturingRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestActionInputs(t *testing.T) {
|
func TestActionInputs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url, err := url.Parse("http://example.com/soap")
|
url, err := url.Parse("http://example.com/soap")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -51,12 +52,13 @@ func TestActionInputs(t *testing.T) {
|
|||||||
type In struct {
|
type In struct {
|
||||||
Foo string
|
Foo string
|
||||||
Bar string `soap:"bar"`
|
Bar string `soap:"bar"`
|
||||||
|
Baz string
|
||||||
}
|
}
|
||||||
type Out struct {
|
type Out struct {
|
||||||
A string
|
A string
|
||||||
B string
|
B string
|
||||||
}
|
}
|
||||||
in := In{"foo", "bar"}
|
in := In{"foo", "bar", "quoted=\"baz\""}
|
||||||
gotOut := Out{}
|
gotOut := Out{}
|
||||||
err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
|
err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,6 +69,7 @@ func TestActionInputs(t *testing.T) {
|
|||||||
`<u:myaction xmlns:u="mynamespace">` +
|
`<u:myaction xmlns:u="mynamespace">` +
|
||||||
`<Foo>foo</Foo>` +
|
`<Foo>foo</Foo>` +
|
||||||
`<bar>bar</bar>` +
|
`<bar>bar</bar>` +
|
||||||
|
`<Baz>quoted="baz"</Baz>` +
|
||||||
`</u:myaction>` +
|
`</u:myaction>` +
|
||||||
soapSuffix)
|
soapSuffix)
|
||||||
body, err := ioutil.ReadAll(rt.capturedReq.Body)
|
body, err := ioutil.ReadAll(rt.capturedReq.Body)
|
||||||
@ -83,3 +86,26 @@ func TestActionInputs(t *testing.T) {
|
|||||||
t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut)
|
t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestEscapeXMLText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"abc123", "abc123"},
|
||||||
|
{"<foo>&", "<foo>&"},
|
||||||
|
{"\"foo'", "\"foo'"},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.input, func(t *testing.T) {
|
||||||
|
got := escapeXMLText(test.input)
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf("want %q, got %q", test.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user