PageRenderTime 203ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/Godeps/_workspace/src/github.com/huin/goupnp/soap/soap.go

https://gitlab.com/akomba/ether-bot-wallet
Go | 157 lines | 127 code | 18 blank | 12 comment | 28 complexity | 5da1e36f03638764665ea3201ec65f09 MD5 | raw file
  1. // Definition for the SOAP structure required for UPnP's SOAP usage.
  2. package soap
  3. import (
  4. "bytes"
  5. "encoding/xml"
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "net/url"
  10. "reflect"
  11. )
  12. const (
  13. soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
  14. soapPrefix = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
  15. soapSuffix = `</s:Body></s:Envelope>`
  16. )
  17. type SOAPClient struct {
  18. EndpointURL url.URL
  19. HTTPClient http.Client
  20. }
  21. func NewSOAPClient(endpointURL url.URL) *SOAPClient {
  22. return &SOAPClient{
  23. EndpointURL: endpointURL,
  24. }
  25. }
  26. // PerformSOAPAction makes a SOAP request, with the given action.
  27. // inAction and outAction must both be pointers to structs with string fields
  28. // only.
  29. func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
  30. requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
  31. if err != nil {
  32. return err
  33. }
  34. response, err := client.HTTPClient.Do(&http.Request{
  35. Method: "POST",
  36. URL: &client.EndpointURL,
  37. Header: http.Header{
  38. "SOAPACTION": []string{`"` + actionNamespace + "#" + actionName + `"`},
  39. "CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
  40. },
  41. Body: ioutil.NopCloser(bytes.NewBuffer(requestBytes)),
  42. // Set ContentLength to avoid chunked encoding - some servers might not support it.
  43. ContentLength: int64(len(requestBytes)),
  44. })
  45. if err != nil {
  46. return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", err)
  47. }
  48. defer response.Body.Close()
  49. if response.StatusCode != 200 {
  50. return fmt.Errorf("goupnp: SOAP request got HTTP %s", response.Status)
  51. }
  52. responseEnv := newSOAPEnvelope()
  53. decoder := xml.NewDecoder(response.Body)
  54. if err := decoder.Decode(responseEnv); err != nil {
  55. return fmt.Errorf("goupnp: error decoding response body: %v", err)
  56. }
  57. if responseEnv.Body.Fault != nil {
  58. return responseEnv.Body.Fault
  59. }
  60. if outAction != nil {
  61. if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
  62. return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", err, responseEnv.Body.RawAction)
  63. }
  64. }
  65. return nil
  66. }
  67. // newSOAPAction creates a soapEnvelope with the given action and arguments.
  68. func newSOAPEnvelope() *soapEnvelope {
  69. return &soapEnvelope{
  70. EncodingStyle: soapEncodingStyle,
  71. }
  72. }
  73. // encodeRequestAction is a hacky way to create an encoded SOAP envelope
  74. // containing the given action. Experiments with one router have shown that it
  75. // 500s for requests where the outer default xmlns is set to the SOAP
  76. // namespace, and then reassigning the default namespace within that to the
  77. // service namespace. Hand-coding the outer XML to work-around this.
  78. func encodeRequestAction(actionNamespace, actionName string, inAction interface{}) ([]byte, error) {
  79. requestBuf := new(bytes.Buffer)
  80. requestBuf.WriteString(soapPrefix)
  81. requestBuf.WriteString(`<u:`)
  82. xml.EscapeText(requestBuf, []byte(actionName))
  83. requestBuf.WriteString(` xmlns:u="`)
  84. xml.EscapeText(requestBuf, []byte(actionNamespace))
  85. requestBuf.WriteString(`">`)
  86. if inAction != nil {
  87. if err := encodeRequestArgs(requestBuf, inAction); err != nil {
  88. return nil, err
  89. }
  90. }
  91. requestBuf.WriteString(`</u:`)
  92. xml.EscapeText(requestBuf, []byte(actionName))
  93. requestBuf.WriteString(`>`)
  94. requestBuf.WriteString(soapSuffix)
  95. return requestBuf.Bytes(), nil
  96. }
  97. func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
  98. in := reflect.Indirect(reflect.ValueOf(inAction))
  99. if in.Kind() != reflect.Struct {
  100. return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", in.Type())
  101. }
  102. enc := xml.NewEncoder(w)
  103. nFields := in.NumField()
  104. inType := in.Type()
  105. for i := 0; i < nFields; i++ {
  106. field := inType.Field(i)
  107. argName := field.Name
  108. if nameOverride := field.Tag.Get("soap"); nameOverride != "" {
  109. argName = nameOverride
  110. }
  111. value := in.Field(i)
  112. if value.Kind() != reflect.String {
  113. return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
  114. }
  115. if err := enc.EncodeElement(value.Interface(), xml.StartElement{xml.Name{"", argName}, nil}); err != nil {
  116. return fmt.Errorf("goupnp: error encoding SOAP arg %q: %v", argName, err)
  117. }
  118. }
  119. enc.Flush()
  120. return nil
  121. }
  122. type soapEnvelope struct {
  123. XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
  124. EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
  125. Body soapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
  126. }
  127. type soapBody struct {
  128. Fault *SOAPFaultError `xml:"Fault"`
  129. RawAction []byte `xml:",innerxml"`
  130. }
  131. // SOAPFaultError implements error, and contains SOAP fault information.
  132. type SOAPFaultError struct {
  133. FaultCode string `xml:"faultcode"`
  134. FaultString string `xml:"faultstring"`
  135. Detail string `xml:"detail"`
  136. }
  137. func (err *SOAPFaultError) Error() string {
  138. return fmt.Sprintf("SOAP fault: %s", err.FaultString)
  139. }