Skip to content

Commit

Permalink
comma seperated header will be seperated into multiple header values #20
Browse files Browse the repository at this point in the history
  • Loading branch information
emiago committed Jun 2, 2023
1 parent ba3d4e3 commit a79c2ee
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 106 deletions.
39 changes: 6 additions & 33 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ func ClientRequestAddVia(c *Client, r *sip.Request) error {
Host: c.host,
Port: c.port,
Params: sip.NewParams(),
Next: nil,
}
// NOTE: Consider lenght of branch configurable
newvia.Params.Add("branch", sip.GenerateBranchN(16))
Expand Down Expand Up @@ -209,45 +208,19 @@ func ClientRequestDecreaseMaxForward(c *Client, r *sip.Request) error {
// ClientResponseRemoveVia is needed when handling client transaction response, where previously used in
// TransactionRequest with ClientRequestAddVia
func ClientResponseRemoveVia(c *Client, r *sip.Response) {
// var removedFromMulti bool
// Faster access TODO
// if via, exists := r.Via(); exists {
// prevvia := via
// for via != nil {
// if via.Host == c.host {

// removedFromMulti = true
// break
// }
// via = via.Next
// prevvia = via
// }
// via, exists := r.Via()
// if !exists {
// return
// }
// if !removedFromMulti {
// r.RemoveHeaderOn("Via", c.removeViaCallback)
// if via.Host == c.host {
// r.RemoveHeader("Via")
// }

r.RemoveHeaderOn("Via", c.removeViaCallback)
}

func (c *Client) removeViaCallback(h sip.Header) bool {
// This is slow
via := h.(*sip.ViaHeader)

// Check is this multivalue
// If yes then just remove that value
// TODO can this be done better
if via.Next != nil {
prevvia := via
for via != nil {
if via.Host == c.host {
prevvia.Next = via.Next
via.Next = nil
return false
}
prevvia = via
via = via.Next
}
}

return via.Host == c.host
}
6 changes: 3 additions & 3 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ func TestClientRequestOptions(t *testing.T) {
// Lets make via multivalue
req = createSimpleRequest(sip.INVITE, sender, recipment, "UDP")
via, _ = req.Via()
via.Next = &tmpvia
req.AppendHeader(&tmpvia)
res = sip.NewResponseFromRequest(req, 400, "", nil)
ClientResponseRemoveVia(c, res)
viaprev, _ = res.Via()
assert.Equal(t, via.Host, viaprev.Host)
assert.Nil(t, viaprev.Next)
// assert.Equal(t, oldvia.Next, nil)

assert.Len(t, res.GetHeaders("Via"), 1)
}
6 changes: 6 additions & 0 deletions parser/parse_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import (
// A HeaderParser is any function that turns raw header data into one or more Header objects.
type HeaderParser func(headerName string, headerData string) (sip.Header, error)

type errComaDetected int

func (e errComaDetected) Error() string {
return "comma detected"
}

// This needs to kept minimalistic in order to avoid overhead of parsing
var headersParsers = map[string]HeaderParser{
"to": parseToAddressHeader,
Expand Down
53 changes: 32 additions & 21 deletions parser/parse_via.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/emiago/sipgo/sip"
)

// Via header is important header
// Via header is important header

// Note that although Via headers may contain a comma-separated list, RFC 3261 makes it clear that
// these should not be treated as separate logical Via headers, but as multiple values on a single
Expand All @@ -21,40 +21,48 @@ func parseViaHeader(headerName string, headerText string) (
state := viaStateProtocol
str := headerText
hop := &h
var ind, nextInd int

for state != nil {
state, str, err = state(hop, str)
state, nextInd, err = state(hop, str[ind:])
if err != nil {

// Fix the offset
if _, ok := err.(errComaDetected); ok {
err = errComaDetected(ind + nextInd)
}
return
}
// If we alocated next hop this means we hit coma
if hop.Next != nil {
hop = h.Next
}
// if hop.Next != nil {
// hop = h.Next
// }
ind += nextInd
}
return &h, nil
}

type viaFSM func(h *sip.ViaHeader, s string) (viaFSM, string, error)
type viaFSM func(h *sip.ViaHeader, s string) (viaFSM, int, error)

func viaStateProtocol(h *sip.ViaHeader, s string) (viaFSM, string, error) {
func viaStateProtocol(h *sip.ViaHeader, s string) (viaFSM, int, error) {
ind := strings.IndexRune(s, '/')
h.ProtocolName = s[:ind]
return viaStateProtocolVersion, s[ind+1:], nil
return viaStateProtocolVersion, ind + 1, nil
}

func viaStateProtocolVersion(h *sip.ViaHeader, s string) (viaFSM, string, error) {
func viaStateProtocolVersion(h *sip.ViaHeader, s string) (viaFSM, int, error) {
ind := strings.IndexRune(s, '/')
h.ProtocolVersion = s[:ind]
return viaStateProtocolTransport, s[ind+1:], nil
return viaStateProtocolTransport, ind + 1, nil
}

func viaStateProtocolTransport(h *sip.ViaHeader, s string) (viaFSM, string, error) {
func viaStateProtocolTransport(h *sip.ViaHeader, s string) (viaFSM, int, error) {
ind := strings.IndexAny(s, " \t")
h.Transport = s[:ind]
return viaStateHost, s[ind+1:], nil
return viaStateHost, ind + 1, nil
}

func viaStateHost(h *sip.ViaHeader, s string) (viaFSM, string, error) {
func viaStateHost(h *sip.ViaHeader, s string) (viaFSM, int, error) {
var colonInd int
var endIndex int = len(s)
var err error
Expand All @@ -73,36 +81,39 @@ loop:
if colonInd > 0 {
h.Port, err = strconv.Atoi(s[colonInd+1 : endIndex])
if err != nil {
return nil, "", nil
return nil, 0, nil
}
h.Host = s[:colonInd]
} else {
h.Host = s[:endIndex]
}

if endIndex == len(s) {
return nil, "", nil
return nil, 0, nil
}

// return nil, "", nil
return viaStateParams, s[endIndex+1:], nil
return viaStateParams, endIndex + 1, nil
}

func viaStateParams(h *sip.ViaHeader, s string) (viaFSM, string, error) {
func viaStateParams(h *sip.ViaHeader, s string) (viaFSM, int, error) {
var err error
coma := strings.IndexRune(s, ',')
if coma > 0 {
// h.Params, _, err = ParseParams(s[:coma], ';', ';', 0, true, true)
// h.Params, _, err = ParseParams(s[:coma], ';', ';')
_, err = UnmarshalParams(s[:coma], ';', ',', h.Params)
h.Next = &sip.ViaHeader{
Params: sip.HeaderParams{},
if err != nil {
return nil, 0, err
}
return viaStateProtocol, s[coma+1:], err
// h.Next = &sip.ViaHeader{
// Params: sip.HeaderParams{},
// }
return viaStateProtocol, coma, errComaDetected(coma)
}

// h.Params, _, err = ParseParams(s, ';', ';', 0, true, true)
// h.Params, _, err = ParseParams(s, ';', ';')
_, err = UnmarshalParams(s, ';', '\r', h.Params)
return nil, "", err
return nil, 0, err
}
56 changes: 52 additions & 4 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,8 @@ func (p *Parser) ParseSIP(data []byte) (msg sip.Message, err error) {
break
}

header, err := p.parseHeader(line)
if err == nil {
msg.AppendHeader(header)
} else {
err = p.parseMsgHeader(msg, line)
if err != nil {
p.log.Info().Err(err).Str("line", line).Msg("skip header due to error")
}
}
Expand Down Expand Up @@ -174,6 +172,56 @@ func (p *Parser) parseHeader(headerText string) (header sip.Header, err error) {
return
}

// parseMsgHeader will append any parsed header
// in case comma seperated values it will add them as new in case comma is detected
func (p *Parser) parseMsgHeader(msg sip.Message, headerText string) (err error) {
// p.log.Tracef("parsing header \"%s\"", headerText)

colonIdx := strings.Index(headerText, ":")
if colonIdx == -1 {
err = fmt.Errorf("field name with no value in header: %s", headerText)
return
}

fieldName := strings.TrimSpace(headerText[:colonIdx])
lowerFieldName := sip.HeaderToLower(fieldName)
fieldText := strings.TrimSpace(headerText[colonIdx+1:])

headerParser, ok := p.headersParsers[lowerFieldName]
if !ok {
// We have no registered parser for this header type,
// so we encapsulate the header data in a GenericHeader struct.
// p.log.Tracef("no parser for header type %s", fieldName)

// TODO Should we check for comma here as well ??
header := sip.NewHeader(fieldName, fieldText)
msg.AppendHeader(header)
return nil
}

// Support comma seperated value
for {
// We have a registered parser for this header type - use it.
// headerParser should detect comma (,) and return as error
header, err := headerParser(lowerFieldName, fieldText)

// Mostly we will run with no error
if err == nil {
msg.AppendHeader(header)
return nil
}

commaErr, ok := err.(errComaDetected)
if !ok {
return err
}

// Ok we detected we have comma in header value
msg.AppendHeader(header)
fieldText = fieldText[commaErr:]
}
}

func ParseLine(startLine string) (msg sip.Message, err error) {
if isRequest(startLine) {
recipient := sip.Uri{}
Expand Down
59 changes: 15 additions & 44 deletions sip/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,6 @@ type ViaHeader struct {
Host string
Port int // This is optional
Params HeaderParams
Next *ViaHeader
}

func (hop *ViaHeader) SentBy() string {
Expand Down Expand Up @@ -799,30 +798,22 @@ func (h *ViaHeader) Value() string {
}

func (h *ViaHeader) ValueStringWrite(buffer io.StringWriter) {
hop := h
for hop != nil {
buffer.WriteString(hop.ProtocolName)
buffer.WriteString("/")
buffer.WriteString(hop.ProtocolVersion)
buffer.WriteString("/")
buffer.WriteString(hop.Transport)
buffer.WriteString(" ")
buffer.WriteString(hop.Host)

if hop.Port > 0 {
buffer.WriteString(":")
buffer.WriteString(strconv.Itoa(hop.Port))
}
buffer.WriteString(h.ProtocolName)
buffer.WriteString("/")
buffer.WriteString(h.ProtocolVersion)
buffer.WriteString("/")
buffer.WriteString(h.Transport)
buffer.WriteString(" ")
buffer.WriteString(h.Host)

if hop.Params != nil && hop.Params.Length() > 0 {
buffer.WriteString(";")
hop.Params.ToStringWrite(';', buffer)
}
if h.Port > 0 {
buffer.WriteString(":")
buffer.WriteString(strconv.Itoa(h.Port))
}

if hop.Next != nil {
buffer.WriteString(", ")
}
hop = hop.Next
if h.Params != nil && h.Params.Length() > 0 {
buffer.WriteString(";")
h.Params.ToStringWrite(';', buffer)
}
}

Expand All @@ -832,27 +823,7 @@ func (h *ViaHeader) headerClone() Header {
}

func (h *ViaHeader) Clone() *ViaHeader {
newHop := h.cloneMe()

newNext := newHop
tmp := h.Next
for tmp != nil {
clone := tmp.cloneMe()
newNext.Next = clone

newNext = clone
tmp = tmp.Next
}
return newHop
}

func (h *ViaHeader) cloneMe() *ViaHeader {
var newHop *ViaHeader
if h == nil {
return newHop
}

newHop = &ViaHeader{
newHop := &ViaHeader{
ProtocolName: h.ProtocolName,
ProtocolVersion: h.ProtocolVersion,
Transport: h.Transport,
Expand Down
2 changes: 1 addition & 1 deletion sip/headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestPrependHeader(t *testing.T) {
assert.Equal(t, 1, len(hs.headerOrder))

v := &ViaHeader{}
hs.PrependHeader(v.cloneMe())
hs.PrependHeader(v.Clone())
assert.Equal(t, 2, len(hs.headerOrder))
assert.Equal(t, v, hs.GetHeader("via"))
}
Expand Down

0 comments on commit a79c2ee

Please sign in to comment.