Correct the encoding of SOAP action arguments.
Also adds a test for this, and the decoding of the response arguments.
This commit is contained in:
		
							
								
								
									
										40
									
								
								soap/soap.go
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								soap/soap.go
									
									
									
									
									
								
							@@ -9,11 +9,12 @@ import (
 | 
				
			|||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
 | 
						soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
 | 
				
			||||||
	soapPrefix        = `<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
 | 
						soapPrefix        = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
 | 
				
			||||||
	soapSuffix        = `</s:Body></s:Envelope>`
 | 
						soapSuffix        = `</s:Body></s:Envelope>`
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,6 +30,8 @@ func NewSOAPClient(endpointURL url.URL) *SOAPClient {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PerformSOAPAction makes a SOAP request, with the given action.
 | 
					// PerformSOAPAction makes a SOAP request, with the given action.
 | 
				
			||||||
 | 
					// inAction and outAction must both be pointers to structs with string fields
 | 
				
			||||||
 | 
					// only.
 | 
				
			||||||
func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
 | 
					func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
 | 
				
			||||||
	requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
 | 
						requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -47,7 +50,7 @@ func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAc
 | 
				
			|||||||
		ContentLength: int64(len(requestBytes)),
 | 
							ContentLength: int64(len(requestBytes)),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer response.Body.Close()
 | 
						defer response.Body.Close()
 | 
				
			||||||
	if response.StatusCode != 200 {
 | 
						if response.StatusCode != 200 {
 | 
				
			||||||
@@ -57,7 +60,7 @@ func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAc
 | 
				
			|||||||
	responseEnv := newSOAPEnvelope()
 | 
						responseEnv := newSOAPEnvelope()
 | 
				
			||||||
	decoder := xml.NewDecoder(response.Body)
 | 
						decoder := xml.NewDecoder(response.Body)
 | 
				
			||||||
	if err := decoder.Decode(responseEnv); err != nil {
 | 
						if err := decoder.Decode(responseEnv); err != nil {
 | 
				
			||||||
		return err
 | 
							return fmt.Errorf("goupnp: error decoding response body: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if responseEnv.Body.Fault != nil {
 | 
						if responseEnv.Body.Fault != nil {
 | 
				
			||||||
@@ -66,7 +69,7 @@ func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAc
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if outAction != nil {
 | 
						if outAction != nil {
 | 
				
			||||||
		if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
 | 
							if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
 | 
				
			||||||
			return err
 | 
								return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", err, responseEnv.Body.RawAction)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -94,8 +97,7 @@ func encodeRequestAction(actionNamespace, actionName string, inAction interface{
 | 
				
			|||||||
	xml.EscapeText(requestBuf, []byte(actionNamespace))
 | 
						xml.EscapeText(requestBuf, []byte(actionNamespace))
 | 
				
			||||||
	requestBuf.WriteString(`">`)
 | 
						requestBuf.WriteString(`">`)
 | 
				
			||||||
	if inAction != nil {
 | 
						if inAction != nil {
 | 
				
			||||||
		requestEnc := xml.NewEncoder(requestBuf)
 | 
							if err := encodeRequestArgs(requestBuf, inAction); err != nil {
 | 
				
			||||||
		if err := requestEnc.Encode(inAction); err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -106,6 +108,32 @@ func encodeRequestAction(actionNamespace, actionName string, inAction interface{
 | 
				
			|||||||
	return requestBuf.Bytes(), nil
 | 
						return requestBuf.Bytes(), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
 | 
				
			||||||
 | 
						in := reflect.Indirect(reflect.ValueOf(inAction))
 | 
				
			||||||
 | 
						if in.Kind() != reflect.Struct {
 | 
				
			||||||
 | 
							return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", in.Type())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						enc := xml.NewEncoder(w)
 | 
				
			||||||
 | 
						nFields := in.NumField()
 | 
				
			||||||
 | 
						inType := in.Type()
 | 
				
			||||||
 | 
						for i := 0; i < nFields; i++ {
 | 
				
			||||||
 | 
							field := inType.Field(i)
 | 
				
			||||||
 | 
							argName := field.Name
 | 
				
			||||||
 | 
							if nameOverride := field.Tag.Get("soap"); nameOverride != "" {
 | 
				
			||||||
 | 
								argName = nameOverride
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							value := in.Field(i)
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						enc.Flush()
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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"`
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										85
									
								
								soap/soap_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								soap/soap_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					package soap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type capturingRoundTripper struct {
 | 
				
			||||||
 | 
						err         error
 | 
				
			||||||
 | 
						resp        *http.Response
 | 
				
			||||||
 | 
						capturedReq *http.Request
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rt *capturingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
 | 
				
			||||||
 | 
						rt.capturedReq = req
 | 
				
			||||||
 | 
						return rt.resp, rt.err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestActionInputs(t *testing.T) {
 | 
				
			||||||
 | 
						url, err := url.Parse("http://example.com/soap")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rt := &capturingRoundTripper{
 | 
				
			||||||
 | 
							err: nil,
 | 
				
			||||||
 | 
							resp: &http.Response{
 | 
				
			||||||
 | 
								StatusCode: 200,
 | 
				
			||||||
 | 
								Body: ioutil.NopCloser(bytes.NewBufferString(`
 | 
				
			||||||
 | 
									<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 | 
				
			||||||
 | 
										<s:Body>
 | 
				
			||||||
 | 
											<u:myactionResponse xmlns:u="mynamespace">
 | 
				
			||||||
 | 
												<A>valueA</A>
 | 
				
			||||||
 | 
												<B>valueB</B>
 | 
				
			||||||
 | 
											</u:myactionResponse>
 | 
				
			||||||
 | 
										</s:Body>
 | 
				
			||||||
 | 
									</s:Envelope>
 | 
				
			||||||
 | 
								`)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client := SOAPClient{
 | 
				
			||||||
 | 
							EndpointURL: *url,
 | 
				
			||||||
 | 
							HTTPClient: http.Client{
 | 
				
			||||||
 | 
								Transport: rt,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type In struct {
 | 
				
			||||||
 | 
							Foo string
 | 
				
			||||||
 | 
							Bar string `soap:"bar"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						type Out struct {
 | 
				
			||||||
 | 
							A string
 | 
				
			||||||
 | 
							B string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						in := In{"foo", "bar"}
 | 
				
			||||||
 | 
						gotOut := Out{}
 | 
				
			||||||
 | 
						err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wantBody := (soapPrefix +
 | 
				
			||||||
 | 
							`<u:myaction xmlns:u="mynamespace">` +
 | 
				
			||||||
 | 
							`<Foo>foo</Foo>` +
 | 
				
			||||||
 | 
							`<bar>bar</bar>` +
 | 
				
			||||||
 | 
							`</u:myaction>` +
 | 
				
			||||||
 | 
							soapSuffix)
 | 
				
			||||||
 | 
						body, err := ioutil.ReadAll(rt.capturedReq.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						gotBody := string(body)
 | 
				
			||||||
 | 
						if wantBody != gotBody {
 | 
				
			||||||
 | 
							t.Errorf("Bad request body\nwant: %q\n got: %q", wantBody, gotBody)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wantOut := Out{"valueA", "valueB"}
 | 
				
			||||||
 | 
						if !reflect.DeepEqual(wantOut, gotOut) {
 | 
				
			||||||
 | 
							t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user