commit - ef08e53ee37488a036de6aacf1895d843ab065a0
commit + 5740e5eef190ea7e2a372f6c5da31c40910fe507
blob - bf9410a1356f23098df574452efc9126020f4ea7
blob + ef73037cc9d6f7a5afef4090b30f7e5fb2bc9e5b
--- internal/sip/sip.go
+++ internal/sip/sip.go
"fmt"
"io"
"net/textproto"
+ "net/url"
"strconv"
"strings"
"unicode"
ContentLength int64
ContentType string
Sequence int
+ To Address
+ From Address
Via Via
Body io.Reader
+}
+
+type URI url.URL
+
+func (u URI) String() string {
+ return "<" + (*url.URL)(&u).String() + ">"
+}
+
+type Address struct {
+ Name string
+ URI
+ Tag string
+}
+
+func (a Address) String() string {
+ var tag string
+ if a.Tag != "" {
+ tag = ";tag=" + a.Tag
+ }
+ if a.Name != "" {
+ return fmt.Sprintf("%s %s%s", a.Name, a.URI, tag)
+ }
+ return a.URI.String() + tag
+}
+
+func ParseAddress(s string) (Address, error) {
+ s = strings.TrimSpace(s)
+
+ // TODO(otl): we're parsing header parameters - should we generalise somewhere?
+ // See section 20.
+ before, tag, found := strings.Cut(s, ";")
+ if found {
+ if !strings.HasPrefix(tag, "tag=") {
+ return Address{}, fmt.Errorf("bad tag: missing %q prefix", "tag=")
+ }
+ tag = tag[4:]
+ }
+ addr := Address{Tag: tag}
+
+ // bare URI without angle brackets
+ // e.g. "sip:test@example.com"
+ u, err := url.Parse(before)
+ if err == nil {
+ addr.URI = URI(*u)
+ return addr, nil
+ }
+
+ // URI without name
+ // e.g. "<sip:test@example.com>"
+ if strings.HasPrefix(before, "<") && strings.HasSuffix(before, ">") {
+ trimmed := strings.Trim(before, "<>")
+ u, err := url.Parse(trimmed)
+ if err != nil {
+ return addr, err
+ }
+ addr.URI = URI(*u)
+ return addr, nil
+ }
+
+ i := strings.Index(before, "<")
+ if i < 0 {
+ return addr, fmt.Errorf("missing angle bracket after name")
+ }
+ j := strings.Index(before, ">")
+ if j < 0 {
+ return addr, fmt.Errorf("missing closing angle bracket")
+ }
+ addr.Name = strings.TrimSpace(before[:i])
+
+ u, err = url.Parse(before[i+1 : j])
+ if err != nil {
+ return addr, fmt.Errorf("parse uri: %w", err)
+ }
+ addr.URI = URI(*u)
+ return addr, nil
}
const magicViaCookie = "z9hG4bK"
func WriteRequest(w io.Writer, req *Request) (n int64, err error) {
// section 8.1.1. We can set Max-Forwards automatically.
- required := []string{"To", "From", "CSeq", "Call-ID"}
+ required := []string{"CSeq", "Call-ID"}
for _, s := range required {
if req.Header.Get(s) == "" {
return 0, fmt.Errorf("missing field %s in header", s)
}
}
+
+ if req.To.URI.String() == "" {
+ return 0, fmt.Errorf("empty uri in to header field")
+ }
+ if req.From.URI.String() == "" {
+ return 0, fmt.Errorf("empty uri in from header field")
+ }
+ req.Header.Set("To", req.To.String())
+ req.Header.Set("From", req.From.String())
+
if req.Via.Address == "" {
return 0, fmt.Errorf("empty address in via header field")
} else if req.Via.Branch == "" {
blob - 6e2d88dd488c1be65fc6cdaeb96bfd4647f2611d
blob + 3a2f63e678d99994c90bf9877c1f1cad2ff11568
--- internal/sip/sip_test.go
+++ internal/sip/sip_test.go
func TestWriteRequest(t *testing.T) {
header := make(textproto.MIMEHeader)
- header.Set("Call-ID", "blabla")
- header.Set("To", "test <sip:test@example.com>")
- header.Set("From", "Oliver <sip:o@olowe.co>")
- header.Set("CSeq", "1 "+MethodRegister)
+ header.Set("Call-ID", "a84b4c76e66710@pc33.example.com")
+ header.Set("CSeq", "314159 "+MethodInvite)
+ header.Set("Contact", "<sip:alice@pc33.example.com>")
req := &Request{
- Method: MethodRegister,
- URI: "sip:test@example.com",
+ Method: MethodInvite,
+ URI: "sip:bob@example.com",
+ To: Address{Name: "Bob", URI: URI{Scheme: "sip", Opaque: "bob@example.com"}},
+ From: Address{Name: "Alice", URI: URI{Scheme: "sip", Opaque: "alice@example.com"}},
+ Via: Via{Address: "pc33.example.com", Branch: "776asdhds"},
Header: header,
}
- _, err := WriteRequest(io.Discard, req)
- if err == nil {
- t.Errorf("no error writing request with zero Via field")
+
+ if _, err := WriteRequest(io.Discard, req); err != nil {
+ t.Fatalf("write request: %v", err)
}
}
+func TestAddress(t *testing.T) {
+ var tests = []struct {
+ name string
+ addr string
+ want string
+ }{
+ {"bare", "sip:test@example.com", "<sip:test@example.com>"},
+ {"basic", "<sip:test@example.com>", "<sip:test@example.com>"},
+ {"bare tag", "sip:+1234@example.com;tag=887s", "<sip:+1234@example.com>;tag=887s"},
+ {"tag", "<sip:test@example.com>;tag=1234", "<sip:test@example.com>;tag=1234"},
+ {"name", "Oliver <sip:test@example.com>", "Oliver <sip:test@example.com>"},
+ {"name tag", "Oliver <sip:test@example.com>;tag=1234", "Oliver <sip:test@example.com>;tag=1234"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ParseAddress(tt.addr)
+ if err != nil {
+ t.Fatalf("parse %q: %v", tt.addr, err)
+ }
+ if got.String() != tt.want {
+ t.Fatalf("ParseAddress(%q) = %s, want %s", tt.addr, got, tt.want)
+ }
+ })
+ }
+}
+
func TestReadRequest(t *testing.T) {
f, err := os.Open("testdata/invite")
if err != nil {