diff --git a/client.go b/client.go index 5236053..29a890a 100644 --- a/client.go +++ b/client.go @@ -256,9 +256,10 @@ func clientRequestBuildReq(c *Client, req *sip.Request) error { // https://www.rfc-editor.org/rfc/rfc3261#section-8.1.1 // A valid SIP request formulated by a UAC MUST, at a minimum, contain // the following header fields: To, From, CSeq, Call-ID, Max-Forwards, - // and Via; + // Via, and User-Agent; + + mustHeader := make([]sip.Header, 0, 7) - mustHeader := make([]sip.Header, 0, 6) if v := req.Via(); v == nil { // Multi VIA value must be manually added via := clientRequestCreateVia(c, req) @@ -313,6 +314,13 @@ func clientRequestBuildReq(c *Client, req *sip.Request) error { } + if v := req.UserAgent(); v == nil { + + useragent := sip.UserAgentHeader(c.UserAgent.uaHeader) + mustHeader = append(mustHeader, &useragent) + + } + if v := req.CSeq(); v == nil { cseq := sip.CSeqHeader{ SeqNo: 1, diff --git a/example/proxysip/go.mod b/example/proxysip/go.mod index 9504534..236ad9a 100644 --- a/example/proxysip/go.mod +++ b/example/proxysip/go.mod @@ -8,7 +8,7 @@ require ( github.com/arl/statsviz v0.6.0 github.com/emiago/sipgo v0.23.1-0.20240913054121-597f4c4406dd github.com/prometheus/client_golang v1.17.0 - github.com/rs/zerolog v1.32.0 + github.com/rs/zerolog v1.33.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 ) @@ -33,7 +33,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.24.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/example/proxysip/go.sum b/example/proxysip/go.sum index ed1362b..f50d786 100644 --- a/example/proxysip/go.sum +++ b/example/proxysip/go.sum @@ -59,6 +59,7 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -79,6 +80,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= diff --git a/sip/headers.go b/sip/headers.go index d6d09e4..a95a1ed 100644 --- a/sip/headers.go +++ b/sip/headers.go @@ -51,6 +51,8 @@ type headers struct { maxForwards *MaxForwardsHeader referTo *ReferToHeader referredBy *ReferredByHeader + + useragent *UserAgentHeader } func (hs *headers) String() string { @@ -96,6 +98,8 @@ func (hs *headers) setHeaderRef(header Header) { hs.contentType = m case *MaxForwardsHeader: hs.maxForwards = m + case *UserAgentHeader: + hs.useragent = m } } @@ -123,6 +127,8 @@ func (hs *headers) unref(header Header) { hs.contentType = nil case *MaxForwardsHeader: hs.maxForwards = nil + case *UserAgentHeader: + hs.useragent = nil } } @@ -427,6 +433,17 @@ func (hs *headers) ParseReferredBy() *ReferredByHeader { return nil } +// UserAgent returns underlying UserAgent parsed header or nil if not exists +func (hs *headers) UserAgent() *UserAgentHeader { + if hs.useragent == nil { + var h UserAgentHeader + if parseHeaderLazy(hs, parseUserAgentHeader, []string{"user-agent", "i"}, &h) { + hs.useragent = &h + } + } + return hs.useragent +} + // NewHeader creates generic type of header func NewHeader(name, value string) Header { return &genericHeader{ diff --git a/sip/parse_header.go b/sip/parse_header.go index 4fe4817..9de1788 100644 --- a/sip/parse_header.go +++ b/sip/parse_header.go @@ -148,6 +148,16 @@ func parseCallIdHeader(headerText string, callId *CallIDHeader) error { return nil } +func parseUserAgentHeader(headerText string, useragent *UserAgentHeader) error { + headerText = strings.TrimSpace(headerText) + if len(headerText) == 0 { + return fmt.Errorf("empty User-Agent body") + } + + *useragent = UserAgentHeader(headerText) + return nil +} + func headerParserMaxForwards(headerName string, headerText string) (header Header, err error) { var maxfwd MaxForwardsHeader return &maxfwd, parseMaxForwardsHeader(headerText, &maxfwd) diff --git a/sip/user_agent_header.go b/sip/user_agent_header.go new file mode 100644 index 0000000..6c2eaad --- /dev/null +++ b/sip/user_agent_header.go @@ -0,0 +1,33 @@ +package sip + +import ( + "io" + "strings" +) + +// UserAgentHeader is User-Agent header representation. +type UserAgentHeader string + +func (h *UserAgentHeader) String() string { + var buffer strings.Builder + h.StringWrite(&buffer) + return buffer.String() +} + +func (h *UserAgentHeader) StringWrite(buffer io.StringWriter) { + buffer.WriteString(h.Name()) + buffer.WriteString(": ") + buffer.WriteString(h.Value()) +} + +func (h *UserAgentHeader) Name() string { return "User-Agent" } + +func (h *UserAgentHeader) Value() string { + if h == nil { + return "" + } else { + return string(*h) + } +} + +func (h *UserAgentHeader) headerClone() Header { return h } diff --git a/ua.go b/ua.go index 6e2faea..207071d 100644 --- a/ua.go +++ b/ua.go @@ -10,6 +10,7 @@ import ( type UserAgent struct { name string hostname string + uaHeader string dnsResolver *net.Resolver tlsConfig *tls.Config parser *sip.Parser @@ -21,9 +22,10 @@ type UserAgentOption func(s *UserAgent) error // WithUserAgent changes user agent name // Default: sipgo -func WithUserAgent(ua string) UserAgentOption { +func WithUserAgent(username, uaHeader string) UserAgentOption { return func(s *UserAgent) error { - s.name = ua + s.name = username + s.uaHeader = uaHeader return nil } }