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/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -126,14 +127,49 @@ func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
|
||||
if value.Kind() != reflect.String {
|
||||
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 {
|
||||
return fmt.Errorf("goupnp: error encoding SOAP arg %q: %v", argName, err)
|
||||
elem := xml.StartElement{xml.Name{"", argName}, nil}
|
||||
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()
|
||||
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 {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||
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) {
|
||||
t.Parallel()
|
||||
url, err := url.Parse("http://example.com/soap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -51,12 +52,13 @@ func TestActionInputs(t *testing.T) {
|
||||
type In struct {
|
||||
Foo string
|
||||
Bar string `soap:"bar"`
|
||||
Baz string
|
||||
}
|
||||
type Out struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
in := In{"foo", "bar"}
|
||||
in := In{"foo", "bar", "quoted=\"baz\""}
|
||||
gotOut := Out{}
|
||||
err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
|
||||
if err != nil {
|
||||
@ -67,6 +69,7 @@ func TestActionInputs(t *testing.T) {
|
||||
`<u:myaction xmlns:u="mynamespace">` +
|
||||
`<Foo>foo</Foo>` +
|
||||
`<bar>bar</bar>` +
|
||||
`<Baz>quoted="baz"</Baz>` +
|
||||
`</u:myaction>` +
|
||||
soapSuffix)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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