Files
go-onvif/soap.go
Rod Apeldoorn 03ded6c189 Updates to uuid package broke backwards compatibility.
Updated discoverDevices and createUserToken functions to handle errors properly. This includes the rare possibility createUserToken could send a new SOAP fault message if a uuid can not be generated (only in out-of-memory situations).

Added vendoring to prevent future dependency issues.

Side Note: Had vendoring not been added, the uuid dependency would cause problems if this go-onvif package was added to a project using vendoring. This is due to go defaulting to a last commit to the master branch, while vendoring defaults to the last published version. This quirk was obvious after seeing the uuid package pushed the breaking change to the master without also pushing it as a new version.
2018-08-28 16:46:42 -07:00

146 lines
3.6 KiB
Go

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 * 5}
// 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
request := soap.createRequest()
// Make sure URL valid and add authentication in xAddr
urlXAddr, err := url.Parse(xaddr)
if err != nil {
return nil, err
}
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 != "" {
soapHeader, err := soap.createUserToken()
if err != nil {
request += "<s:Header />"
soap.Body = `
<s:Fault>
<s:Code>
<s:Value>s:Receiver</s:Value>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-US">Error creating user token</s:Text>
</s:Reason>
</s:Fault>`
} else {
request += "<s:Header>" + soapHeader + "</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, error) {
newUuid, err := uuid.NewV4()
if err != nil {
return "", err
}
nonce := newUuid.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>`, nil
}