153 lines
3.9 KiB
Go
153 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// SOAP Envelope structures
|
|
type Envelope struct {
|
|
XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Envelope"`
|
|
Header *Header `xml:"http://www.w3.org/2003/05/soap-envelope Header,omitempty"`
|
|
Body Body `xml:"http://www.w3.org/2003/05/soap-envelope Body"`
|
|
}
|
|
|
|
type Header struct {
|
|
Security *Security `xml:"Security,omitempty"`
|
|
}
|
|
|
|
type Body struct {
|
|
Content interface{} `xml:",omitempty"`
|
|
}
|
|
|
|
type Security struct {
|
|
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
|
|
MustUnderstand string `xml:"http://www.w3.org/2003/05/soap-envelope mustUnderstand,attr,omitempty"`
|
|
UsernameToken *UsernameToken `xml:"UsernameToken,omitempty"`
|
|
}
|
|
|
|
type UsernameToken struct {
|
|
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"`
|
|
Username string `xml:"Username"`
|
|
Password Password `xml:"Password"`
|
|
Nonce Nonce `xml:"Nonce"`
|
|
Created string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Created"`
|
|
}
|
|
|
|
type Password struct {
|
|
Type string `xml:"Type,attr"`
|
|
Password string `xml:",chardata"`
|
|
}
|
|
|
|
type Nonce struct {
|
|
Type string `xml:"EncodingType,attr"`
|
|
Nonce string `xml:",chardata"`
|
|
}
|
|
|
|
type GetDeviceInformation struct {
|
|
XMLName xml.Name `xml:"tds:GetDeviceInformation"`
|
|
Xmlns string `xml:"xmlns:tds,attr"`
|
|
}
|
|
|
|
func createSecurityHeader(username, password string) *Security {
|
|
nonceBytes := make([]byte, 16)
|
|
_, _ = rand.Read(nonceBytes)
|
|
nonce := base64.StdEncoding.EncodeToString(nonceBytes)
|
|
|
|
created := time.Now().UTC().Format(time.RFC3339)
|
|
|
|
hash := sha1.New()
|
|
hash.Write(nonceBytes)
|
|
hash.Write([]byte(created))
|
|
hash.Write([]byte(password))
|
|
digest := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
|
|
|
return &Security{
|
|
MustUnderstand: "1",
|
|
UsernameToken: &UsernameToken{
|
|
Username: username,
|
|
Password: Password{
|
|
Type: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest",
|
|
Password: digest,
|
|
},
|
|
Nonce: Nonce{
|
|
Type: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary",
|
|
Nonce: nonce,
|
|
},
|
|
Created: created,
|
|
},
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
endpoint := "http://192.168.1.201/onvif/device_service"
|
|
username := "service"
|
|
password := "Service.1234"
|
|
|
|
fmt.Println("Testing direct SOAP request to camera...")
|
|
|
|
// Build request
|
|
req := GetDeviceInformation{
|
|
Xmlns: "http://www.onvif.org/ver10/device/wsdl",
|
|
}
|
|
|
|
envelope := &Envelope{
|
|
Header: &Header{
|
|
Security: createSecurityHeader(username, password),
|
|
},
|
|
Body: Body{
|
|
Content: req,
|
|
},
|
|
}
|
|
|
|
// Marshal to XML
|
|
body, err := xml.MarshalIndent(envelope, "", " ")
|
|
if err != nil {
|
|
log.Fatalf("Failed to marshal: %v", err)
|
|
}
|
|
|
|
xmlBody := append([]byte(xml.Header), body...)
|
|
|
|
fmt.Println("\n=== Request XML ===")
|
|
fmt.Println(string(xmlBody))
|
|
|
|
// Create HTTP request
|
|
httpReq, err := http.NewRequestWithContext(context.Background(), "POST", endpoint, bytes.NewReader(xmlBody))
|
|
if err != nil {
|
|
log.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
|
|
httpReq.Header.Set("Content-Type", "application/soap+xml; charset=utf-8")
|
|
|
|
// Send request
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Do(httpReq)
|
|
if err != nil {
|
|
log.Fatalf("Failed to send request: %v", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
// Read response
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Fatalf("Failed to read response: %v", err)
|
|
}
|
|
|
|
fmt.Printf("\n=== HTTP Status: %d ===\n", resp.StatusCode)
|
|
fmt.Printf("\n=== Response Headers ===\n")
|
|
for k, v := range resp.Header {
|
|
fmt.Printf("%s: %v\n", k, v)
|
|
}
|
|
fmt.Printf("\n=== Response Body ===\n")
|
|
fmt.Println(string(respBody))
|
|
}
|