Add workaround for SOAP server XML decoding limitations.

This commit is contained in:
John Beisley 2017-11-07 18:19:10 -05:00
parent 29fc54a4c0
commit 991e174e2e
2 changed files with 65 additions and 3 deletions

View File

@ -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 "&lt;"
case ">":
return "&gt;"
case "&":
return "&amp;"
}
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"`

View File

@ -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>&", "&lt;foo&gt;&amp;"},
{"\"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)
}
})
}
}