Files

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))
}