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 := ``
request += `"
// Set request header
if soap.User != "" {
soapHeader, err := soap.createUserToken()
if err != nil {
request += ""
soap.Body = `
s:Receiver
Error creating user token
`
} else {
request += "" + soapHeader + ""
}
}
// Set request body
request += "" + soap.Body + ""
// Close request envelope
request += ""
// 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 `
` + soap.User + `
` + shaDigest64 + `
` + nonce64 + `
` + timestamp + `
`, nil
}