-
Notifications
You must be signed in to change notification settings - Fork 56
/
soap.go
134 lines (109 loc) · 3.47 KB
/
soap.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package onvif
import (
"bytes"
"crypto/sha1"
"encoding/base64"
"errors"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"time"
"github.com/clbanning/mxj"
"github.com/satori/go.uuid"
)
var httpClient = &http.Client{Timeout: time.Second * 4}
// SOAP contains data for SOAP request
type SOAP struct {
Body string
XMLNs []string
User string
Password string
TokenAge time.Duration
}
// SendRequest sends SOAP request to xAddr
func (soap SOAP) SendRequest(xaddr string) (mxj.Map, error) {
// Create SOAP request
// Make sure URL valid and add authentication in xAddr
urlXAddr, err := url.Parse(xaddr)
if urlXAddr.User != nil && urlXAddr.User.Username() != "" {
soap.User = urlXAddr.User.Username()
soap.Password, _ = urlXAddr.User.Password()
}
if err != nil {
return nil, err
}
request := soap.createRequest()
// Make sure URL valid and add authentication in xAddr
//urlXAddr, err := url.Parse(xaddr)
if soap.User != "" {
urlXAddr.User = url.UserPassword(soap.User, soap.Password)
}
// Create HTTP request
buffer := bytes.NewBuffer([]byte(request))
req, err := http.NewRequest("POST", urlXAddr.String(), buffer)
req.Header.Set("Content-Type", "application/soap+xml")
req.Header.Set("Charset", "utf-8")
// Send request
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Read response body
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// Parse XML to map
mapXML, err := mxj.NewMapXml(responseBody)
if err != nil {
return nil, err
}
// Check if SOAP returns fault
fault, _ := mapXML.ValueForPathString("Envelope.Body.Fault.Reason.Text.#text")
if fault != "" {
return nil, errors.New(fault)
}
return mapXML, nil
}
func (soap SOAP) createRequest() string {
// Create request envelope
request := `<?xml version="1.0" encoding="UTF-8"?>`
request += `<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"`
// Set XML namespace
for _, namespace := range soap.XMLNs {
request += " " + namespace
}
request += ">"
// Set request header
if soap.User != "" {
request += "<s:Header>" + soap.createUserToken() + "</s:Header>"
}
// Set request body
request += "<s:Body>" + soap.Body + "</s:Body>"
// Close request envelope
request += "</s:Envelope>"
// Clean request
request = regexp.MustCompile(`\>\s+\<`).ReplaceAllString(request, "><")
request = regexp.MustCompile(`\s+`).ReplaceAllString(request, " ")
return request
}
func (soap SOAP) createUserToken() string {
nonce := uuid.NewV4().Bytes()
nonce64 := base64.StdEncoding.EncodeToString(nonce)
timestamp := time.Now().Add(soap.TokenAge).UTC().Format(time.RFC3339)
token := string(nonce) + timestamp + soap.Password
sha := sha1.New()
sha.Write([]byte(token))
shaToken := sha.Sum(nil)
shaDigest64 := base64.StdEncoding.EncodeToString(shaToken)
return `<Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<UsernameToken>
<Username>` + soap.User + `</Username>
<Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">` + shaDigest64 + `</Password>
<Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">` + nonce64 + `</Nonce>
<Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">` + timestamp + `</Created>
</UsernameToken>
</Security>`
}