From d0ac99fc69f84ab433624663c746a9d085b0012d Mon Sep 17 00:00:00 2001 From: seydx Date: Mon, 10 Feb 2025 20:21:25 +0100 Subject: [PATCH 1/2] fix onvif client --- pkg/onvif/client.go | 82 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go index cb6221e1..3b1f065d 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" @@ -37,13 +38,22 @@ func NewClient(rawURL string) (*Client, error) { client.deviceURL = baseURL + u.Path } + // Set default media URL before trying to get capabilities + client.mediaURL = baseURL + "/onvif/media_service" + client.imaginURL = baseURL + "/onvif/imaging_service" + b, err := client.DeviceRequest(DeviceGetCapabilities) if err != nil { return nil, err } - client.mediaURL = FindTagValue(b, "Media.+?XAddr") - client.imaginURL = FindTagValue(b, "Imaging.+?XAddr") + // Update URLs if found in capabilities + if mediaAddr := FindTagValue(b, "Media.+?XAddr"); mediaAddr != "" { + client.mediaURL = mediaAddr + } + if imagingAddr := FindTagValue(b, "Imaging.+?XAddr"); imagingAddr != "" { + client.imaginURL = imagingAddr + } return client, nil } @@ -172,26 +182,70 @@ 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) + // Ensure we have a port + host := u.Host + if !strings.Contains(host, ":") { + host = host + ":80" } - return b, err -} + // Connect with timeout + conn, err := net.DialTimeout("tcp", host, 5*time.Second) + if err != nil { + return nil, err + } + defer conn.Close() + + // Send request + httpReq := fmt.Sprintf("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%s", u.Path, u.Host, len(e.Bytes()), e.Bytes()) + + if _, err = conn.Write([]byte(httpReq)); err != nil { + return nil, err + } + + // Read full response first + var fullResponse []byte + buf := make([]byte, 4096) + for { + n, err := conn.Read(buf) + if n > 0 { + fullResponse = append(fullResponse, buf[:n]...) + } + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + } + + // Look for XML in complete response + if idx := bytes.Index(fullResponse, []byte("= 0 { + return fullResponse[idx:], nil + } + + // No XML found - might be an error response + if idx := bytes.Index(fullResponse, []byte("\r\n\r\n")); idx >= 0 { + // Return body after headers + return fullResponse[idx+4:], nil + } + + return fullResponse, nil +} \ No newline at end of file From 7291c03cea72f283a2eed92738b54692480fa8ba Mon Sep 17 00:00:00 2001 From: Alex X Date: Fri, 10 Oct 2025 12:38:20 +0300 Subject: [PATCH 2/2] Code refactoring for #1589 --- pkg/onvif/client.go | 68 +++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go index 2d48e653..77bbe0ff 100644 --- a/pkg/onvif/client.go +++ b/pkg/onvif/client.go @@ -38,22 +38,20 @@ func NewClient(rawURL string) (*Client, error) { client.deviceURL = baseURL + u.Path } - // Set default media URL before trying to get capabilities - client.mediaURL = baseURL + "/onvif/media_service" - client.imaginURL = baseURL + "/onvif/imaging_service" - b, err := client.DeviceRequest(DeviceGetCapabilities) if err != nil { return nil, err } - // Update URLs if found in capabilities - if mediaAddr := FindTagValue(b, "Media.+?XAddr"); mediaAddr != "" { - client.mediaURL = mediaAddr - } - if imagingAddr := FindTagValue(b, "Imaging.+?XAddr"); imagingAddr != "" { - client.imaginURL = imagingAddr - } + 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 } @@ -198,57 +196,49 @@ func (c *Client) Request(rawUrl, body string) ([]byte, error) { return nil, err } - // Ensure we have a port host := u.Host - if !strings.Contains(host, ":") { - host = host + ":80" + if u.Port() == "" { + host += ":80" } - // Connect with timeout conn, err := net.DialTimeout("tcp", host, 5*time.Second) if err != nil { return nil, err } defer conn.Close() - // Send request - httpReq := fmt.Sprintf("POST %s HTTP/1.1\r\n"+ + 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%s", u.Path, u.Host, len(e.Bytes()), e.Bytes()) + "\r\n", u.Path, u.Host, len(reqBody)) + rawReq = append(rawReq, reqBody...) - if _, err = conn.Write([]byte(httpReq)); err != nil { + if _, err = conn.Write(rawReq); err != nil { return nil, err } - // Read full response first - var fullResponse []byte - buf := make([]byte, 4096) - for { - n, err := conn.Read(buf) - if n > 0 { - fullResponse = append(fullResponse, buf[:n]...) - } - if err == io.EOF { - break - } - if err != nil { - return nil, err - } + rawRes, err := io.ReadAll(conn) + if err != nil { + return nil, err } // Look for XML in complete response - if idx := bytes.Index(fullResponse, []byte("= 0 { - return fullResponse[idx:], nil + if i := bytes.Index(rawRes, []byte(" 0 { + return rawRes[i:], nil } // No XML found - might be an error response - if idx := bytes.Index(fullResponse, []byte("\r\n\r\n")); idx >= 0 { + 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 fullResponse[idx+4:], nil + return rawRes[i+4:], nil } - return fullResponse, nil -} \ No newline at end of file + return rawRes, nil +}