diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go index 936f65f6..77bbe0ff 100644 --- a/pkg/onvif/client.go +++ b/pkg/onvif/client.go @@ -3,9 +3,10 @@ package onvif import ( "bytes" "errors" + "fmt" "html" "io" - "net/http" + "net" "net/url" "regexp" "strings" @@ -43,7 +44,14 @@ func NewClient(rawURL string) (*Client, error) { } client.mediaURL = FindTagValue(b, "Media.+?XAddr") + if client.mediaURL == "" { + client.mediaURL = baseURL + "/onvif/media_service" + } + client.imaginURL = FindTagValue(b, "Imaging.+?XAddr") + if client.imaginURL == "" { + client.imaginURL = baseURL + "/onvif/imaging_service" + } return client, nil } @@ -175,26 +183,62 @@ func (c *Client) MediaRequest(operation string) ([]byte, error) { return c.Request(c.mediaURL, operation) } -func (c *Client) Request(url, body string) ([]byte, error) { - if url == "" { +func (c *Client) Request(rawUrl, body string) ([]byte, error) { + if rawUrl == "" { return nil, errors.New("onvif: unsupported service") } e := NewEnvelopeWithUser(c.url.User) e.Append(body) - client := &http.Client{Timeout: time.Second * 5000} - res, err := client.Post(url, `application/soap+xml;charset=utf-8`, bytes.NewReader(e.Bytes())) + u, err := url.Parse(rawUrl) if err != nil { return nil, err } - // need to close body with eny response status - b, err := io.ReadAll(res.Body) - - if err == nil && res.StatusCode != http.StatusOK { - err = errors.New("onvif: " + res.Status + " for " + url) + host := u.Host + if u.Port() == "" { + host += ":80" } - return b, err + conn, err := net.DialTimeout("tcp", host, 5*time.Second) + if err != nil { + return nil, err + } + defer conn.Close() + + reqBody := e.Bytes() + rawReq := fmt.Appendf(nil, "POST %s HTTP/1.1\r\n"+ + "Host: %s\r\n"+ + "Content-Type: application/soap+xml;charset=utf-8\r\n"+ + "Content-Length: %d\r\n"+ + "Connection: close\r\n"+ + "\r\n", u.Path, u.Host, len(reqBody)) + rawReq = append(rawReq, reqBody...) + + if _, err = conn.Write(rawReq); err != nil { + return nil, err + } + + rawRes, err := io.ReadAll(conn) + if err != nil { + return nil, err + } + + // Look for XML in complete response + if i := bytes.Index(rawRes, []byte(" 0 { + return rawRes[i:], nil + } + + // No XML found - might be an error response + if i := bytes.Index(rawRes, []byte("\r\n\r\n")); i > 0 { + if bytes.Contains(rawRes[:i], []byte("chunked")) { + return nil, errors.New("onvif: TODO: support chunked encoding") + } + + // Return body after headers + return rawRes[i+4:], nil + } + + return rawRes, nil }